diff --git a/.codespellrc b/.codespellrc
index fdf6f1d61..0873d2227 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -1,3 +1,3 @@
[codespell]
skip = [setup.cfg]
-ignore-words-list = Reson, DNE, ACI, FPT, sagital, saggital, abd, Joo, Mapp, Commun, vor, Claus, coo
\ No newline at end of file
+ignore-words-list = Reson, DNE, ACI, FPT, sagital, saggital, abd, Joo, Mapp, Commun, vor, Claus, coo, ans
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 436634144..37b3f8d1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,6 @@ AFQ/version.py
docs/_build
docs/build
docs/source/auto_examples/
-docs/source/tutorials/tutorial_examples/
-docs/source/howto/howto_examples/
docs/source/reference/config.rst
examples/**/*.nii.gz
examples/**/*.trk
diff --git a/AFQ/_fixes.py b/AFQ/_fixes.py
index b6b7b2f4c..d38488a81 100644
--- a/AFQ/_fixes.py
+++ b/AFQ/_fixes.py
@@ -3,6 +3,7 @@
import tempfile
from math import radians
+import imageio
import numpy as np
from dipy.align import vector_fields as vfu
from dipy.align.imwarp import DiffeomorphicMap, mult_aff
@@ -11,7 +12,7 @@
from PIL import Image
from scipy.linalg import blas, pinvh
from scipy.special import gammaln, lpmv
-from tqdm import tqdm
+from tqdm.auto import tqdm
logger = logging.getLogger("AFQ")
@@ -361,9 +362,9 @@ def _weighting_failed():
return w
-def make_gif(show_m, out_path, n_frames=36, az_ang=-10, duration=150):
+def make_mp4(show_m, out_path, n_frames=720, az_ang=-0.5, fps=30, crf=35, verbose=True):
"""
- Make a video from a Fury Show Manager.
+ Make an MP4 video from a Fury Show Manager with auto-cropping.
Parameters
----------
@@ -371,28 +372,43 @@ def make_gif(show_m, out_path, n_frames=36, az_ang=-10, duration=150):
The Fury Show Manager to use for rendering.
out_path : str
- The name of the output file.
+ The name of the output file
n_frames : int
The number of frames to render.
- Default: 36
+ Default: 720
az_ang : float
The angle to rotate the camera around the
z-axis for each frame, in degrees.
- Default: -10
+ Default: -0.5
- duration : int
- The duration of each frame in the output GIF, in milliseconds.
- Default: 150
+ fps : float
+ The frames per second for the output video.
+ Default: 30
+
+ crf : int
+ The Constant Rate Factor for the output video, which controls the
+ quality and file size. Lower values result in
+ higher quality and larger file sizes.
+ Default: 35 (very low quality, small file size)
+
+ verbose : bool
+ Whether to show a progress bar while generating the video.
+ Default: True
"""
+ if not out_path.lower().endswith(".mp4"):
+ out_path += ".mp4"
+
video = []
show_m.render()
show_m.window.draw()
with tempfile.TemporaryDirectory() as tmp_dir:
- for ii in tqdm(range(n_frames), desc="Generating GIF", leave=False):
+ for ii in tqdm(
+ range(n_frames), desc="Generating MP4", leave=False, disable=not verbose
+ ):
frame_fname = f"{tmp_dir}/{ii}.png"
show_m.screens[0].controller.rotate((radians(az_ang), 0), None)
show_m.render()
@@ -406,7 +422,6 @@ def make_gif(show_m, out_path, n_frames=36, az_ang=-10, duration=150):
for img in video:
arr = np.array(img)
bg_color = arr[0, 0]
-
mask = np.any(arr != bg_color, axis=-1)
if np.any(mask):
@@ -421,20 +436,42 @@ def make_gif(show_m, out_path, n_frames=36, az_ang=-10, duration=150):
all_lower = max(all_lower, ymax)
if all_left < all_right:
- crop_box = (
- max(0, all_left),
- max(0, all_upper),
- min(video[0].width, all_right),
- min(video[0].height, all_lower),
- )
+
+ def align_down(x, multiple=16):
+ return (x // multiple) * multiple
+
+ def align_up(x, multiple=16):
+ return ((x + multiple - 1) // multiple) * multiple
+
+ left = align_up(max(0, all_left), 16)
+ upper = align_up(max(0, all_upper), 16)
+ right = align_down(min(video[0].width, all_right), 16)
+ lower = align_down(min(video[0].height, all_lower), 16)
+
+ crop_box = (left, upper, right, lower)
cropped_video = [img.crop(crop_box) for img in video]
else:
cropped_video = video
- cropped_video[0].save(
+ width, height = cropped_video[0].size
+ with imageio.get_writer(
out_path,
- save_all=True,
- append_images=cropped_video[1:],
- duration=duration,
- loop=1,
- )
+ fps=fps,
+ format="ffmpeg",
+ codec="libx264",
+ pixelformat="yuv420p",
+ output_params=[
+ "-crf",
+ f"{str(int(crf))}",
+ "-preset",
+ "veryslow",
+ "-movflags",
+ "+faststart",
+ ],
+ ) as writer:
+ for img in cropped_video:
+ if img.size != (width, height):
+ img = img.crop((0, 0, width, height))
+
+ frame_arr = np.array(img)
+ writer.append_data(frame_arr)
diff --git a/AFQ/api/bundle_dict.py b/AFQ/api/bundle_dict.py
index 76362ae45..ca1cc7a26 100644
--- a/AFQ/api/bundle_dict.py
+++ b/AFQ/api/bundle_dict.py
@@ -387,7 +387,7 @@ def default_bd():
"primary_axis": "I/S",
"ORG_spectral_subbundles": SpectralSubbundleDict(
{
- "Left V1V3": {
+ "Left Early Visual": {
"cluster_IDs": [78],
"Left Optic Radiation": {
"core": "Anterior",
@@ -449,7 +449,7 @@ def default_bd():
"primary_axis": "I/S",
"ORG_spectral_subbundles": SpectralSubbundleDict(
{
- "Right V1V3": {
+ "Right Early Visual": {
"cluster_IDs": [78],
"Right Optic Radiation": {
"core": "Anterior",
diff --git a/AFQ/api/group.py b/AFQ/api/group.py
index 559c9a801..0a3ff3af6 100644
--- a/AFQ/api/group.py
+++ b/AFQ/api/group.py
@@ -18,7 +18,7 @@
from dipy.io.streamline import save_tractogram
from dipy.utils.parallel import paramap
from PIL import Image
-from tqdm import tqdm
+from tqdm.auto import tqdm
import AFQ.api.bundle_dict as abd
import AFQ.definitions.image as afm
@@ -555,7 +555,7 @@ def load_next_subject():
ses
]
seg_sft = aus.SegmentedSFT.fromfile(this_bundles_file, this_img)
- seg_sft.sft.to_rasmm()
+ seg_sft.to_rasmm()
subses_info.append((seg_sft, this_mapping, this_img, this_reg_template))
bundle_dict = self.export("bundle_dict", collapse=False)[
@@ -567,7 +567,7 @@ def load_next_subject():
for b in bundle_dict.bundle_names:
for i in range(len(self.valid_sub_list)):
seg_sft, mapping, img, reg_template = subses_info[i]
- idx = seg_sft.bundle_idxs[b]
+ idx = seg_sft.get_bundle_idxs(b)
# use the first subses that works
# otherwise try each successive subses
if len(idx) == 0:
diff --git a/AFQ/api/participant.py b/AFQ/api/participant.py
index d5873e15a..194a3dba0 100644
--- a/AFQ/api/participant.py
+++ b/AFQ/api/participant.py
@@ -8,7 +8,7 @@
import nibabel as nib
from dipy.align import resample
from PIL import Image, ImageDraw, ImageFont
-from tqdm import tqdm
+from tqdm.auto import tqdm
import AFQ.utils.streamlines as aus
from AFQ.api.utils import (
diff --git a/AFQ/data/fetch.py b/AFQ/data/fetch.py
index 463b00203..a7f4f30d7 100644
--- a/AFQ/data/fetch.py
+++ b/AFQ/data/fetch.py
@@ -22,7 +22,7 @@
from dipy.segment.clustering import QuickBundles
from dipy.segment.featurespeed import ResampleFeature
from dipy.segment.metric import AveragePointwiseEuclideanMetric
-from tqdm import tqdm
+from tqdm.auto import tqdm
from AFQ._fixes import get_simplified_transform
from AFQ.data.utils import aws_import_msg_error
diff --git a/AFQ/models/asym_filtering.py b/AFQ/models/asym_filtering.py
index 13dbef8fd..f0bbed503 100644
--- a/AFQ/models/asym_filtering.py
+++ b/AFQ/models/asym_filtering.py
@@ -14,7 +14,7 @@
from dipy.direction import peak_directions
from dipy.reconst.shm import sh_to_sf, sh_to_sf_matrix, sph_harm_ind_list
from numba import config, njit, prange, set_num_threads
-from tqdm import tqdm
+from tqdm.auto import tqdm
logger = logging.getLogger("AFQ")
diff --git a/AFQ/models/msmt.py b/AFQ/models/msmt.py
index f161c6faa..d3d17a5d9 100644
--- a/AFQ/models/msmt.py
+++ b/AFQ/models/msmt.py
@@ -2,7 +2,7 @@
import osqp
from dipy.reconst.mcsd import MSDeconvFit, MultiShellDeconvModel
from scipy.sparse import csr_matrix
-from tqdm import tqdm
+from tqdm.auto import tqdm
__all__ = ["MultiShellDeconvModel"]
diff --git a/AFQ/nn/multiaxial.py b/AFQ/nn/multiaxial.py
index bee2374d9..3d1ad0aff 100644
--- a/AFQ/nn/multiaxial.py
+++ b/AFQ/nn/multiaxial.py
@@ -3,7 +3,7 @@
import nibabel as nib
import numpy as np
-from tqdm import tqdm
+from tqdm.auto import tqdm
from AFQ.data.fetch import afq_home, fetch_multiaxial_models
from AFQ.nn.utils import prepare_t1_for_nn, resample_output
diff --git a/AFQ/recognition/criteria.py b/AFQ/recognition/criteria.py
index 373710b1a..6c8ebf446 100644
--- a/AFQ/recognition/criteria.py
+++ b/AFQ/recognition/criteria.py
@@ -11,7 +11,7 @@
from dipy.segment.featurespeed import ResampleFeature
from dipy.segment.metricspeed import AveragePointwiseEuclideanMetric
from scipy.ndimage import distance_transform_edt
-from tqdm import tqdm
+from tqdm.auto import tqdm
from trx.io import load as load_trx
import AFQ.recognition.cleaning as abc
diff --git a/AFQ/recognition/utils.py b/AFQ/recognition/utils.py
index aef0a4127..8b8352e36 100644
--- a/AFQ/recognition/utils.py
+++ b/AFQ/recognition/utils.py
@@ -9,7 +9,7 @@
from dipy.io.streamline import save_tractogram
from dipy.tracking import Streamlines
from dipy.tracking.distances import bundles_distances_mdf
-from tqdm import tqdm
+from tqdm.auto import tqdm
axes_dict = {
"L/R": 0,
diff --git a/AFQ/tasks/segmentation.py b/AFQ/tasks/segmentation.py
index 69dcd5141..709f85c99 100644
--- a/AFQ/tasks/segmentation.py
+++ b/AFQ/tasks/segmentation.py
@@ -38,9 +38,7 @@
@immlib.calc("bundles")
@as_file("_desc-bundles_tractography")
-def segment(
- structural_imap, data_imap, mapping_imap, tractography_imap, segmentation_params
-):
+def segment(data_imap, mapping_imap, tractography_imap, segmentation_params):
"""
full path to a trk/trx file containing containing
segmented streamlines, labeled by bundle
@@ -93,16 +91,15 @@ def segment(
**segmentation_params,
)
- seg_sft = aus.SegmentedSFT(bundles)
-
- if len(seg_sft.sft) < 1:
+ if len(bundles) == 0:
raise ValueError("Fatal: No bundles recognized.")
+ seg_sft = aus.SegmentedSFT(bundles)
+
if is_trx:
seg_sft.sft.dtype_dict = {"positions": np.float16, "offsets": np.uint32}
tgram = TrxFile.from_sft(seg_sft.sft)
tgram.groups = seg_sft.bundle_idxs
-
else:
tgram = seg_sft.sft
@@ -209,15 +206,9 @@ def export_bundle_lengths(bundles):
len_data[f"{bundle} Median"] = 0
len_data[f"{bundle} Min"] = 0
len_data[f"{bundle} Max"] = 0
- len_data["Total Recognized Median"] = np.median(
- seg_sft.sft._tractogram._streamlines._lengths
- )
- len_data["Total Recognized Min"] = np.min(
- seg_sft.sft._tractogram._streamlines._lengths
- )
- len_data["Total Recognized Max"] = np.max(
- seg_sft.sft._tractogram._streamlines._lengths
- )
+ len_data["Total Recognized Median"] = np.median(seg_sft.get_lengths())
+ len_data["Total Recognized Min"] = np.min(seg_sft.get_lengths())
+ len_data["Total Recognized Max"] = np.max(seg_sft.get_lengths())
counts_df = pd.DataFrame(
data=len_data,
@@ -297,7 +288,7 @@ def tract_profiles(
reference = nib.load(scalar_dict[list(scalar_dict.keys())[0]])
seg_sft = aus.SegmentedSFT.fromfile(bundles, reference=reference)
- seg_sft.sft.to_rasmm()
+ seg_sft.to_rasmm()
for bundle_name in seg_sft.bundle_names:
this_sl = seg_sft.get_bundle(bundle_name).streamlines
if len(this_sl) == 0:
diff --git a/AFQ/tasks/viz.py b/AFQ/tasks/viz.py
index cf1a89099..20228cef0 100644
--- a/AFQ/tasks/viz.py
+++ b/AFQ/tasks/viz.py
@@ -120,13 +120,13 @@ def viz_bundles(
)
fname = None
- if "no_gif" not in viz_backend.backend:
- fname = get_fname(base_fname, ".gif", "..")
+ if "no_mp4" not in viz_backend.backend:
+ fname = get_fname(base_fname, ".mp4", "..")
try:
- viz_backend.create_gif(figure, fname)
+ viz_backend.create_mp4(figure, fname)
except PermissionError as e:
- logger.warning(f"Failed to write GIF file: {fname} \n{e}")
+ logger.warning(f"Failed to write MP4 file: {fname} \n{e}")
if "plotly" in viz_backend.backend:
fname = get_fname(base_fname, ".html", "..")
@@ -156,7 +156,7 @@ def viz_indivBundle(
n_points_indiv=40,
):
"""
- list of full paths to html or gif files
+ list of full paths to html or mp4 files
containing visualizations of individual bundles
Parameters
@@ -255,15 +255,15 @@ def viz_indivBundle(
base_fname = op.join(output_dir, op.split(base_fname)[1])
figures[bundle_name] = figure
- if "no_gif" not in viz_backend.backend:
+ if "no_mp4" not in viz_backend.backend:
fname = get_fname(
base_fname,
- f"_desc-{str_to_desc(bundle_name)}_tractography.gif",
+ f"_desc-{str_to_desc(bundle_name)}_tractography.mp4",
"viz_bundles",
)
try:
- viz_backend.create_gif(figure, fname)
+ viz_backend.create_mp4(figure, fname)
except PermissionError as e:
if not failed_write:
logger.warning(
@@ -380,7 +380,7 @@ def plot_tract_profiles(base_fname, output_dir, scalars, segmentation_imap):
@immlib.calc("viz_backend")
-def init_viz_backend(viz_backend_spec="plotly_no_gif"):
+def init_viz_backend(viz_backend_spec="plotly_no_mp4"):
"""
An instance of the `AFQ.viz.utils.viz_backend` class.
@@ -390,8 +390,8 @@ def init_viz_backend(viz_backend_spec="plotly_no_gif"):
Which visualization backend to use.
See Visualization Backends page in documentation for details
https://tractometry.org/pyAFQ/reference/viz_backend.html
- One of {"fury", "plotly", "plotly_no_gif"}.
- Default: "plotly_no_gif"
+ One of {"fury", "plotly", "plotly_no_mp4"}.
+ Default: "plotly_no_mp4"
"""
if "fury" not in viz_backend_spec and "plotly" not in viz_backend_spec:
raise TypeError("viz_backend_spec must contain either 'fury' or 'plotly'")
diff --git a/AFQ/tractography/tractography.py b/AFQ/tractography/tractography.py
index 9a6282619..33eb97e4b 100644
--- a/AFQ/tractography/tractography.py
+++ b/AFQ/tractography/tractography.py
@@ -18,7 +18,7 @@
)
from nibabel.streamlines.tractogram import LazyTractogram
from skimage.segmentation import find_boundaries
-from tqdm import tqdm
+from tqdm.auto import tqdm
from AFQ._fixes import tensor_odf
from AFQ.tractography.utils import gen_seeds
diff --git a/AFQ/utils/conversion.py b/AFQ/utils/conversion.py
index b3729a09c..ba579ffe7 100644
--- a/AFQ/utils/conversion.py
+++ b/AFQ/utils/conversion.py
@@ -2,7 +2,7 @@
import numpy as np
import scipy.io
from dipy.io.stateful_tractogram import Space, StatefulTractogram
-from tqdm import tqdm
+from tqdm.auto import tqdm
from AFQ.data.utils import BUNDLE_MAT_2_PYTHON
diff --git a/AFQ/utils/docs.py b/AFQ/utils/docs.py
index a46091725..fbf87794c 100644
--- a/AFQ/utils/docs.py
+++ b/AFQ/utils/docs.py
@@ -1,55 +1,24 @@
-import os
-import shutil
-from glob import glob
+import base64
-from sphinx_gallery.scrapers import figure_rst
+from IPython.display import HTML
-class PNGScraper(object):
- def __init__(self):
- self.seen = set()
+def embed_video(path):
+ with open(path, "rb") as f:
+ mp4_data = base64.b64encode(f.read()).decode()
+ return HTML(
+ f''
+ )
- def __repr__(self):
- return "PNGScraper"
- def __call__(self, block, block_vars, gallery_conf):
- # Find all PNG files in the directory of this example.
- path_current_example = os.path.dirname(block_vars["src_file"])
- pngs = sorted(glob(os.path.join(path_current_example, "*.png")))
+def embed_image(path):
+ with open(path, "rb") as f:
+ img_data = base64.b64encode(f.read()).decode()
+ return HTML(f'')
- # Iterate through PNGs, copy them to the sphinx-gallery output directory
- image_names = list()
- image_path_iterator = block_vars["image_path_iterator"]
- for png in pngs:
- if png not in self.seen:
- self.seen |= set(png)
- this_image_path = image_path_iterator.next()
- image_names.append(this_image_path)
- shutil.move(png, this_image_path)
- # Use the `figure_rst` helper function to generate rST for image files
- return figure_rst(image_names, gallery_conf["src_dir"])
-
-class GIFScraper(object):
- def __init__(self):
- self.seen = set()
-
- def __repr__(self):
- return "GIFScraper"
-
- def __call__(self, block, block_vars, gallery_conf):
- # Find all GIF files in the directory of this example.
- path_current_example = os.path.dirname(block_vars["src_file"])
- gifs = sorted(glob(os.path.join(path_current_example, "*.gif")))
-
- # Iterate through GIFs, copy them to the sphinx-gallery output directory
- image_names = list()
- image_path_iterator = block_vars["image_path_iterator"]
- for gif in gifs:
- if gif not in self.seen:
- self.seen |= set(gif)
- this_image_path = image_path_iterator.next()
- image_names.append(this_image_path)
- shutil.move(gif, this_image_path)
- # Use the `figure_rst` helper function to generate rST for image files
- return figure_rst(image_names, gallery_conf["src_dir"])
+def embed_html(path):
+ with open(path, "r") as f:
+ html_content = f.read()
+ return HTML(html_content)
diff --git a/AFQ/utils/streamlines.py b/AFQ/utils/streamlines.py
index 6d64003fc..5964aab98 100644
--- a/AFQ/utils/streamlines.py
+++ b/AFQ/utils/streamlines.py
@@ -1,8 +1,10 @@
import os.path as op
import numpy as np
-from dipy.io.stateful_tractogram import Space, StatefulTractogram
+from dipy.io.stateful_tractogram import Origin, Space, StatefulTractogram
from dipy.io.streamline import load_tractogram, save_tractogram
+from nibabel.affines import voxel_sizes
+from nibabel.orientations import aff2axcodes
try:
from trx.io import load as load_trx
@@ -17,8 +19,22 @@
class SegmentedSFT:
def __init__(self, bundles, sidecar_info=None):
+ """
+ Initialize a SegmentedSFT object.
+
+ Parameters
+ ----------
+ bundles : dict
+ A dictionary where keys are bundle names and values are either
+ StatefulTractogram objects or dictionaries with keys "sl"
+ and "idx".
+ sidecar_info : dict, optional
+ Sidecar information to optionally attach.
+ """
+
if sidecar_info is None:
sidecar_info = {}
+ self.sidecar_info = sidecar_info
self.bundle_names = []
sls = []
idxs = {}
@@ -43,7 +59,6 @@ def __init__(self, bundles, sidecar_info=None):
else:
self.this_tracking_idxs = None
- self.sidecar_info = sidecar_info
self.sidecar_info["bundle_ids"] = {}
dps = np.zeros(len(sls))
for ii, bundle_name in enumerate(self.bundle_names):
@@ -59,6 +74,15 @@ def __init__(self, bundles, sidecar_info=None):
)
self.sidecar_info["tracking_idx"] = self.this_tracking_idxs
+ def get_lengths(self):
+ return self.sft._tractogram._streamlines._lengths
+
+ def to_rasmm(self):
+ self.sft.to_space(Space.RASMM)
+
+ def get_bundle_idxs(self, b_name):
+ return self.bundle_idxs[b_name]
+
def get_bundle(self, b_name):
return self.sft[self.bundle_idxs[b_name]]
@@ -68,8 +92,6 @@ def get_bundle_param_info(self, b_name):
@classmethod
def fromfile(cls, trk_or_trx_file, reference="same", sidecar_file=None):
if sidecar_file is None:
- # assume json sidecar has the same name as trk_file,
- # but with json suffix
sidecar_file = f"{drop_extension(trk_or_trx_file)}.json"
if not op.exists(sidecar_file):
raise ValueError(
@@ -80,20 +102,11 @@ def fromfile(cls, trk_or_trx_file, reference="same", sidecar_file=None):
)
sidecar_info = read_json(sidecar_file)
if trk_or_trx_file.endswith(".trx"):
- trx = load_trx(trk_or_trx_file, reference)
- trx.streamlines._data = trx.streamlines._data.astype(np.float32)
- sft = trx.to_sft()
- if reference == "same":
- reference = sft
- bundles = {}
- for bundle in trx.groups:
- idx = trx.groups[bundle]
- bundles[bundle] = StatefulTractogram(
- sft.streamlines[idx], reference, Space.RASMM
- )
+ bundles = load_trx(trk_or_trx_file, reference)
+ bundles.streamlines._data = bundles.streamlines._data.astype(np.float32)
+ return SegmentedTRX(bundles, sidecar_info)
else:
sft = load_tractogram(trk_or_trx_file, reference, to_space=Space.RASMM)
-
if reference == "same":
reference = sft
bundles = {}
@@ -105,8 +118,53 @@ def fromfile(cls, trk_or_trx_file, reference="same", sidecar_file=None):
)
else:
bundles["whole_brain"] = sft
+ return SegmentedSFT(bundles, sidecar_info)
+
- return cls(bundles, sidecar_info)
+class SegmentedTRX(SegmentedSFT):
+ def __init__(self, trx, sidecar_info=None):
+ """
+ Initialize a SegmentedTRX object.
+
+ Parameters
+ ----------
+ trx : TrxFile
+ The TRX file to interact with.
+ sidecar_info : dict, optional
+ Sidecar information to optionally attach.
+ """
+ if sidecar_info is None:
+ sidecar_info = {}
+ self.sidecar_info = sidecar_info
+
+ # Loading from TRX, we do not calculate additional information,
+ # To avoid loading unnecessary streamlines into memory
+ self.bundle_names = list(trx.groups.keys())
+ self.sft = trx
+
+ # Mimic SFT attributes for compatibility
+ affine = np.array(self.sft.header["VOXEL_TO_RASMM"], dtype=np.float32)
+ dimensions = np.array(self.sft.header["DIMENSIONS"], dtype=np.uint16)
+ vox_sizes = np.array(voxel_sizes(affine), dtype=np.float32)
+ vox_order = "".join(aff2axcodes(affine))
+ space_attributes = (affine, dimensions, vox_sizes, vox_order)
+ self.sft.space_attributes = space_attributes
+ self.sft.space = Space.RASMM
+ self.sft.origin = Origin.NIFTI
+ self.sft.dtype_dict = self.sft.get_dtype_dict()
+
+ def get_lengths(self):
+ return self.sft.streamlines._lengths
+
+ def to_rasmm(self):
+ pass # always in RASMM
+
+ def get_bundle_idxs(self, b_name):
+ return self.sft.groups[b_name]
+
+ def get_bundle(self, b_name):
+ idx = self.sft.groups[b_name]
+ return StatefulTractogram(self.sft.streamlines[idx], self.sft, Space.RASMM)
def split_streamline(streamlines, sl_to_split, split_idx):
diff --git a/AFQ/utils/tests/test_streamlines.py b/AFQ/utils/tests/test_streamlines.py
index 8931670fc..5bf350a6d 100644
--- a/AFQ/utils/tests/test_streamlines.py
+++ b/AFQ/utils/tests/test_streamlines.py
@@ -2,47 +2,158 @@
import nibabel as nib
import numpy as np
import numpy.testing as npt
-from dipy.io.stateful_tractogram import Space, StatefulTractogram
+from dipy.io.stateful_tractogram import Origin, Space, StatefulTractogram
+from trx.trx_file_memmap import TrxFile
from AFQ.utils import streamlines as aus
+affine = np.array(
+ [
+ [2.0, 0.0, 0.0, -80.0],
+ [0.0, 2.0, 0.0, -120.0],
+ [0.0, 0.0, 2.0, -60.0],
+ [0.0, 0.0, 0.0, 1.0],
+ ]
+)
-def test_SegmentedSFT():
- affine = np.array(
+img = nib.Nifti1Image(np.ones((10, 10, 10, 30)), affine)
+
+
+bundles = {
+ "ba": StatefulTractogram(
[
- [2.0, 0.0, 0.0, -80.0],
- [0.0, 2.0, 0.0, -120.0],
- [0.0, 0.0, 2.0, -60.0],
- [0.0, 0.0, 0.0, 1.0],
- ]
- )
- img = nib.Nifti1Image(np.ones((10, 10, 10, 30)), affine)
-
- bundles = {
- "ba": StatefulTractogram(
- [
- np.array([[0, 0, 0], [0, 0, 0.5], [0, 0, 1], [0, 0, 1.5]]),
- np.array([[0, 0, 0], [0, 0.5, 0.5], [0, 1, 1]]),
- ],
- img,
- Space.VOX,
- ),
- "bb": StatefulTractogram(
- [
- np.array([[0, 0, 0], [0, 0, 0.5], [0, 0, 2], [0, 0, 2.5]]),
- np.array([[0, 0, 0], [0, 0.5, 0.5], [0, 2, 2]]),
- ],
- img,
- Space.VOX,
- ),
- }
-
- seg_sft = aus.SegmentedSFT(bundles)
- for k1 in bundles.keys():
- for sl1, sl2 in zip(
- bundles[k1].streamlines, seg_sft.get_bundle(k1).streamlines
- ):
- npt.assert_equal(sl1, sl2)
+ np.array([[0, 0, 0], [0, 0, 0.5], [0, 0, 1], [0, 0, 1.5]]),
+ np.array([[0, 0, 0], [0, 0.5, 0.5], [0, 1, 1]]),
+ ],
+ img,
+ Space.VOX,
+ ),
+ "bb": StatefulTractogram(
+ [
+ np.array([[0, 0, 0], [0, 0, 0.5], [0, 0, 2], [0, 0, 2.5]]),
+ np.array([[0, 0, 0], [0, 0.5, 0.5], [0, 2, 2]]),
+ ],
+ img,
+ Space.VOX,
+ ),
+}
+
+
+class TestSegmentedSFT:
+ def setup_method(self):
+ self.img = img
+ self.bundles = bundles
+ self.seg = aus.SegmentedSFT(self.bundles)
+
+ def test_bundle_names(self):
+ assert set(self.seg.bundle_names) == {"ba", "bb"}
+
+ def test_get_bundle_roundtrip(self):
+ for k in self.bundles:
+ for sl_orig, sl_got in zip(
+ self.bundles[k].streamlines,
+ self.seg.get_bundle(k).streamlines,
+ ):
+ npt.assert_array_equal(sl_orig, sl_got)
+
+ def test_get_bundle_idxs_contiguous(self):
+ idxs_a = self.seg.get_bundle_idxs("ba")
+ idxs_b = self.seg.get_bundle_idxs("bb")
+ assert len(idxs_a) == 2
+ assert len(idxs_b) == 2
+ assert set(idxs_a).isdisjoint(set(idxs_b))
+
+ def test_sidecar_bundle_ids_populated(self):
+ ids = self.seg.sidecar_info["bundle_ids"]
+ assert set(ids.keys()) == {"ba", "bb"}
+
+ def test_bundle_dps_match_sidecar(self):
+ ids = self.seg.sidecar_info["bundle_ids"]
+ dps = self.seg.sft.data_per_streamline["bundle"]
+ for b_name, b_id in ids.items():
+ for idx in self.seg.get_bundle_idxs(b_name):
+ assert dps[idx] == b_id
+
+ def test_get_lengths_returns_array(self):
+ lengths = self.seg.get_lengths()
+ assert len(lengths) == 4 # 2 bundles × 2 streamlines each
+
+ def test_to_rasmm_does_not_raise(self):
+ self.seg.to_rasmm()
+ assert self.seg.sft.space == Space.RASMM
+
+ def test_get_bundle_param_info_missing(self):
+ assert self.seg.get_bundle_param_info("ba") == {}
+
+ def test_get_bundle_param_info_present(self):
+ seg = aus.SegmentedSFT(
+ self.bundles,
+ sidecar_info={"Bundle Parameters": {"ba": {"min_len": 10}}},
+ )
+ assert seg.get_bundle_param_info("ba") == {"min_len": 10}
+ assert seg.get_bundle_param_info("bb") == {}
+
+ def test_default_sidecar_is_empty_dict(self):
+ seg = aus.SegmentedSFT(self.bundles)
+ assert "bundle_ids" in seg.sidecar_info
+
+ def test_sidecar_not_shared_between_instances(self):
+ seg2 = aus.SegmentedSFT(self.bundles)
+ assert self.seg.sidecar_info is not seg2.sidecar_info
+
+ def test_dict_bundles_with_tracking_idx(self):
+ bundles_with_idx = {
+ "ba": {"sl": self.bundles["ba"], "idx": np.array([0, 1])},
+ "bb": {"sl": self.bundles["bb"], "idx": np.array([2, 3])},
+ }
+ seg = aus.SegmentedSFT(bundles_with_idx)
+ assert "tracking_idx" in seg.sidecar_info
+ assert set(seg.sidecar_info["tracking_idx"].keys()) == {"ba", "bb"}
+ assert isinstance(seg.sidecar_info["tracking_idx"]["ba"], list)
+
+ def test_single_bundle_no_tracking_idx(self):
+ single = {"ba": self.bundles["ba"]}
+ seg = aus.SegmentedSFT(single)
+ assert seg.this_tracking_idxs is None
+ assert "tracking_idx" not in seg.sidecar_info
+
+
+class TestSegmentedTRX:
+ def setup_method(self):
+ seg_sft = aus.SegmentedSFT(bundles)
+ self.trx = TrxFile.from_sft(seg_sft.sft)
+ self.trx.groups = seg_sft.bundle_idxs
+ self.seg = aus.SegmentedTRX(self.trx)
+
+ def test_bundle_names(self):
+ assert set(self.seg.bundle_names) == {"ba", "bb"}
+
+ def test_sft_is_trx(self):
+ assert self.seg.sft is self.trx
+
+ def test_space_attributes_set(self):
+ assert hasattr(self.seg.sft, "space_attributes")
+ assert self.seg.sft.space == Space.RASMM
+ assert self.seg.sft.origin == Origin.NIFTI
+
+ def test_get_lengths(self):
+ lengths = self.seg.get_lengths()
+ assert len(lengths) == 4
+
+ def test_to_rasmm_is_noop(self):
+ self.seg.to_rasmm()
+ assert self.seg.sft.space == Space.RASMM
+
+ def test_get_bundle_idxs(self):
+ idxs = self.seg.get_bundle_idxs("ba")
+ npt.assert_array_equal(idxs, self.trx.groups["ba"])
+
+ def test_get_bundle_returns_sft(self):
+ result = self.seg.get_bundle("ba")
+ assert isinstance(result, StatefulTractogram)
+
+ def test_get_bundle_param_info_missing(self):
+ assert self.seg.get_bundle_param_info("ba") == {}
def test_split_streamline():
diff --git a/AFQ/viz/fury_backend.py b/AFQ/viz/fury_backend.py
index 4e73f22e3..47970c84d 100644
--- a/AFQ/viz/fury_backend.py
+++ b/AFQ/viz/fury_backend.py
@@ -5,7 +5,7 @@
from dipy.tracking.streamline import set_number_of_points
import AFQ.viz.utils as vut
-from AFQ._fixes import make_gif
+from AFQ._fixes import make_mp4
try:
from fury import actor, window
@@ -46,6 +46,7 @@ def visualize_bundles(
bundle=None,
colors=None,
color_by_direction=False,
+ n_sls_viz=65536,
opacity=1.0,
line_width=2.0,
flip_axes=None,
@@ -86,6 +87,11 @@ def visualize_bundles(
color_by_direction : bool
Whether to color by direction instead of by bundle. Default: False
+ n_sls_viz : int
+ Maximum number of streamlines to visualize. If there are more than
+ this number of streamlines, a random subset of streamlines will be
+ visualized. Default: 65536
+
opacity : float
Float between 0 and 1 defining the opacity of the bundle.
Default: 1.0
@@ -119,7 +125,12 @@ def visualize_bundles(
figure.background = (background[0], background[1], background[2])
for sls, color, name, dimensions in vut.tract_generator(
- seg_sft, bundle, colors, n_points, img
+ seg_sft,
+ bundle,
+ colors,
+ n_points,
+ img,
+ n_sls_viz=n_sls_viz,
):
sls = list(sls)
if name == "all_bundles":
@@ -150,15 +161,15 @@ def scene_rotate_forward(show_m, scene):
show_m.window.draw()
-def create_gif(
+def create_mp4(
figure,
file_name,
- n_frames=36,
- az_ang=-10,
+ fps=30,
+ az_ang=-0.5,
size=(600, 600),
):
"""
- Convert a Fury Scene object into a gif
+ Convert a Fury Scene object into a mp4
Make a video from a Fury Show Manager.
@@ -170,17 +181,17 @@ def create_gif(
file_name : str
The name of the output file.
- n_frames : int
- The number of frames to render.
- Default: 36
+ fps : int
+ The frames per second for the output video.
+ Default: 30
az_ang : float
The angle to rotate the camera around the
z-axis for each frame, in degrees.
- Default: -10
+ Default: -0.5
size : tuple
- The size of the output gif, in pixels.
+ The size of the output mp4, in pixels.
Default: (600, 600)
"""
show_m = window.ShowManager(
@@ -189,7 +200,7 @@ def create_gif(
size=size,
)
scene_rotate_forward(show_m, figure)
- make_gif(show_m, file_name, n_frames=n_frames, az_ang=az_ang)
+ make_mp4(show_m, file_name, fps=fps, az_ang=az_ang)
def visualize_roi(
diff --git a/AFQ/viz/plotly_backend.py b/AFQ/viz/plotly_backend.py
index 8b2f8745a..11cab62b3 100644
--- a/AFQ/viz/plotly_backend.py
+++ b/AFQ/viz/plotly_backend.py
@@ -1,10 +1,13 @@
import enum
import logging
+import os.path as op
import tempfile
+import imageio
import numpy as np
import pandas as pd
from dipy.tracking.streamline import set_number_of_points
+from PIL import Image
import AFQ.viz.utils as vut
@@ -475,43 +478,64 @@ def visualize_bundles(
return _inline_interact(figure, interact, inline)
-def create_gif(figure, file_name, n_frames=30, zoom=2.5, z_offset=0.5, size=(600, 600)):
+def create_mp4(
+ figure, file_name, n_frames=720, fps=30, zoom=2.5, z_offset=0.5, size=(600, 600)
+):
"""
- Convert a Plotly Figure object into a gif
+ Convert a Plotly Figure object into a mp4
Parameters
----------
figure: Plotly Figure object
- Figure to be converted to a gif
+ Figure to be converted to a mp4
file_name: str
- File to save gif to.
+ File to save mp4 to.
- n_frames: int, optional
- Number of frames in gif.
- Will be evenly distributed throughout the rotation.
- Default: 60
+ fps: int, optional
+ Frames per second for the output video.
+ Default: 30
zoom: float, optional
How much to magnify the figure in the fig.
Default: 2.5
size: tuple, optional
- Size of the gif.
+ Size of the mp4.
Default: (600, 600)
"""
- tdir = tempfile.gettempdir()
+ width, height = size
+ if width % 2 != 0:
+ width -= 1
+ if height % 2 != 0:
+ height -= 1
- for i in range(n_frames):
- theta = (i * 6.28) / n_frames
- camera = dict(
- eye=dict(x=np.cos(theta) * zoom, y=np.sin(theta) * zoom, z=z_offset)
- )
- figure.update_layout(scene_camera=camera)
- figure.write_image(tdir + f"/tgif{i}.png")
- scope._shutdown_kaleido() # temporary fix for memory leak
+ figure.update_layout(width=width, height=height)
+
+ with tempfile.TemporaryDirectory() as tdir:
+ frame_paths = []
+
+ for i in range(n_frames):
+ theta = (i * 2 * np.pi) / n_frames
+
+ camera = dict(
+ eye=dict(x=np.cos(theta) * zoom, y=np.sin(theta) * zoom, z=z_offset)
+ )
+ figure.update_layout(scene_camera=camera)
+
+ frame_path = op.join(tdir, f"tframe{i}.png")
+ figure.write_image(frame_path)
+ frame_paths.append(frame_path)
+
+ pio.kaleido.scope._shutdown_kaleido()
- vut.gif_from_pngs(tdir, file_name, n_frames, png_fname="tgif", add_zeros=False)
+ with imageio.get_writer(
+ file_name, fps=fps, codec="libx264", quality=8
+ ) as writer:
+ for path in frame_paths:
+ img = Image.open(path).convert("RGB")
+ frame_arr = np.array(img)
+ writer.append_data(frame_arr)
def _draw_roi(figure, roi, name, color, opacity, dimensions, flip_axes):
diff --git a/AFQ/viz/utils.py b/AFQ/viz/utils.py
index 24e5cc635..04f6dbe14 100644
--- a/AFQ/viz/utils.py
+++ b/AFQ/viz/utils.py
@@ -1,10 +1,8 @@
import colorsys
import logging
-import os.path as op
from collections import OrderedDict
import dipy.tracking.streamlinespeed as dps
-import imageio as io
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import nibabel as nib
@@ -125,10 +123,10 @@ def get_distinct_shades(base_rgb, n_steps, hue_shift):
"Right Posterior Arcuate": (0.5, 0.95, 0.7),
"Left Vertical Occipital": vof_l_base,
"Right Vertical Occipital": vof_r_base,
- "Left V1V3": vof_l_shades[0],
+ "Left Early Visual": vof_l_shades[0],
"Left Posterior Vertical Occipital": vof_l_shades[1],
"Left Anterior Vertical Occipital": vof_l_shades[2],
- "Right V1V3": vof_r_shades[0],
+ "Right Early Visual": vof_r_shades[0],
"Right Posterior Vertical Occipital": vof_r_shades[1],
"Right Anterior Vertical Occipital": vof_r_shades[2],
"median": tableau_20[6],
@@ -265,6 +263,7 @@ def add_img(
x_coord,
y_coord,
reduct_count=1,
+ trim_buffer=2,
subplot_label_pos=(0.1, 0.1),
legend=None,
legend_kwargs=None,
@@ -285,6 +284,9 @@ def add_img(
reduct_count : int
number of times to trim whitespace around image
Default: 1
+ trim_buffer : int
+ number of pixels to add back around image after trimming
+ Default: 2
subplot_label_pos : tuple of floats
position of subplot label
Default: (0.1, 0.1)
@@ -308,7 +310,7 @@ def add_img(
ax = self.fig.add_subplot(self.grid[y_coord, x_coord])
im1 = Image.open(fname)
for _ in range(reduct_count):
- im1 = trim(im1)
+ im1 = trim(im1, buffer=trim_buffer)
if legend is not None:
patches = []
for value, color in legend.items():
@@ -372,7 +374,7 @@ def format_and_save_figure(self, fname, trim_final=True, tight_final=True):
self.fig.savefig(fname, dpi=300)
if trim_final:
im1 = Image.open(fname)
- im1 = trim(im1)
+ im1 = trim(im1, buffer=0)
im1.save(fname, dpi=(300, 300))
@@ -537,11 +539,11 @@ def tract_generator(
if colors is None:
colors = gen_color_dict(seg_sft.bundle_names)
- seg_sft.sft.to_rasmm()
- streamlines = seg_sft.sft.streamlines
+ seg_sft.to_rasmm()
viz_logger.info("Generating colorful lines from tractography...")
if len(seg_sft.bundle_names) == 1 and seg_sft.bundle_names[0] == "whole_brain":
+ streamlines = seg_sft.sft.streamlines
if isinstance(colors, dict):
colors = list(colors.values())
# There are no bundles in here:
@@ -556,8 +558,9 @@ def tract_generator(
else:
if bundle is None:
# No selection: visualize all of them:
+ streamlines = seg_sft.sft.streamlines
for bundle_name in sorted(seg_sft.bundle_names):
- idx = seg_sft.bundle_idxs[bundle_name]
+ idx = seg_sft.get_bundle_idxs(bundle_name)
if len(idx) == 0:
continue
n_sl_viz = (len(idx) * n_sls_viz) // len(streamlines)
@@ -588,53 +591,29 @@ def tract_generator(
yield sls, color, bundle, dim
-def bbox(img):
+def bbox(img, buffer=0):
img = np.sum(img, axis=-1)
rows = np.any(img, axis=1)
cols = np.any(img, axis=0)
rmin, rmax = np.where(rows)[0][[0, -1]]
cmin, cmax = np.where(cols)[0][[0, -1]]
- return cmin, rmin, cmax, rmax
+ return (
+ max(cmin - buffer, 0),
+ max(rmin - buffer, 0),
+ min(cmax + buffer, img.shape[1]),
+ min(rmax + buffer, img.shape[0]),
+ )
-def trim(im):
+def trim(im, buffer=0):
bg = Image.new(im.mode, im.size, im.getpixel((0, 0)))
diff = ImageChops.difference(im, bg)
- this_bbox = bbox(diff)
+ this_bbox = bbox(diff, buffer=buffer)
if this_bbox:
return im.crop(this_bbox)
-def gif_from_pngs(tdir, gif_fname, n_frames, png_fname="tgif", add_zeros=False):
- """
- Helper function
- Stitches together gif from screenshots
- """
- if add_zeros:
- fname_suffix10 = "00000"
- fname_suffix100 = "0000"
- fname_suffix1000 = "000"
- else:
- fname_suffix10 = ""
- fname_suffix100 = ""
- fname_suffix1000 = ""
- angles = []
- n_frame_copies = 60 // n_frames
- for i in range(n_frames):
- if i < 10:
- angle_fname = f"{png_fname}{fname_suffix10}{i}.png"
- elif i < 100:
- angle_fname = f"{png_fname}{fname_suffix100}{i}.png"
- else:
- angle_fname = f"{png_fname}{fname_suffix1000}{i}.png"
- frame = io.imread(op.join(tdir, angle_fname))
- for _j in range(n_frame_copies):
- angles.append(frame)
-
- io.mimsave(gif_fname, angles)
-
-
def prepare_roi(roi, resample_to=None):
"""
Load the ROI
@@ -716,7 +695,7 @@ def __init__(self, backend="fury"):
self.visualize_bundles = AFQ.viz.fury_backend.visualize_bundles
self.visualize_roi = AFQ.viz.fury_backend.visualize_roi
self.visualize_volume = AFQ.viz.fury_backend.visualize_volume
- self.create_gif = AFQ.viz.fury_backend.create_gif
+ self.create_mp4 = AFQ.viz.fury_backend.create_mp4
elif "plotly" in backend:
try:
import AFQ.viz.plotly_backend
@@ -725,7 +704,7 @@ def __init__(self, backend="fury"):
self.visualize_bundles = AFQ.viz.plotly_backend.visualize_bundles
self.visualize_roi = AFQ.viz.plotly_backend.visualize_roi
self.visualize_volume = AFQ.viz.plotly_backend.visualize_volume
- self.create_gif = AFQ.viz.plotly_backend.create_gif
+ self.create_mp4 = AFQ.viz.plotly_backend.create_mp4
self.single_bundle_viz = AFQ.viz.plotly_backend.single_bundle_viz
else:
raise TypeError(
diff --git a/docs/Makefile b/docs/Makefile
index f12775089..adda92a3c 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -26,9 +26,11 @@ upload: html
# find artifacts from sphinx-gallery in examples folder
ARTIFACTS := $(strip \
- $(wildcard ../examples/*.gz) $(wildcard ../examples/**/*.gz) \
- $(wildcard ../examples/*.trk) $(wildcard ../examples/**/*.trk) \
- $(wildcard ../examples/*.npy) $(wildcard ../examples/**/*.npy) \
+ $(wildcard source/examples/*/*.gz) $(wildcard source/examples/**/*.gz) \
+ $(wildcard source/examples/*/*.trk) $(wildcard source/examples/**/*.trk) \
+ $(wildcard source/examples/*/*.npy) $(wildcard source/examples/**/*.npy) \
+ $(wildcard source/examples/*/*.png) $(wildcard source/examples/**/*.png) \
+ $(wildcard source/examples/*/*.mp4) $(wildcard source/examples/**/*.mp4) \
)
# leaves only the files that were in the distribution. deletes files created
@@ -47,23 +49,3 @@ realclean: distclean
rm -rf $(HOME)/.dipy/
rm -rf $(HOME)/AFQ_data/
-
-html-noplot:
- PYDEVD_DISABLE_FILE_VALIDATION=1 $(SPHINXBUILD) \
- -D plot_gallery=0 \
- -D exclude_patterns=howto/howto_examples/*.ipynb,tutorials/tutorial_examples/*.ipynb \
- -b html $(ALLSPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-html-noplot-clean:
- @echo Removing files created by sphinx-build
- rm -rf $(BUILDDIR)
- rm -f $(SOURCEDIR)/reference/config.rst
- $(if $(ARTIFACTS),rm -f $(ARTIFACTS))
- PYDEVD_DISABLE_FILE_VALIDATION=1 $(SPHINXBUILD) \
- -D plot_gallery=0 \
- -D exclude_patterns=howto/howto_examples/*.ipynb,tutorials/tutorial_examples/*.ipynb \
- -b html $(ALLSPHINXOPTS) $(SOURCEDIR) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
diff --git a/docs/source/_progressbars.py b/docs/source/_progressbars.py
deleted file mode 100644
index 5ad5786ff..000000000
--- a/docs/source/_progressbars.py
+++ /dev/null
@@ -1,33 +0,0 @@
-def _no_tqdm(iterable, *args, **kwargs):
- """
- replacement for tqdm that just passes back the iterable to silence `tqdm`
- """
- return iterable
-
-
-def _no_progressbar(progress, total_length):
- """
- no-op calls to update the `fetcher` progress bar
- """
- return
-
-
-def reset_progressbars(gallery_conf, fname):
- """
- monkey patch to disable various progress bar output for examples. the
- progress bar updates pollutes sphinx gallery output. using this monkey
- patch from the spinx-build will leave the progress bars in place for other
- uses.
- """
-
- # disable tqdm
- import AFQ._fixes as fixes
- import AFQ.viz.utils as utils
-
- fixes.tqdm = _no_tqdm
- utils.tqdm = _no_tqdm
-
- # disable update_progressbar
- from dipy.data import fetcher
-
- fetcher.update_progressbar = _no_progressbar
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 3229c7ca1..8c8da81f1 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -12,9 +12,6 @@
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-from plotly.io._sg_scraper import plotly_sg_scraper
-from AFQ.utils.docs import PNGScraper, GIFScraper
-
import sys
import os
import AFQ
@@ -53,7 +50,7 @@
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
- 'sphinx_gallery.gen_gallery',
+ 'myst_nb',
'sphinx_design',
'sphinx.ext.autosummary',
'sphinxcontrib.bibtex',
@@ -62,7 +59,6 @@
'updatedocs',
'kwargsdocs',
'methodsdocs',
- 'myst_nb',
]
bibtex_bibfiles = ['references.bib']
@@ -75,8 +71,11 @@
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = {
+ '.rst': 'restructuredtext',
+ '.md': 'myst-nb',
+ '.ipynb': 'myst-nb',
+}
# The master toctree document.
master_doc = 'index'
@@ -222,28 +221,20 @@
'dipy': ('https://docs.dipy.org/stable/', None)
}
-image_scrapers = ('matplotlib', plotly_sg_scraper, PNGScraper(), GIFScraper())
-
-from _progressbars import reset_progressbars # noqa
-
-sphinx_gallery_thumbnail_path = '../source/_static/logo.png'
-
-from sphinx_gallery.sorting import FileNameSortKey
-
-sphinx_gallery_conf = {
+# Myst settings
+nb_execution_mode = "force"
+nb_execution_timeout = 7200
+myst_enable_extensions = [
+ "colon_fence",
+ "deflist",
+ "dollarmath",
+ "amsmath",
+]
+nb_execution_raise_on_error = True
+nb_scroll_outputs = True
- # path to your examples scripts
- 'examples_dirs': ['../../examples/howto_examples',
- '../../examples/tutorial_examples'],
- # path where to save gallery generated examples
- 'gallery_dirs': ['howto/howto_examples', 'tutorials/tutorial_examples'],
- 'image_scrapers': image_scrapers,
- 'reset_modules': (reset_progressbars),
- 'filename_pattern': r'/plot_(?!.*(003_rerun|006_bids_layout)).*\.py$',
- 'show_memory': True,
- 'abort_on_example_error': True,
- 'within_subsection_order': FileNameSortKey,
-}
+# example of how to exclude notebooks when testing
+# nb_execution_excludepatterns = ["plot_00*"]
# Auto API
autoapi_type = 'python'
diff --git a/docs/source/examples/howto_examples/acoustic_radiations.md b/docs/source/examples/howto_examples/acoustic_radiations.md
new file mode 100644
index 000000000..16599a03b
--- /dev/null
+++ b/docs/source/examples/howto_examples/acoustic_radiations.md
@@ -0,0 +1,133 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to add new bundles into pyAFQ (Acoustic Radiations Example)
+
+pyAFQ is designed to be customizable and extensible. This example shows how you
+can customize it to define a new bundle based on a definition of waypoint and
+endpoint ROIs of your design. In this case, we add the acoustic radiations.
+
+We start by importing some of the components that we need for this example and
+fixing the random seed for reproducibility
+
+```{code-cell} ipython3
+import os.path as op
+import plotly
+import numpy as np
+
+from AFQ.api.group import GroupAFQ
+import AFQ.api.bundle_dict as abd
+import AFQ.data.fetch as afd
+from AFQ.definitions.image import ImageFile, RoiImage
+np.random.seed(1234)
+```
+
+## Get dMRI data
+
+We will analyze one subject from the Healthy Brain Network Processed Open
+Diffusion Derivatives dataset (HBN-POD2) [^1], [^2]. We'll use a fetcher to
+get preprocessed dMRI data for one of the >2,000 subjects in that study. The
+data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
+folder:
+
+```{code-cell} ipython3
+study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
+```
+
+## Define custom `BundleDict` object
+The `BundleDict` object holds information about "include" and "exclude" ROIs,
+as well as endpoint ROIS, and whether the bundle crosses the midline. In this
+case, the ROIs are all defined in the MNI template space that is used as the
+default template space in pyAFQ, but, in principle, other template spaces
+could be used.
+
+The ROIs for the case can be downloaded using a custom fetcher which saves
+the ROIs to a folder and creates a dictionary of paths to the ROIs:
+
+```{code-cell} ipython3
+ar_rois = afd.read_ar_templates()
+
+bundles = abd.BundleDict({
+ "Left Acoustic Radiation": {
+ "start": ar_rois["AAL_Thal_L"],
+ "end": ar_rois["AAL_TempSup_L"],
+ "cross_midline": False,
+ },
+ "Right Acoustic Radiation": {
+ "start": ar_rois["AAL_Thal_R"],
+ "end": ar_rois["AAL_TempSup_R"],
+ "cross_midline": False
+ }
+})
+```
+
+## Define GroupAFQ object
+
+HBN POD2 have been processed with qsiprep [^3]. This means that a brain mask
+has already been computer for them. As you can see in other examples, these
+data also have a mapping calculated for them, which can also be incorporated
+into processing. However, in this case, we will let pyAFQ calculate its own
+SyN-based mapping so that the `combine_bundle` method can be used below to
+create a montage visualization.
+
+For tractography, we use CSD-based probabilistic tractography seeding
+extensively (`n_seeds=4` means 81 seeds per voxel!), but only within the ROIs
+and not throughout the white matter. This is controlled by passing
+`"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
+are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
+initiates the pipeline.
+
+```{code-cell} ipython3
+my_afq = GroupAFQ(
+ bids_path=study_dir,
+ dwi_preproc_pipeline="qsiprep",
+ participant_labels=["NDARAA948VFH"],
+ output_dir=op.join(study_dir, "derivatives", "afq_ar"),
+ tracking_params={"n_seeds": 4,
+ "random_seeds": False,
+ "directions": "prob",
+ "odf_model": "CSD",
+ "seed_mask": RoiImage(use_endpoints=True)},
+ bundle_info=bundles)
+
+my_afq.export_all()
+```
+
+## Interactive bundle visualization
+Another way to examine the outputs is to export the individual bundle
+figures, which show the streamlines, as well as the ROIs used to define the
+bundle. This is an html file, which contains an interactive figure that can
+be navigated, zoomed, rotated, etc.
+
+```{code-cell} ipython3
+bundle_html = my_afq.export("indiv_bundles_figures")
+plotly.io.show(bundle_html["NDARAA948VFH"]["Left Acoustic Radiation"])
+```
+
+## 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.
diff --git a/examples/howto_examples/add_custom_bundle.py b/docs/source/examples/howto_examples/add_custom_bundle.md
similarity index 50%
rename from examples/howto_examples/add_custom_bundle.py
rename to docs/source/examples/howto_examples/add_custom_bundle.md
index f8ac73f38..f5777ab4a 100644
--- a/examples/howto_examples/add_custom_bundle.py
+++ b/docs/source/examples/howto_examples/add_custom_bundle.md
@@ -1,19 +1,33 @@
-"""
-=====================================================
-How to add new bundles into pyAFQ (SLF 1/2/3 Example)
-=====================================================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to add new bundles into pyAFQ (SLF 1/2/3 Example)
pyAFQ is designed to be customizable and extensible. This example shows how you
can customize it to define a new bundle based on a definition of waypoint and
endpoint ROIs of your design.
In this case, we add sub-bundles of the superior longitudinal fasciculus,
-based on work by Sagi et al [1]_.
+based on work by Sagi et al [^1].
We start by importing some of the components that we need for this example and
fixing the random seed for reproducibility
-"""
+```{code-cell} ipython3
import os.path as op
import numpy as np
@@ -24,18 +38,18 @@
import wget
import os
np.random.seed(1234)
+```
+## Get dMRI data
-#############################################################################
-# Get dMRI data
-# ---------------
-# We will analyze eight subject from the Healthy Brain Network Processed Open
-# Diffusion Derivatives dataset (HBN-POD2) [2]_, [3]_. We'll use a fetcher to
-# get preprocessed dMRI data for eight of the >2,000 subjects in that study. The
-# data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
-# folder. These 12 subjects have very high quality data.
-# The fether returns this directory as study_dir:
+We will analyze eight subject from the Healthy Brain Network Processed Open
+Diffusion Derivatives dataset (HBN-POD2) [^2], [^3]. We'll use a fetcher to
+get preprocessed dMRI data for eight of the >2,000 subjects in that study. The
+data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
+folder. These 12 subjects have very high quality data.
+The fether returns this directory as study_dir:
+```{code-cell} ipython3
_, study_dir = afd.fetch_hbn_preproc([
'NDARKP893TWU',
'NDAREP505XAD',
@@ -50,17 +64,17 @@
'NDARJG687YYX',
'NDARJA157YB3',
])
+```
-#############################################################################
-# Get ROIs and save to disk
-# --------------------------------
-# The goal of this tutorial is to demonstrate how to segment new pathways based
-# on ROIs that are saved to disk. In principle, ROIs can be a) files created by
-# the user and saved to the local disk, b) files stored somewhere on the internet
-# (as is the case here) or c) Files that are accessed with a fetcher. In this
-# example we download these files from the MATLAB AFQ website, but this code could
-# be commented out and paths could be used to local ROIs on disk
+## Get ROIs and save to disk
+The goal of this tutorial is to demonstrate how to segment new pathways based
+on ROIs that are saved to disk. In principle, ROIs can be a) files created by
+the user and saved to the local disk, b) files stored somewhere on the internet
+(as is the case here) or c) Files that are accessed with a fetcher. In this
+example we download these files from the MATLAB AFQ website, but this code could
+be commented out and paths could be used to local ROIs on disk
+```{code-cell} ipython3
roi_urls = ['https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/MFgL.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/MFgR.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/PaL.nii.gz',
@@ -87,19 +101,18 @@
for roi_url in roi_urls:
wget.download(roi_url, template_dir)
-
-
-#############################################################################
-# Define custom `BundleDict` object
-# ---------------------------------
-# A `BundleDict` is a custom object that holds information about "include" and
-# "exclude" ROIs, as well as endpoint ROIs, and whether the bundle crosses the
-# midline. In this case, the ROIs are all defined in the MNI template space that
-# is used as the default template space in pyAFQ, but, in principle, other
-# template spaces could be used. In this example, we provide paths to the ROIs
-# to populate the `BundleDict`, but we could also provide already-loaded nifti
-# objects, as demonstrated in other examples.
-
+```
+
+## Define custom `BundleDict` object
+A `BundleDict` is a custom object that holds information about "include" and
+"exclude" ROIs, as well as endpoint ROIs, and whether the bundle crosses the
+midline. In this case, the ROIs are all defined in the MNI template space that
+is used as the default template space in pyAFQ, but, in principle, other
+template spaces could be used. In this example, we provide paths to the ROIs
+to populate the `BundleDict`, but we could also provide already-loaded nifti
+objects, as demonstrated in other examples.
+
+```{code-cell} ipython3
bundles = abd.BundleDict({
"L_SLF1": {
"include": [
@@ -144,29 +157,33 @@
"distance_threshold": 2}
}
})
+```
+
+Custom bundle definitions such as the SLF or OR, and the standard BundleDict
+can be combined through addition. To get both the SLF and the standard
+bundles, we would execute the following code:
+
+```python
+bundles = bundles + abd.default_bd()
+```
+
+In this case, we will skip this and generate just the SLF.
+
++++
-#############################################################################
-# Custom bundle definitions such as the SLF or OR, and the standard BundleDict
-# can be combined through addition. To get both the SLF and the standard
-# bundles, we would execute the following code::
-#
-# bundles = bundles + abd.default_bd()
-#
-# In this case, we will skip this and generate just the SLF.
-
-#############################################################################
-# Define GroupAFQ object
-# ----------------------
-# HBN POD2 have been processed with qsiprep [4]_. This means that a brain mask
-# has already been computed for them.
-#
-# For tractography, we use CSD-based probabilistic tractography,
-# seeding 200,000 seeds but only within the ROIs
-# and not throughout the white matter. This is controlled by passing
-# `"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
-# are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
-# initiates the pipeline.
+## Define GroupAFQ object
+HBN POD2 have been processed with qsiprep [^4]. This means that a brain mask
+has already been computed for them.
+
+For tractography, we use CSD-based probabilistic tractography,
+seeding 200,000 seeds but only within the ROIs
+and not throughout the white matter. This is controlled by passing
+`"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
+are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
+initiates the pipeline.
+
+```{code-cell} ipython3
my_afq = GroupAFQ(
bids_path=study_dir,
dwi_preproc_pipeline="qsiprep",
@@ -187,49 +204,49 @@
my_afq.clobber(dependent_on='recog')
my_afq.export_all()
+```
-#############################################################################
-# Visualize a montage
-# ----------------------
-# One way to examine the output of the pyAFQ pipeline is by creating a montage
-# of images of a particular bundle across a group of participants. In the montage function
-# the first input refers to a key in the bundlediect and the second gives the layout
-# of the figure (eg. 3 rows 4 columns) and finally is the view.
+## Visualize a montage
+One way to examine the output of the pyAFQ pipeline is by creating a montage
+of images of a particular bundle across a group of participants. In the montage function
+the first input refers to a key in the bundlediect and the second gives the layout
+of the figure (eg. 3 rows 4 columns) and finally is the view.
+```{code-cell} ipython3
montage = my_afq.group_montage(
"L_SLF1", (3, 4), "Sagittal", "left", slice_pos=0.5)
montage = my_afq.group_montage(
"L_SLF2", (3, 4), "Sagittal", "left", slice_pos=0.5)
montage = my_afq.group_montage(
"L_SLF3", (3, 4), "Sagittal", "left", slice_pos=0.5)
+```
-#############################################################################
-# Interactive bundle visualization
-# --------------------------------
-# Another way to examine the outputs is to export the individual bundle
-# figures, which show the streamlines, as well as the ROIs used to define the
-# bundle. This is an html file, which contains an interactive figure that can
-# be navigated, zoomed, rotated, etc.
+## Interactive bundle visualization
+Another way to examine the outputs is to export the individual bundle
+figures, which show the streamlines, as well as the ROIs used to define the
+bundle. This is an html file, which contains an interactive figure that can
+be navigated, zoomed, rotated, etc.
+```{code-cell} ipython3
bundle_html = my_afq.export("all_bundles_figure")
+```
+
+## References
+
+[^1]: Romi Sagi, J.S.H. Taylor, Kyriaki Neophytou, Tamar Cohen,
+ Brenda Rapp, Kathleen Rastle, Michal Ben-Shachar.
+ White matter associations with spelling performance.
+ Brain Struct Funct 229, 2115–2135 (2024).
+ https://doi.org/10.1007/s00429-024-02775-7
+
+[^2]: 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.
+
+[^3]: 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.
-#############################################################################
-# References
-# ----------
-# .. [1] Romi Sagi, J.S.H. Taylor, Kyriaki Neophytou, Tamar Cohen,
-# Brenda Rapp, Kathleen Rastle, Michal Ben-Shachar.
-# White matter associations with spelling performance.
-# Brain Struct Funct 229, 2115–2135 (2024).
-# https://doi.org/10.1007/s00429-024-02775-7
-#
-# .. [2] 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.
-#
-# .. [3] 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.
-#
-# .. [4] 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.
+[^4]: 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.
diff --git a/docs/source/examples/howto_examples/afq_callosal.md b/docs/source/examples/howto_examples/afq_callosal.md
new file mode 100644
index 000000000..118cde95e
--- /dev/null
+++ b/docs/source/examples/howto_examples/afq_callosal.md
@@ -0,0 +1,119 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Callosal bundles using AFQ API
+An example using the AFQ API to find callosal bundles using the templates from:
+http://hdl.handle.net/1773/34926
+
+```{code-cell} ipython3
+import os.path as op
+import matplotlib.pyplot as plt
+import nibabel as nib
+
+import plotly
+
+from AFQ.api.group import GroupAFQ
+import AFQ.api.bundle_dict as abd
+from AFQ.definitions.image import RoiImage
+import AFQ.data.fetch as afd
+```
+
+## Get some example data
+
+Retrieves [Stanford HARDI dataset](https://purl.stanford.edu/ng782rw8378).
+
+```{code-cell} ipython3
+afd.organize_stanford_data(clear_previous_afq="track")
+```
+
+## Set tractography parameters (optional)
+We make this tracking_params which we will pass to the GroupAFQ object
+which specifies that we want 100,000 seeds randomly distributed
+in the ROIs of every bundle.
+
+We only do this to make this example faster and consume less space.
+
+```{code-cell} ipython3
+tracking_params = dict(seed_mask=RoiImage(),
+ n_seeds=25000,
+ random_seeds=True,
+ rng_seed=42)
+```
+
+## Set segmentation parameters (optional)
+We make this segmentation_params which we will pass to the GroupAFQ object
+which specifies that we want to clip the extracted tract profiles
+to only be between the two ROIs.
+
+We do this because tract profiles become less reliable as the bundles
+approach the gray matter-white matter boundary. On some of the non-callosal
+bundles, ROIs are not in a good position to clip edges. In these cases,
+one can remove the first and last nodes in a tract profile.
+
+```{code-cell} ipython3
+segmentation_params = {"clip_edges": True}
+```
+
+## Initialize a GroupAFQ object:
+
+We specify bundle_info as the callosal bundles only
+(`abd.callosal_bd`). If we want to segment both the callosum
+and the other bundles, we would pass
+`abd.callosal_bd() + abd.default_bd()`
+instead. This would tell the GroupAFQ object to use bundles from both
+the standard and callosal templates.
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'stanford_hardi'),
+ dwi_preproc_pipeline='vistasoft',
+ t1_preproc_pipeline='freesurfer',
+ bundle_info=abd.callosal_bd(),
+ tracking_params=tracking_params,
+ segmentation_params=segmentation_params,
+ viz_backend_spec='plotly_no_mp4')
+
+# Calling export all produces all of the outputs of processing, including
+# tractography, scalar maps, tract profiles and visualizations:
+myafq.export_all()
+```
+
+## Create Group Density Maps:
+
+pyAFQ can make density maps of streamline counts per subject/session
+by calling `myafq.export("density_map")`. When using `GroupAFQ`, you can also
+combine these into one file by calling `myafq.export_group_density()`.
+
+```{code-cell} ipython3
+group_density = myafq.export_group_density()
+group_density = nib.load(group_density).get_fdata()
+fig, ax = plt.subplots(1)
+ax.matshow(
+ group_density[:, :, group_density.shape[-1] // 2, 0],
+ cmap='viridis')
+ax.axis("off")
+```
+
+## Visualizing bundles and tract profiles:
+This would run the script and visualize the bundles using the plotly
+interactive visualization, which should automatically open in a
+new browser window.
+
+```{code-cell} ipython3
+bundle_html = myafq.export("all_bundles_figure")
+plotly.io.show(bundle_html["01"][0])
+```
diff --git a/docs/source/examples/howto_examples/afq_fwdti.md b/docs/source/examples/howto_examples/afq_fwdti.md
new file mode 100644
index 000000000..113ee6fab
--- /dev/null
+++ b/docs/source/examples/howto_examples/afq_fwdti.md
@@ -0,0 +1,174 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to use Free water DTI
+
+The free-water DTI model [^1], [^2] fits a two compartment model to dMRI data
+with more than one non-zero shell. One compartment is a spherical compartment
+with the diffusivity of water, which accounts for free water in the tissue.
+The other compartment is the standard diffusion tensor.
+
+In this example, we will compare the results of the fwDTI model and the
+standard DTI model.
+
+```{code-cell} ipython3
+import os.path as op
+
+import matplotlib.pyplot as plt
+import nibabel as nib
+
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+
+from AFQ.definitions.image import ImageFile, RoiImage
+import AFQ.api.bundle_dict as abd
+
+import pandas as pd
+```
+
+## Get some data
+
+In this example, we will look at one subject from the Healthy Brain Network
+Processed Open Diffusion Derivatives dataset (HBN-POD2) [^3], [^4]. The data in
+this study were collected with a multi-shell sequence, meaning that most
+subjects in this study have data with more than one non-zero b-value. This
+means that we can fit the fwDTI model to their data.
+
+We'll use a fetcher to get preprocessd dMRI data for one of the >2,000
+subjects in that study. The data gets organized into a BIDS-compatible
+format in the `~/AFQ_data/HBN` folder.
+
+```{code-cell} ipython3
+study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
+```
+
+## Define an AFQ object
+In addition to preprocessd dMRI data, HBN-POD2 contains brain mask and mapping
+information for each subject. We can use this information in our pipeline, by
+inserting this information as `mapping_definition` inputs to the `GroupAFQ` class
+initializer. When initializing this object, we will also ask for the fwDTI scalars
+to be computed. For expedience, we will limit our investigation to the bilateral
+arcuate fasciculus and track only around that bundle. If you would like to do this
+for all bundles, you would remove the `bundle_dict` and `tracking_params` inputs
+to the initializer that
+are provided below.
+
+```{code-cell} ipython3
+bundle_names = ["Left Arcuate", "Right Arcuate"]
+bundle_dict = abd.default_bd()[bundle_names]
+
+myafq = GroupAFQ(
+ bids_path=study_dir,
+ dwi_preproc_pipeline='qsiprep',
+ output_dir=op.join(study_dir, "derivatives", "afq_fwdti"),
+ bundle_info=bundle_dict,
+ tracking_params={
+ "n_seeds": 50000,
+ "random_seeds": True,
+ "seed_mask": RoiImage(use_waypoints=True, use_endpoints=True),
+ },
+ scalars=["fwdti_fa", "fwdti_md", "fwdti_fwf", "dti_fa", "dti_md"])
+```
+
+## Compare fwDTI and DTI maps
+First, we take a look at the maps for the FA and MD calculated using the two
+models
+
+```{code-cell} ipython3
+fwFA = nib.load(myafq.export("fwdti_fa")["NDARAA948VFH"]).get_fdata()
+FA = nib.load(myafq.export("dti_fa")["NDARAA948VFH"]).get_fdata()
+
+fig, ax = plt.subplots(1, 2)
+ax[0].matshow(FA[:, :, FA.shape[-1] // 2], cmap='gray')
+ax[0].axis("off")
+
+ax[1].matshow(fwFA[:, :, fwFA.shape[-1] // 2], cmap='gray')
+ax[1].axis("off")
+
+
+fwMD = nib.load(myafq.export("fwdti_md")["NDARAA948VFH"]).get_fdata()
+MD = nib.load(myafq.export("dti_md")["NDARAA948VFH"]).get_fdata()
+
+fig, ax = plt.subplots(1, 2)
+ax[0].matshow(MD[:, :, MD.shape[-1] // 2], cmap='gray', vmax=0.005)
+ax[0].axis("off")
+
+ax[1].matshow(fwMD[:, :, fwMD.shape[-1] // 2], cmap='gray', vmax=0.005)
+ax[1].axis("off")
+```
+
+## Free-water fraction map
+In addition to the standard tensor scalars, provided by the fwDTI model, this
+model also computes a free-water fraction, which is a number between 0 and 1
+that assesses the fraction of the voxel signal that is explained by the free
+water compartment.
+
+```{code-cell} ipython3
+fwf = nib.load(myafq.export("fwdti_fwf")["NDARAA948VFH"]).get_fdata()
+fig, ax = plt.subplots()
+ax.matshow(fwf[:, :, fwf.shape[-1] // 2], cmap='gray')
+ax.axis("off")
+```
+
+## Comparing bundle profiles
+Exporting the profiles will create a CSV file that contains information about
+node-by-node values of the scalars computed with both models. Here, we read in
+this information with Pandas and plot a comparison. As you can see, when free
+water is accounted for with the fwDTI model, FA along the bundle is higher and
+MD is lower than that estimated with the standard DTI model.
+
+```{code-cell} ipython3
+profiles_csv = myafq.export("profiles")['NDARAA948VFH']
+profiles = pd.read_csv(profiles_csv)
+
+fig, ax = plt.subplots(3, 2)
+for ii, bundle in enumerate(["Left Arcuate", "Right Arcuate"]):
+ ax[0, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_fa"],
+ label="fwDTI")
+ ax[0, ii].plot(profiles[profiles["tractID"] == bundle]["dti_fa"],
+ label="DTI")
+ ax[0, ii].set_ylabel("FA")
+ ax[0, ii].legend()
+ ax[1, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_md"],
+ label="fwDTI")
+ ax[1, ii].plot(profiles[profiles["tractID"] == bundle]["dti_md"],
+ label="DTI")
+ ax[1, ii].set_ylabel("MD")
+ ax[1, ii].legend()
+ ax[2, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_fwf"])
+ ax[2, ii].set_ylabel("Free water fraction")
+ ax[2, ii].set_xlabel("Distance along the bundle (A => P)")
+```
+
+## References
+
+[^1]: Hoy AR, Koay CG, Kecskemeti SR, Alexander AL. Optimization of a free
+ water elimination two-compartment model for diffusion tensor imaging.
+ Neuroimage. 2014;103:323-333.
+
+[^2]: Henriques RN, Rokem A, Garyfallidis E, St-Jean S, Peterson ET, Correia
+ MM. [Re] Optimization of a free water elimination two-compartment model
+ for diffusion tensor imaging. bioRxiv. February 2017:108795.
+ doi:10.1101/108795
+
+[^3]: 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.
+
+[^4]: 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.
diff --git a/docs/source/examples/howto_examples/baby_afq.md b/docs/source/examples/howto_examples/baby_afq.md
new file mode 100644
index 000000000..b5b7cd7db
--- /dev/null
+++ b/docs/source/examples/howto_examples/baby_afq.md
@@ -0,0 +1,168 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# BabyAFQ : tractometry for infant dMRI data
+
+The following is an example of tractometry for infant bundles. This example and
+resulting pyAFQ support for pediatric bundles was inspired by and largely due
+to the work of Grotheer et al. [^Grotheer2021], as implemented in
+[^Grotheer2023].
+
+:::{note}
+Because it is time and disk-space consuming, this example
+is not run when the pyAFQ documentation is built. To run this example
+yourself, you can download the contents of this file as an
+executable `.py` file or as a Jupyter notebook from the links at the bottom
+of the page.
+:::
+
+```{code-cell} ipython3
+import os.path as op
+import plotly
+import wget
+import zipfile
+
+from AFQ.api.group import GroupAFQ
+import AFQ.api.bundle_dict as abd
+import AFQ.data.fetch as afd
+```
+
+## Baby dMRI data
+
+Infant MRI data are quite different from data acquired in grownup
+participants, and even from children that are just a few years older.
+First, there is the rather obvious difference in size. Baby brains are
+approximately 25% the size of grownup brains at birth. But there are also
+often less known differences in brain tissue properties. For example, the
+myelin content of white matter is much lower in infants than in grownups.
+This is important because the diffusion signal that we measure with dMRI is
+sensitive to the myelin content, and it means that the dMRI signal differs
+quite a bit in newborn infants. For the purpose of delineating the major
+white matter pathways, it is also important to know that their shape,
+location and curvature is different in infants than in grownups. For example,
+the arcuate fasciculus is much more curved in infants than in grownups.
+Because of this, we use a different set of templates for infant brains than
+for grownup brains. These templates were created and validated by
+Mareike Grotheer and colleagues in [^Grotheer2021]. They are available for
+download as part of the pyAFQ software, as we will show below.
+
+In this example, we will demonstrate the use of pyAFQ on data from one
+infant. The data, provided by Kalanit Grill Spector's
+[Stanford Vision and Perception Neuroscience Lab](http://vpnl.stanford.edu/),
+and was previously published in [^Grotheer2021].
+The data is available to download on
+[Figshare](https://figshare.com/articles/dataset/Example_babyAFQ_BIDS_subject/21440739).
+You can download it from there and unzip it into ~/AFQ_Data/baby_example/
+(Note that this is 2.69GB of data, so it can take a while to download). Or
+you can download it and unzip it using the following block of code.
+
+```{code-cell} ipython3
+data_folder = op.join(op.expanduser('~'), "AFQ_data/")
+baby_zip = op.join(data_folder, "baby_example.zip")
+if not op.exists(baby_zip):
+ print("Downloading processed pediatric data; this could take a while...")
+ wget.download(
+ "https://figshare.com/ndownloader/files/38053692",
+ baby_zip)
+
+with zipfile.ZipFile(baby_zip, 'r') as zip_ref:
+ zip_ref.extractall(op.join(data_folder, "baby_example"))
+```
+
+## Initialize a GroupAFQ object:
+
+Now that the data is downloaded and organized in a BIDS-compliant structure,
+we can start running pyAFQ on it. We start by initializing a GroupAFQ object.
+This object manages all of the data transformations and computations
+conducted by the software, based on its initial configuration, which we set
+up below.
+
+A few special things to note here:
+
+1. The data were preprocessed using the `vistasoft` pipeline, so we set
+ `dwi_preproc_pipeline = "vistasoft"`.
+2. We use the UNC neonatal template, which can be read on a call to the
+ `read_pediatric_templates` function in `AFQ.data.fetch`.
+3. We use the `baby_bd` to define the bundles that we want to
+ segment. This dictionary is different from the default behavior in that it
+ uses the waypoint ROIs from [^Grotheer2021].
+4. In this case, tractography has already been run using
+ [MRTRIX](https://www.mrtrix.org/), and is accessed using the
+ `import_tract` key-word argument.
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ bids_path=op.join(op.expanduser('~'),
+ "AFQ_data/baby_example/example_bids_subject"),
+ dwi_preproc_pipeline="vistasoft",
+ reg_template_spec=afd.read_pediatric_templates(
+ )["UNCNeo-withCerebellum-for-babyAFQ"],
+ reg_subject_spec="b0",
+ bundle_info=abd.baby_bd(),
+ import_tract={
+ "suffix": "tractography", "scope": "mrtrix"},
+)
+```
+
+## Running the pipeline
+
+A call to the `export` function will trigger the pyAFQ pipeline. This will
+run tractography, bundle segmentation, and bundle cleaning. The results will
+be saved in the `~/AFQ_data/baby_example/derivatives/afq` folder. This can
+take a while to run, depending on your computer.
+In this case, we call `export` with the `all_bundles_figure` option. This is
+because visualizations are created after most other parts of the pipeline
+have been run. This means that when this call is done, you should have many
+of the derivative results in the output folder, including the tractography,
+segmentation, and tract profile results, as well as the visualizations.
+
+```{code-cell} ipython3
+viz = myafq.export("all_bundles_figure")
+```
+
+## Viewing the results
+One way to view the results is to open the file named
+`sub-01_ses-01_dwi_space-RASMM_model-probCSD_algo-AFQ_desc-viz_dwi.html`
+in your browser. This is a visualization of the tractography and segmentation
+results for all of the bundles. You can navigate this visualization by
+clicking on the different bundles in the legend on the right side of the
+screen. You can also zoom in and out using the mouse wheel, and rotate the
+view by clicking and dragging with the mouse. You can also view the FA tract
+profiles in a plot on the left side of the page.
+
+If the baby bundles appear dark in the html visualization due to low FA values, you
+can reduce the upper limit of the range in the `sbv_lims_bundles` option when
+building your GroupAFQ object (e.g. `GroupAFQ(..., sbv_lims_bundles=[0, 0.5])`).
+
+```{code-cell} ipython3
+
+```
+
+## References
+
+[^Grotheer2021]: Grotheer, Mareike, Mona Rosenke, Hua Wu, Holly Kular,
+ Francesca R. Querdasi, Vaidehi S. Natu, Jason D. Yeatman,
+ and Kalanit Grill-Spector. "White matter myelination during
+ early infancy is linked to spatial gradients and myelin
+ content at birth." Nature communications 13: 997.
+
+[^Grotheer2023]: Grotheer, Mareike, David Bloom, John Kruper,
+ Adam Richie-Halford, Stephanie Zika,
+ Vicente A. Aguilera González, Jason D. Yeatman,
+ Kalanit Grill-Spector, and Ariel Rokem. "Human white matter
+ myelinates faster in utero than ex utero." Proceedings
+ of the National Academy of Sciences 120: e2303491120.
diff --git a/examples/howto_examples/cerebellar_peduncles.py b/docs/source/examples/howto_examples/cerebellar_peduncles.md
similarity index 67%
rename from examples/howto_examples/cerebellar_peduncles.py
rename to docs/source/examples/howto_examples/cerebellar_peduncles.md
index b8868621b..b7e8aaca2 100644
--- a/examples/howto_examples/cerebellar_peduncles.py
+++ b/docs/source/examples/howto_examples/cerebellar_peduncles.md
@@ -1,40 +1,53 @@
-"""
-================================
-Delineating cerebellar peduncles
-================================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# 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].
+on [^1], [^2].
-"""
+```{code-cell} ipython3
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.
-"""
+```{code-cell} ipython3
bids_path = afd.fetch_hbn_afq(["NDARAA948VFH"])[1]
+```
-"""
The next line downloads the cerebellar peduncle templates from `Figshare `_.
-"""
+```{code-cell} ipython3
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
@@ -43,46 +56,44 @@
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.
-"""
+```{code-cell} ipython3
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
+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.
-"""
+```{code-cell} ipython3
cp_afq = GroupAFQ(
name="cp_afq",
bids_path=bids_path,
dwi_preproc_pipeline="qsiprep",
tracking_params={
"n_seeds": 4,
+ "random_seeds": False,
"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")
-"""
+```{code-cell} ipython3
+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.
+## References
-.. [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
+[^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
diff --git a/docs/source/examples/howto_examples/cloudknot_example.md b/docs/source/examples/howto_examples/cloudknot_example.md
new file mode 100644
index 000000000..395d70ca6
--- /dev/null
+++ b/docs/source/examples/howto_examples/cloudknot_example.md
@@ -0,0 +1,198 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Using cloudknot to run pyAFQ on AWS batch:
+
+One of the purposes of `pyAFQ` is to analyze large-scale openly-available
+datasets, such as those in the
+[Human Connectome Project](https://www.humanconnectome.org/).
+
+To analyze these datasets, large amounts of compute are needed.
+One way to gain access to massive computational power is by using
+cloud computing. Here, we will demonstrate
+how to use `pyAFQ` in the Amazon Web Services cloud.
+
+We will rely on the [AWS Batch Service](https://aws.amazon.com/batch/),
+and we will submit work into AWS Batch using software that our group
+developed called [Cloudknot](https://nrdg.github.io/cloudknot/).
+
++++
+
+Import cloudknot and set the AWS region within which computations will take
+place. Setting a region is important, because if the data that you are
+analyzing is stored in `AWS S3 `_ in a
+particular region, it is best to run the computation in that region as well.
+That is because AWS charges for inter-region transfer of data.
+
+```{code-cell} ipython3
+import cloudknot as ck
+ck.set_region('us-east-1')
+```
+
+## Define the function to use
+
+`Cloudknot` uses the single program multiple data paradigm of computing.
+This means that the same function will be run on multiple different inputs.
+For example, a `pyAFQ` processing function run
+on multiple different subjects in a dataset.
+Below, we define the function that we will use. Notice that
+`Cloudknot` functions include the import statements of the dependencies
+used. This is necessary so that `Cloudknot` knows
+what dependencies to install into AWS Batch to run this function.
+
+```{code-cell} ipython3
+
+def afq_process_subject(subject):
+ # define a function that each job will run
+ # In this case, each process does a single subject
+ import s3fs
+ # all imports must be at the top of the function
+ # cloudknot installs the appropriate packages from pip
+ from s3bids.utils import S3BIDSStudy
+ from AFQ.api.group import GroupAFQ
+ import AFQ.definitions.image as afm
+
+ # Download the given subject to your local machine from s3
+ # Can find subjects more easily if they are specified in a
+ # BIDS participants.tsv file, even if it is sparse
+ study_ixi = S3BIDSStudy(
+ "my_study",
+ "my_study_bucket",
+ "my_study_prefix",
+ subjects=[subject],
+ use_participants_tsv=True,
+ anon=False)
+ study_ixi.download(
+ "local_bids_dir",
+ include_derivs=["pipeline_name"])
+
+ # define the api AFQ object
+ myafq = GroupAFQ(
+ "local_bids_dir",
+ dwi_preproc_pipeline="pipeline_name",
+ viz_backend_spec='plotly') # this will generate both interactive html and MP4s # noqa
+
+ # export_all runs the entire pipeline and creates many useful derivates
+ myafq.export_all()
+
+ # upload the results to some location on s3
+ myafq.upload_to_s3(
+ s3fs.S3FileSystem(),
+ "my_study_bucket/my_study_prefix/derivatives/afq")
+```
+
+Here we provide a list of subjects that we have selected to process
+to randomly select 3 subjects without replacement, instead do:
+subjects = [[1], [2], [3]]
+see the docstring for S3BIDSStudy.__init__ for more information
+
+```{code-cell} ipython3
+subjects = ["123456", "123457", "123458"]
+```
+
+## Defining a `Knot` instance
+
+We instantiate a class instance of the `ck.Knot` class.
+This object will be used to run your jobs.
+The object is instantiated with the `'AmazonS3FullAccess'` policy,
+so that it can write the results
+out to S3, into a bucket that you have write permissions on.
+Setting the `bid_percentage` key-word makes AWS Batch use
+[spot EC2 instances](https://aws.amazon.com/ec2/spot/) for the
+computation. This can result in substantial cost-savings, as spot compute
+instances can cost much less than on-demand instances.
+However, not that spot instances can also
+be evicted, so if completing all of the work is very time-sensitive,
+do not set this key-word argument. Using the `image_github_installs`
+key-word argument will install pyAFQ from GitHub.
+You can also specify other forks and branches to install from.
+
+```{code-cell} ipython3
+knot = ck.Knot(
+ name='afq-process-subject-201009-0',
+ func=afq_process_subject,
+ base_image='python:3.11',
+ image_github_installs="https://github.com/tractometry/pyAFQ.git",
+ pars_policies=('AmazonS3FullAccess',),
+ bid_percentage=100)
+```
+
+## Launching the computation
+
+The `map` method of the `Knot` object maps each of the inputs
+provided as a sequence onto the function and executes the function on each
+one of them in parallel.
+
+```{code-cell} ipython3
+result_futures = knot.map(subjects)
+```
+
+Once computations have started, you can call the following
+function to view the progress of jobs:
+
+```python
+knot.view_jobs()
+```
+
+You can also view the status of a specific job:
+
+```python
+knot.jobs[0].status
+```
+
+```{code-cell} ipython3
+
+```
+
+When all jobs are finished, remember to use the `clobber` method to
+destroy all of the AWS resources created by the `Knot`
+
+```{code-cell} ipython3
+result_futures.result()
+knot.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
+```
+
+In a second `Knot` object, we use a function that takes the
+resulting profiles of each subject and combines them into one csv file.
+
+```{code-cell} ipython3
+
+def afq_combine_profiles(dummy_argument):
+ from AFQ.api import download_and_combine_afq_profiles
+ download_and_combine_afq_profiles(
+ "my_study_bucket", "my_study_prefix")
+
+
+knot2 = ck.Knot(
+ name='afq_combine_subjects-201009-0',
+ func=afq_combine_profiles,
+ base_image='python:3.11',
+ image_github_installs="https://github.com/tractometry/pyAFQ.git",
+ pars_policies=('AmazonS3FullAccess',),
+ bid_percentage=100)
+```
+
+This knot is called with a dummy argument, which is not used within the
+function itself. The `job_type` key-word argument is used to signal to
+`Cloudknot` that only one job is submitted rather than the default
+array of jobs.
+
+```{code-cell} ipython3
+result_futures2 = knot2.map(["dummy_argument"], job_type="independent")
+result_futures2.result()
+knot2.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
+```
diff --git a/examples/howto_examples/cloudknot_hcp_example.py b/docs/source/examples/howto_examples/cloudknot_hcp_example.md
similarity index 53%
rename from examples/howto_examples/cloudknot_hcp_example.py
rename to docs/source/examples/howto_examples/cloudknot_hcp_example.md
index b58da085d..81d4afa85 100644
--- a/examples/howto_examples/cloudknot_hcp_example.py
+++ b/docs/source/examples/howto_examples/cloudknot_hcp_example.md
@@ -1,34 +1,52 @@
-"""
-==========================
-AFQ with HCP data
-==========================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# AFQ with HCP data
+
This example demonstrates how to use the AFQ API to analyze HCP data.
For this example to run properly, you will need to gain access to the HCP data.
This can be done by following this instructions on the webpage
-`here `_.
-We will use the ``Cloudknot`` library to run our AFQ analysis in the AWS
+[here](https://wiki.humanconnectome.org/display/PublicData/How+To+Connect+to+Connectome+Data+via+AWS).
+We will use the `Cloudknot` library to run our AFQ analysis in the AWS
Batch service (see also
-`this example `_).
-In the following we will use ``Cloudknot`` to run multiple
+[this example](http://tractometry.org/pyAFQ/auto_examples/cloudknot_example.html)).
+In the following we will use `Cloudknot` to run multiple
configurations of pyAFQ on the HCP dataset. Specifically, here we will run
pyAFQ with different tractography seeding strategies.
-"""
-##########################################################################
-# Import cloudknot and set the correct region. The HCP data is stored in `us-east-1`, so it's best
-# to analyze it there.
++++
+
+Import cloudknot and set the correct region. The HCP data is stored in `us-east-1`, so it's best
+to analyze it there.
+
+```{code-cell} ipython3
import configparser
import itertools
import cloudknot as ck
import os.path as op
ck.set_region('us-east-1')
+```
-##########################################################################
-# Define a function to run. This function allows us to pass in the subject ID for the subjects we would
-# like to analyze , as well as strategies for seeding tractography (different masks and/or different
-# numbers of seeds per voxel).
+Define a function to run. This function allows us to pass in the subject ID for the subjects we would
+like to analyze , as well as strategies for seeding tractography (different masks and/or different
+numbers of seeds per voxel).
+```{code-cell} ipython3
def afq_process_subject(subject, seed_mask, n_seeds,
aws_access_key, aws_secret_key):
@@ -89,41 +107,49 @@ def afq_process_subject(subject, seed_mask, n_seeds,
s3fs.S3FileSystem(),
(f"my_study_bucket/my_study_prefix_{seed_mask}_{n_seeds}"
f"/derivatives/afq"))
+```
+In this example, we will process the data from the following subjects
-##########################################################################
-# In this example, we will process the data from the following subjects
+```{code-cell} ipython3
subjects = ["103818", "105923", "111312"]
+```
+
+We will test combinations of different conditions:
+subjects, seed masks, and number of seeds
-##########################################################################
-# We will test combinations of different conditions:
-# subjects, seed masks, and number of seeds
+```{code-cell} ipython3
seed_mask = ["fa", "roi"]
n_seeds = [1, 2, 1000000, 2000000]
+```
-##########################################################################
-# The following function creates all the combinations of the above lists, such that every subject is
-# run with every mask and every number of seeds.
+The following function creates all the combinations of the above lists, such that every subject is
+run with every mask and every number of seeds.
+
+```{code-cell} ipython3
args = list(itertools.product(subjects, seed_mask, n_seeds))
+```
+
+We assume that the credentials for HCP usage are stored in the home directory in a
+`~/.aws/credentials` file. This is where these credentials are stored if the AWS CLI is used to
+configure the profile. We use the standard lib `configparser` library
+to get the relevant hcp keys from there.
-##########################################################################
-# We assume that the credentials for HCP usage are stored in the home directory in a
-# `~/.aws/credentials` file. This is where these credentials are stored if the AWS CLI is used to
-# configure the profile. We use the standard lib ``configparser`` library
-# to get the relevant hcp keys from there.
+```{code-cell} ipython3
CP = configparser.ConfigParser()
CP.read_file(open(op.join(op.expanduser('~'), '.aws', 'credentials')))
CP.sections()
aws_access_key = CP.get('hcp', 'AWS_ACCESS_KEY_ID')
aws_secret_key = CP.get('hcp', 'AWS_SECRET_ACCESS_KEY')
+```
-##########################################################################
-# The following function will attach your AWS keys to each list in a list of lists
-# We use this with each list being a list of arguments,
-# and we append the AWS keys to each list of arguments, so that we can pass
-# them into the function to be used on AWS Batch to download the data into the
-# AWS Batch machines.
+The following function will attach your AWS keys to each list in a list of lists
+We use this with each list being a list of arguments,
+and we append the AWS keys to each list of arguments, so that we can pass
+them into the function to be used on AWS Batch to download the data into the
+AWS Batch machines.
+```{code-cell} ipython3
def attach_keys(list_of_arg_lists):
new_list_of_arg_lists = []
@@ -132,16 +158,19 @@ def attach_keys(list_of_arg_lists):
arg_ls.extend([aws_access_key, aws_secret_key])
new_list_of_arg_lists.append(arg_ls)
return new_list_of_arg_lists
+```
+This calls the function to attach the access keys to the argument list
-##########################################################################
-# This calls the function to attach the access keys to the argument list
+```{code-cell} ipython3
args = attach_keys(args)
+```
-##########################################################################
-# Define the :meth:`Knot` object to run your jobs on. See
-# `this example `_ for more
-# details about the arguments to the object.
+Define the `Knot` object to run your jobs on. See
+[this example](http://tractometry.org/pyAFQ/auto_examples/cloudknot_example.html) for more
+details about the arguments to the object.
+
+```{code-cell} ipython3
knot = ck.Knot(
name='afq-hcp-tractography-201110-0',
func=afq_process_subject,
@@ -149,33 +178,43 @@ def attach_keys(list_of_arg_lists):
image_github_installs="https://github.com/tractometry/pyAFQ.git",
pars_policies=('AmazonS3FullAccess',),
bid_percentage=100)
+```
+
+This launches a process for each combination.
+Because `starmap` is `True`, each list in `args` will be unfolded
+and passed into `afq_process_subject` as arguments.
-##########################################################################
-# This launches a process for each combination.
-# Because `starmap` is `True`, each list in `args` will be unfolded
-# and passed into `afq_process_subject` as arguments.
+```{code-cell} ipython3
result_futures = knot.map(args, starmap=True)
+```
-##########################################################################
-# The following function can be called repeatedly in a jupyter notebook
-# to view the progress of jobs::
-#
-# knot.view_jobs()
-#
-# You can also view the status of a specific job::
-#
-# knot.jobs[0].status
-
-##########################################################################
-# When all jobs are finished, remember to clobber the knot to destroy all the resources that were
-# created in AWS.
+The following function can be called repeatedly in a jupyter notebook
+to view the progress of jobs:
+
+```python
+knot.view_jobs()
+```
+
+You can also view the status of a specific job:
+
+```python
+knot.jobs[0].status
+```
+
++++
+
+When all jobs are finished, remember to clobber the knot to destroy all the resources that were
+created in AWS.
+
+```{code-cell} ipython3
result_futures.result() # waits for futures to resolve, not needed in notebook
knot.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
+```
-##########################################################################
-# We continue processing to create another knot which takes the resulting profiles of each
-# combination and combines them all into one csv file
+We continue processing to create another knot which takes the resulting profiles of each
+combination and combines them all into one csv file
+```{code-cell} ipython3
def afq_combine_profiles(seed_mask, n_seeds):
from AFQ.api import download_and_combine_afq_profiles
@@ -190,9 +229,11 @@ def afq_combine_profiles(seed_mask, n_seeds):
image_github_installs="https://github.com/tractometry/pyAFQ.git",
pars_policies=('AmazonS3FullAccess',),
bid_percentage=100)
+```
+
+the arguments to this call to `map` are all the different configurations of pyAFQ that we ran
-##########################################################################
-# the arguments to this call to :meth:`map` are all the different configurations of pyAFQ that we ran
+```{code-cell} ipython3
seed_mask = ["fa", "roi"]
n_seeds = [1, 2, 1000000, 2000000]
args = list(itertools.product(seed_mask, n_seeds))
@@ -200,3 +241,4 @@ def afq_combine_profiles(seed_mask, n_seeds):
result_futures2 = knot2.map(args, starmap=True)
result_futures2.result()
knot2.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
+```
diff --git a/examples/howto_examples/README.rst b/docs/source/examples/howto_examples/index.rst
similarity index 88%
rename from examples/howto_examples/README.rst
rename to docs/source/examples/howto_examples/index.rst
index 74b2d9998..92873f73c 100644
--- a/examples/howto_examples/README.rst
+++ b/docs/source/examples/howto_examples/index.rst
@@ -8,4 +8,7 @@ of pyAFQ. These are intended to provide a reference and answer questions
about the variety of pyAFQ functionality that is available.
.. toctree::
- :maxdepth: 2
\ No newline at end of file
+ :maxdepth: 2
+ :glob:
+
+ *
diff --git a/docs/source/examples/howto_examples/optic_radiations.md b/docs/source/examples/howto_examples/optic_radiations.md
new file mode 100644
index 000000000..6786fd5e9
--- /dev/null
+++ b/docs/source/examples/howto_examples/optic_radiations.md
@@ -0,0 +1,175 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to add new bundles into pyAFQ (Optic Radiations Example)
+
+pyAFQ is designed to be customizable and extensible. This example shows how you
+can customize it to define a new bundle based on a definition of waypoint and
+endpoint ROIs of your design.
+
+In this case, we add the optic radiations, based on work by Caffara et al. [^1],
+[^2]. The optic radiations (OR) are the primary projection of visual information
+from the lateral geniculate nucleus of the thalamus to the primary visual
+cortex. Studying the optic radiations with dMRI provides a linkage between white
+matter tissue properties, visual perception and behavior, and physiological
+responses of the visual cortex to visual stimulation.
+
+We start by importing some of the components that we need for this example and
+fixing the random seed for reproducibility
+
+```{code-cell} ipython3
+import os.path as op
+import plotly
+import numpy as np
+import shutil
+
+from AFQ.api.group import GroupAFQ
+import AFQ.api.bundle_dict as abd
+import AFQ.data.fetch as afd
+from AFQ.definitions.image import ImageFile, RoiImage
+import AFQ.utils.streamlines as aus
+np.random.seed(1234)
+```
+
+## Get dMRI data
+
+We will analyze one subject from the Healthy Brain Network Processed Open
+Diffusion Derivatives dataset (HBN-POD2) [^3], [^4]. We'll use a fetcher to
+get preprocessed dMRI data for one of the >2,000 subjects in that study. The
+data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
+folder:
+
+```{code-cell} ipython3
+study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
+```
+
+## Define custom `BundleDict` object
+The `BundleDict` object holds information about "include" and "exclude" ROIs,
+as well as endpoint ROIS, and whether the bundle crosses the midline. In this
+case, the ROIs are all defined in the MNI template space that is used as the
+default template space in pyAFQ, but, in principle, other template spaces
+could be used.
+
+The ROIs for the case can be downloaded using a custom fetcher which saves
+the ROIs to a folder and creates a dictionary of paths to the ROIs:
+
+```{code-cell} ipython3
+or_rois = afd.read_or_templates()
+
+bundles = abd.OR_bd()
+```
+
+Custom bundle definitions such as the OR, and the standard BundleDict can be
+combined through addition. To get both the OR and the standard bundles, we
+would execute the following code:
+
+```python
+bundles = bundles + abd.default_bd()
+```
+
+In this case, we will skip this and generate just the OR.
+
+```{code-cell} ipython3
+
+```
+
+## Define GroupAFQ object
+
+HBN POD2 have been processed with qsiprep [^5]. This means that a brain mask
+has already been computer for them. As you can see in other examples, these
+data also have a mapping calculated for them, which can also be incorporated
+into processing. However, in this case, we will let pyAFQ calculate its own
+SyN-based mapping so that the `combine_bundle` method can be used below to
+create a montage visualization.
+
+For tractography, we use CSD-based probabilistic tractography seeding
+extensively (`n_seeds=4` means 81 seeds per voxel!), but only within the ROIs
+and not throughout the white matter. This is controlled by passing
+`"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
+are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
+initiates the pipeline.
+
+```{code-cell} ipython3
+my_afq = GroupAFQ(
+ bids_path=study_dir,
+ dwi_preproc_pipeline="qsiprep",
+ participant_labels=["NDARAA948VFH"],
+ output_dir=op.join(study_dir, "derivatives", "afq_or"),
+ tracking_params={"n_seeds": 4,
+ "random_seeds": False,
+ "directions": "prob",
+ "odf_model": "CSD",
+ "seed_mask": RoiImage()},
+ bundle_info=bundles)
+
+my_afq.export_all()
+```
+
+## Visualize a montage
+
+One way to examine the output of the pyAFQ pipeline is by creating a montage
+of images of a particular bundle across a group of participants (or, in this
+case, the one participant that was analyzed).
+
+:::{note}
+The montage file is copied to the present working directory so that it gets
+properly rendered into the web-page containing this example. It is not
+necessary to do this when running this type of analysis.
+:::
+
+```{code-cell} ipython3
+my_afq.combine_bundle("Left Optic Radiation")
+montage = my_afq.group_montage(
+ "Left Optic Radiation",
+ (1, 1), "Axial", "left")
+shutil.copy(montage[0], op.split(montage[0])[-1])
+```
+
+## Interactive bundle visualization
+Another way to examine the outputs is to export the individual bundle
+figures, which show the streamlines, as well as the ROIs used to define the
+bundle. This is an html file, which contains an interactive figure that can
+be navigated, zoomed, rotated, etc.
+
+```{code-cell} ipython3
+bundle_html = my_afq.export("indiv_bundles_figures")
+plotly.io.show(bundle_html["NDARAA948VFH"]["Left Optic Radiation"])
+```
+
+## References
+
+[^1]: Caffarra S, Joo SJ, Bloom D, Kruper J, Rokem A, Yeatman JD. Development
+ of the visual white matter pathways mediates development of
+ electrophysiological responses in visual cortex. Hum Brain Mapp.
+ 2021;42(17):5785-5797.
+
+[^2]: Caffarra S, Kanopka K, Kruper J, Richie-Halford A, Roy E, Rokem A,
+ Yeatman JD. Development of the alpha rhythm is linked to visual white
+ matter pathways and visual detection performance. bioRxiv.
+ doi:10.1101/2022.09.03.506461
+
+[^3]: 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.
+
+[^4]: 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.
+
+[^5]: 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.
diff --git a/docs/source/examples/howto_examples/optic_tract.md b/docs/source/examples/howto_examples/optic_tract.md
new file mode 100644
index 000000000..1f945427d
--- /dev/null
+++ b/docs/source/examples/howto_examples/optic_tract.md
@@ -0,0 +1,190 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to track the Optic Tract and Posterior Optic Nerve in pyAFQ
+
+pyAFQ is designed to be customizable and extensible, even to
+relatively small bundles. This example will be based on the work
+of Kruper et al. [^1]. Here, part of the trick is that most preprocessing
+pipelines will cut off a portion or all of the optic nerve. So, we
+only attempt to track the most posterior portion of the optic nerve,
+the optic tract, and the optic chiasm. In addition, we will use our own
+custom PVE maps from the T1-weighted image to help with segmentation.
+
+```{code-cell} ipython3
+import plotly
+import os.path as op
+
+import AFQ.api.bundle_dict as abd
+from AFQ.api.group import GroupAFQ
+
+import AFQ.data.fetch as afd
+from AFQ.definitions.image import RoiImage
+import AFQ.definitions.image as afm
+```
+
+## Get dMRI data
+
+We will analyze one subject from the Healthy Brain Network Processed Open
+Diffusion Derivatives dataset (HBN-POD2) [^2], [^3]. We'll use a fetcher to
+get preprocessed dMRI data for one of the >2,000 subjects in that study. The
+data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
+folder:
+
+```{code-cell} ipython3
+study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
+```
+
+## Define Optic Tract and Optic Nerve `BundleDict` object
+The `BundleDict` object holds information about the ROIs used to define the
+optic tract and optic nerve bundles. In this case, there is also a curvature
+criterion applied to the optic tract bundles to help separate them from
+other nearby bundles. Additionally, qb_thresh is set to 6 to use a clustering
+approach to cleaning as opposed to our standard Mahalanobis distance approach.
+
+```{code-cell} ipython3
+oton_rois = afd.read_oton_templates(as_img=False)
+
+otoc_bd = abd.BundleDict({
+ "Left Optic": {
+ "include": [
+ oton_rois["left_OT_1"],
+ oton_rois["left_OT_0"],
+ oton_rois["left_ON_0"],
+ ],
+ "curvature": {"path": oton_rois["left_OTOC_curve"], "thresh": 20, "cut": True},
+ "qb_thresh": 6
+ },
+ "Right Optic": {
+ "include": [
+ oton_rois["right_OT_1"],
+ oton_rois["right_OT_0"],
+ oton_rois["right_ON_0"],
+ ],
+ "curvature": {"path": oton_rois["right_OTOC_curve"], "thresh": 20, "cut": True},
+ "qb_thresh": 6
+ },
+ "Left Optic Cross": {
+ "include": [
+ oton_rois["left_OT_1"],
+ oton_rois["left_OT_0"],
+ oton_rois["right_ON_0"],
+ ],
+ "qb_thresh": 6
+ },
+ "Right Optic Cross": {
+ "include": [
+ oton_rois["right_OT_1"],
+ oton_rois["right_OT_0"],
+ oton_rois["left_ON_0"],
+ ],
+ "qb_thresh": 6
+ },
+})
+```
+
+## Tractography and segmentation parameters
+Here, we define some custom parameters for tractography and segmentation.
+For tractography, we use a higher max_angle to account for the
+sharp turn the optic tract makes around the midbrain. Additionally, we seed
+densely around the ROIs.
+For segmentation, we use more lenient cleaning parameters to account for
+the small size of these bundles.
+
+```{code-cell} ipython3
+tractography_params = {
+ "seed_mask": RoiImage(),
+ "n_seeds": 20,
+ "random_seeds": False,
+ "max_angle": 60,
+ "trx": True,
+}
+
+segmentation_params = {
+ "cleaning_params": {
+ "distance_threshold": 2,
+ "length_threshold": 3,
+ }
+}
+```
+
+## Define PVE images for segmentation
+Finally, we define the PVE images that will be used to guide tracking.
+For these bundles in particular, this is the trickiest part. Portions of
+the optic nerve often have low FA, fall outside of the brain mask, or are
+simply misclassified as gray matter or CSF. In this case, we threshold
+on the T1-weighted image using manually set thresholds. We only divide it into
+gray and white matter and accept all streamlines (we do not attempt to filter
+out streamlines terminating in the CSF; these are normally handled in the bundle
+recognition and cleaning steps). In your case, this can also be done manually,
+or done using DIPY's Markov Random Field (MRF;
+https://docs.dipy.org/stable/examples_built/segmentation/tissue_classification.html).
+Just remember to either use the unmasked T1 (as we do here) or ensure that
+the brain mask includes the optic nerve.
+
+In the event that tractography fails,
+you can also check pyAFQ's outputs to see the PVE images and white matter gray
+matter interface (wmgmi) that pyAFQ used for tractography. You can then adjust
+pyAFQ parameters, delete these files, and re-run accordingly.
+
+```{code-cell} ipython3
+pve = afm.PVEImages(
+ afm.ThresholdedScalarImage(
+ "t1_file",
+ upper_bound=0),
+ afm.ThresholdedScalarImage(
+ "t1_file",
+ upper_bound=180),
+ afm.ThresholdedScalarImage(
+ "t1_file",
+ lower_bound=180))
+```
+
+## Define GroupAFQ object
+Finally, we define the `GroupAFQ` object and export all the results.
+
+```{code-cell} ipython3
+my_afq = GroupAFQ(
+ bids_path=study_dir,
+ dwi_preproc_pipeline="qsiprep",
+ participant_labels=["NDARAA948VFH"],
+ output_dir=op.join(study_dir, "derivatives", "afq_otoc"),
+ pve=pve,
+ tracking_params=tractography_params,
+ segmentation_params=segmentation_params,
+ bundle_info=otoc_bd)
+
+my_afq.export_all()
+
+bundle_html = my_afq.export("all_bundles_figure")
+plotly.io.show(bundle_html["NDARAA948VFH"])
+```
+
+## References
+
+[^1]: Kruper, John, and Ariel Rokem. "Automatic fast and reliable
+ recognition of a small brain white matter bundle." International
+ Workshop on Computational Diffusion MRI. Cham: Springer Nature
+ Switzerland, 2023.
+
+[^2]: 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.
+
+[^3]: 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.
diff --git a/examples/howto_examples/plot_stages_of_tractometry.py b/docs/source/examples/howto_examples/plot_stages_of_tractometry.md
similarity index 58%
rename from examples/howto_examples/plot_stages_of_tractometry.py
rename to docs/source/examples/howto_examples/plot_stages_of_tractometry.md
index 3058befbe..f2635d0f9 100644
--- a/examples/howto_examples/plot_stages_of_tractometry.py
+++ b/docs/source/examples/howto_examples/plot_stages_of_tractometry.md
@@ -1,7 +1,19 @@
-"""
-=============================================================
-Understanding the different stages of tractometry with videos
-=============================================================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Understanding the different stages of tractometry with videos
Two-dimensional figures of anatomical data are somewhat limited, because of the
complex three-dimensional configuration of the brain. Therefored, dynamic
@@ -13,13 +25,11 @@
videos of each stage of the process using the Python Image Library (PIL, also
known as pillow).
-"""
++++
-##############################################################################
-# Imports
-# -------
-#
+## Imports
+```{code-cell} ipython3
import os.path as op
import nibabel as nib
@@ -40,36 +50,35 @@
import AFQ.data.fetch as afd
from AFQ.viz.utils import gen_color_dict
-from AFQ._fixes import make_gif
+from AFQ._fixes import make_mp4
+```
+## Get data from HBN POD2
+We get the same data that is used in the visualization tutorials.
-###############################################################################
-# Get data from HBN POD2
-# ----------------------------
-# We get the same data that is used in the visualization tutorials.
-
+```{code-cell} ipython3
afd.fetch_hbn_preproc(["NDARAA948VFH"])
study_path = afd.fetch_hbn_afq(["NDARAA948VFH"])[1]
-
-#############################################################################
-# Visualize the processed dMRI data
-# ---------------------------------
-# The HBN POD2 dataset was processed using the ``qsiprep`` pipeline. The
-# results from this processing are stored within a sub-folder of the
-# derivatives folder within the study folder.
-# Here, we will start by visualizing the diffusion data. We read in the
-# diffusion data, as well as the gradient table, using the `nibabel` library.
-# We then extract the b0, b1000, and b2000 volumes from the diffusion data.
-# We will use the `actor.data_slicer` function from `fury` to visualize these. This
-# function takes a 3D volume as input and returns a `slicer` actor, which can
-# then be added to a `window.Scene` object. We create a helper function that
-# will create a slicer actor for a given volume and a given slice along the x,
-# y, or z dimension. We then call this function three times, once for each of
-# the b0, b1000, and b2000 volumes, and add the resulting slicer actors to a
-# scene. We set the camera on the scene to a view that we like, and then we
-# record the scene into png files and subsequently gif animations. We do this
-# for each of the three volumes.
-
+```
+
+## Visualize the processed dMRI data
+The HBN POD2 dataset was processed using the `qsiprep` pipeline. The
+results from this processing are stored within a sub-folder of the
+derivatives folder within the study folder.
+Here, we will start by visualizing the diffusion data. We read in the
+diffusion data, as well as the gradient table, using the `nibabel` library.
+We then extract the b0, b1000, and b2000 volumes from the diffusion data.
+We will use the `actor.data_slicer` function from `fury` to visualize these. This
+function takes a 3D volume as input and returns a `slicer` actor, which can
+then be added to a `window.Scene` object. We create a helper function that
+will create a slicer actor for a given volume and a given slice along the x,
+y, or z dimension. We then call this function three times, once for each of
+the b0, b1000, and b2000 volumes, and add the resulting slicer actors to a
+scene. We set the camera on the scene to a view that we like, and then we
+record the scene into png files and subsequently mp4 files. We do this
+for each of the three volumes.
+
+```{code-cell} ipython3
deriv_path = op.join(
study_path, "derivatives")
@@ -128,30 +137,47 @@ def slice_volume(data, x=None, y=None, z=None):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
window.update_camera(show_m.screens[0].camera, None, slicer)
show_m.screens[0].controller.rotate((0, radians(-90)), None)
- make_gif(show_m, f'b{bval}.gif')
-
-#############################################################################
-# Visualizing whole-brain tractography
-# ------------------------------------
-# One of the first steps of the pyAFQ pipeline is to generate whole-brain
-# tractography. We will visualize the results of this step. We start by reading
-# in the FA image, which is used as a reference for the tractography. We then
-# load the whole brain tractography, and transform the coordinates of the
-# streamlines into the coordinate frame of the T1-weighted data.
-#
-# If you are interested in learning more about the different steps of the
-# tractometry pipeline, you can reference DIPY examples. Here are some
-# relevant links:
-#
-# For an example of fitting FA, see:
-# https://docs.dipy.org/1.11.0/examples_built/reconstruction/reconst_dti.html
-# For an example of running tractography, see:
-# https://docs.dipy.org/1.11.0/examples_built/fiber_tracking/tracking_probabilistic.html
-
+ make_mp4(show_m, f'b{bval}.mp4', n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from AFQ.utils.docs import embed_video, embed_image
+
+embed_video("b0.mp4")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("b1000.mp4")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("b2000.mp4")
+```
+
+## Visualizing whole-brain tractography
+One of the first steps of the pyAFQ pipeline is to generate whole-brain
+tractography. We will visualize the results of this step. We start by reading
+in the FA image, which is used as a reference for the tractography. We then
+load the whole brain tractography, and transform the coordinates of the
+streamlines into the coordinate frame of the T1-weighted data.
+
+If you are interested in learning more about the different steps of the
+tractometry pipeline, you can reference DIPY examples. Here are some
+relevant links:
+
+For an example of fitting FA, see:
+https://docs.dipy.org/1.11.0/examples_built/reconstruction/reconst_dti.html
+For an example of running tractography, see:
+https://docs.dipy.org/1.11.0/examples_built/fiber_tracking/tracking_probabilistic.html
+
+```{code-cell} ipython3
afq_path = op.join(
deriv_path,
'afq',
@@ -173,16 +199,15 @@ def slice_volume(data, x=None, y=None, z=None):
whole_brain_t1w = transform_streamlines(
sft_whole_brain.streamlines,
np.linalg.inv(t1w_img.affine))
+```
-#############################################################################
-# Visualize the streamlines
-# -------------------------
-# The streamlines are visualized in the context of the T1-weighted data.
-#
+## Visualize the streamlines
+The streamlines are visualized in the context of the T1-weighted data.
+```{code-cell} ipython3
-whole_brain_actor = actor.streamlines(whole_brain_t1w, thickness=2)
+whole_brain_actor = actor.streamlines(whole_brain_t1w, thickness=0.5)
slicer = slice_volume(t1w, y=t1w.shape[1] // 2 - 5, z=t1w.shape[-1] // 3)
def rotate_to_anterior(show_m):
@@ -198,24 +223,29 @@ def rotate_to_anterior(show_m):
scene.background = (1, 1, 1)
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "whole_brain.gif")
-
-#############################################################################
-# Whole brain with waypoints
-# --------------------------------------
-# We can also generate a gif video with the whole brain tractography and the
-# waypoints that are used to define the bundles. We will use the same scene as
-# before, but we will add the waypoints as contours to the scene.
-#
-# To get these waypoints in subject space, we had to register to MNI.
-# Once again, there is a helpful DIPY example for details:
-# https://docs.dipy.org/1.11.0/examples_built/registration/syn_registration_3d.html
+make_mp4(show_m, "whole_brain.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("whole_brain.mp4")
+```
+
+## Whole brain with waypoints
+We can also generate a mp4 video with the whole brain tractography and the
+waypoints that are used to define the bundles. We will use the same scene as
+before, but we will add the waypoints as contours to the scene.
+To get these waypoints in subject space, we had to register to MNI.
+Once again, there is a helpful DIPY example for details:
+https://docs.dipy.org/1.11.0/examples_built/registration/syn_registration_3d.html
+
+```{code-cell} ipython3
scene.clear()
-whole_brain_actor = actor.streamlines(whole_brain_t1w, thickness=2)
+whole_brain_actor = actor.streamlines(whole_brain_t1w, thickness=0.5)
scene.add(whole_brain_actor)
scene.add(slicer)
@@ -252,23 +282,30 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "whole_brain_with_waypoints.gif")
+make_mp4(show_m, "whole_brain_with_waypoints.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
bundle_path = op.join(afq_path,
'bundles')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("whole_brain_with_waypoints.mp4")
+```
+
+## Define the bundles
-############################################
-# Define the bundles
-# The bundles are defined by the waypoints that we just visualized. Here
-# we organize some names of bundles we want to visualize.
-# In current pyAFQ, only the formal names are used. But for this example,
-# we will use derivatives from previous versions of pyAFQ, where names
-# were abbreviated. We have standardized colors for each bundle,
-# provided by `gen_color_dict`, which we will use for visualization.
+The bundles are defined by the waypoints that we just visualized. Here
+we organize some names of bundles we want to visualize.
+In current pyAFQ, only the formal names are used. But for this example,
+we will use derivatives from previous versions of pyAFQ, where names
+were abbreviated. We have standardized colors for each bundle,
+provided by `gen_color_dict`, which we will use for visualization.
+```{code-cell} ipython3
bundles = [
"ARC_R",
"ATR_R",
@@ -318,13 +355,12 @@ def rotate_to_anterior(show_m):
]
color_dict = gen_color_dict(formal_bundles)
+```
-#############################################################################
-# Visualize the arcuate bundle
-# ----------------------------
-# Now visualize only the arcuate bundle that is selected with these waypoints.
-#
+## Visualize the arcuate bundle
+Now visualize only the arcuate bundle that is selected with these waypoints.
+```{code-cell} ipython3
fa_img = nib.load(op.join(afq_path,
'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_model-DKI_FA.nii.gz'))
fa = fa_img.get_fdata()
@@ -335,7 +371,7 @@ def rotate_to_anterior(show_m):
arc_t1w = transform_streamlines(sft_arc.streamlines,
np.linalg.inv(t1w_img.affine))
-arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_dict['Left Arcuate'])
+arc_actor = actor.streamlines(arc_t1w, thickness=0.5, colors=color_dict['Left Arcuate'])
scene.clear()
scene.add(arc_actor)
@@ -346,17 +382,22 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "arc1.gif")
+make_mp4(show_m, "arc1.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
-#############################################################################
-# Clean bundle
-# ------------
-# The next step in processing would be to clean the bundle by removing
-# streamlines that are outliers. We will visualize the cleaned bundle.
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("arc1.mp4")
+```
+## Clean bundle
+The next step in processing would be to clean the bundle by removing
+streamlines that are outliers. We will visualize the cleaned bundle.
+
+```{code-cell} ipython3
scene.clear()
scene.add(arc_actor)
@@ -364,10 +405,10 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "arc2.gif")
+make_mp4(show_m, "arc2.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
clean_bundles_path = op.join(afq_path,
'clean_bundles')
@@ -379,7 +420,7 @@ def rotate_to_anterior(show_m):
arc_t1w = transform_streamlines(sft_arc.streamlines,
np.linalg.inv(t1w_img.affine))
-arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=tab20.colors[18])
+arc_actor = actor.streamlines(arc_t1w, thickness=0.5, colors=tab20.colors[18])
scene.clear()
scene.add(arc_actor)
@@ -387,30 +428,41 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "arc3.gif")
-
-#############################################################################
-# Show the values of tissue properties along the bundle
-# ------------------------------------------------------
-# We can also visualize the values of tissue properties along the bundle. Here
-# we will visualize the fractional anisotropy (FA) along the arcuate bundle.
-# This is done by using a colormap to color the streamlines according to the
-# values of the tissue property, with `fury.colormap.create_colormap`.
-#
-# There is a DIPY example with more details here:
-# https://docs.dipy.org/1.11.0/examples_built/streamline_analysis/afq_tract_profiles.html
+make_mp4(show_m, "arc3.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("arc2.mp4")
+```
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("arc3.mp4")
+```
+
+## Show the values of tissue properties along the bundle
+We can also visualize the values of tissue properties along the bundle. Here
+we will visualize the fractional anisotropy (FA) along the arcuate bundle.
+This is done by using a colormap to color the streamlines according to the
+values of the tissue property, with `fury.colormap.create_colormap`.
+
+There is a DIPY example with more details here:
+https://docs.dipy.org/1.11.0/examples_built/streamline_analysis/afq_tract_profiles.html
+
+```{code-cell} ipython3
scene.clear()
fa_in_t1 = resample(fa_img, t1w_img).get_fdata()
fa_profiles = values_from_volume(fa_in_t1, arc_t1w, np.eye(4))
for ii in range(len(arc_t1w)):
- colors = create_colormap(np.asarray(fa_profiles[ii]), name="blues", auto=False)
+ colors = create_colormap(np.asarray(fa_profiles[ii]), name="viridis")
arc_actor = actor.streamlines(
- arc_t1w[ii], thickness=8,
+ arc_t1w[ii], thickness=2,
+ opacity=0.5,
colors=colors)
scene.add(arc_actor)
@@ -418,18 +470,23 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "arc4.gif")
+make_mp4(show_m, "arc4.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
-#############################################################################
-# Core of the bundle and tract profile
-# -------------------------------------
-# Finally, we can visualize the core of the bundle and the tract profile. The
-# core of the bundle is the median of the streamlines, and the tract profile is
-# the values of the tissue property along the core of the bundle.
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("arc4.mp4")
+```
+## Core of the bundle and tract profile
+Finally, we can visualize the core of the bundle and the tract profile. The
+core of the bundle is the median of the streamlines, and the tract profile is
+the values of the tissue property along the core of the bundle.
+
+```{code-cell} ipython3
core_arc = np.median(np.asarray(set_number_of_points(arc_t1w, 20)), axis=0)
sft_arc.to_vox()
@@ -438,33 +495,38 @@ def rotate_to_anterior(show_m):
core_arc_actor = actor.streamlines(
[core_arc],
- thickness=40,
+ thickness=5,
colors=create_colormap(arc_profile, name='viridis')
)
arc_actor = actor.streamlines(
arc_t1w,
- thickness=1,
- opacity=0.2) # better to visualize the core
+ thickness=0.5,
+ opacity=0.05) # better to visualize the core
scene.clear()
scene.add(slicer)
-scene.add(arc_actor)
scene.add(core_arc_actor)
+scene.add(arc_actor)
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "arc5.gif")
+make_mp4(show_m, "arc5.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
-#############################################################################
-# Core of all bundles and their tract profiles
-# --------------------------------------------
-# Same as before, but for all bundles.
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("arc5.mp4")
+```
+## Core of all bundles and their tract profiles
+Same as before, but for all bundles.
+
+```{code-cell} ipython3
scene.clear()
scene.add(slicer)
@@ -478,17 +540,17 @@ def rotate_to_anterior(show_m):
bundle_actor = actor.streamlines(
bundle_t1w,
- thickness=8,
+ thickness=0.5,
colors=color_dict[formal_bundles[ii]]
)
scene.add(bundle_actor)
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0
)
rotate_to_anterior(show_m)
-make_gif(show_m, "all_bundles.gif")
+make_mp4(show_m, "all_bundles.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
scene.clear()
@@ -511,7 +573,7 @@ def rotate_to_anterior(show_m):
core_actor = actor.streamlines(
[core_bundle],
- thickness=40,
+ thickness=5,
colors=create_colormap(tract_profiles[-1], name='viridis')
)
@@ -519,21 +581,30 @@ def rotate_to_anterior(show_m):
show_m = window.ShowManager(
scene=scene, window_type="offscreen",
- size=(2400, 2400)
+ size=(600, 600), pixel_ratio=2.0,
)
rotate_to_anterior(show_m)
-make_gif(show_m, "all_tract_profiles.gif")
-
-
-#############################################################################
-# Tract profiles as a table
-# -------------------------
-# Finally, we can visualize the tract profiles as a table. This is done by
-# plotting the tract profiles for each bundle as a line plot, with the x-axis
-# representing the position along the bundle, and the y-axis representing the
-# value of the tissue property. We will use the `matplotlib` library to create
-# this plot.
-
+make_mp4(show_m, "all_tract_profiles.mp4", n_frames=90, fps=15, az_ang=-2, crf=28, verbose=False)
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("all_bundles.mp4")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_video("all_tract_profiles.mp4")
+```
+
+## Tract profiles as a table
+Finally, we can visualize the tract profiles as a table. This is done by
+plotting the tract profiles for each bundle as a line plot, with the x-axis
+representing the position along the bundle, and the y-axis representing the
+value of the tissue property. We will use the `matplotlib` library to create
+this plot.
+
+```{code-cell} ipython3
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
for ii, bundle in enumerate(bundles):
@@ -546,3 +617,10 @@ def rotate_to_anterior(show_m):
fig.set_size_inches(10, 5)
plt.subplots_adjust(bottom=0.2)
fig.savefig("tract_profiles_table.png")
+print("Done!")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image("tract_profiles_table.png")
+```
diff --git a/docs/source/examples/howto_examples/pyAFQ_with_GPU.md b/docs/source/examples/howto_examples/pyAFQ_with_GPU.md
new file mode 100644
index 000000000..8d547d5c0
--- /dev/null
+++ b/docs/source/examples/howto_examples/pyAFQ_with_GPU.md
@@ -0,0 +1,74 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Running pyAFQ using the GPU for tractography
+Running pyAFQ using the GPU for tractography is as simple as
+(1) Installing GPUStreamlines using `pip install` and
+(2) passing in the `jit_backend` parameter when you create your
+ GroupAFQ object.
+To install GPUStreamlines, do:
+ `pip install git+https://github.com/dipy/GPUStreamlines.git`
+That's step 1 complete! The rest of this example is the same as the GroupAFQ
+example except with the `jit_backend` parameter set.
+
+```{code-cell} ipython3
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+import os.path as op
+import plotly
+```
+
+We start with some example data. The data we will use here is
+generated from the
+[Stanford HARDI dataset](https://purl.stanford.edu/ng782rw8378).
+We then setup our myafq object which we will use to demonstrate
+the clobber method.
+
+```{code-cell} ipython3
+afd.organize_stanford_data()
+```
+
+## Set tractography parameters
+We make create a `tracking_params` variable to define the parameters for tractography.
+The only parameter we need to set to use the GPU is `jit_backend`,
+which we set to "cuda". Other backends include: "metal", "webgpu", or "numba".
+Numba is the default.
+Note that the GPU backend will only run for probabilistic tracking,
+which is the default.
+
+```{code-cell} ipython3
+tracking_params = dict(n_seeds=1e7,
+ random_seeds=True,
+ rng_seed=2025,
+ jit_backend="cuda",
+ trx=True)
+```
+
+## Running with the GPU
+Then, run pyAFQ normally.
+That's it!
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'stanford_hardi'),
+ dwi_preproc_pipeline='vistasoft',
+ t1_preproc_pipeline='freesurfer',
+ tracking_params=tracking_params)
+
+bundle_html = myafq.export("all_bundles_figure")
+plotly.io.show(bundle_html["01"][0])
+```
diff --git a/examples/howto_examples/pyafq_2.py b/docs/source/examples/howto_examples/pyafq_2.md
similarity index 73%
rename from examples/howto_examples/pyafq_2.py
rename to docs/source/examples/howto_examples/pyafq_2.md
index 06358b21f..62320e3c7 100644
--- a/examples/howto_examples/pyafq_2.py
+++ b/docs/source/examples/howto_examples/pyafq_2.md
@@ -1,10 +1,24 @@
-"""
-.. _pyafq-2-settings:
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
-======================================
-Running pyAFQ 2.x defaults in pyAFQ 3.x
-======================================
-"""
+(pyafq-2-settings)=
+# Running pyAFQ 2.x defaults in pyAFQ 3.x
+
+```{code-cell} ipython3
from AFQ.api.group import GroupAFQ
import AFQ.data.fetch as afd
import AFQ.definitions.image as afm
@@ -13,14 +27,14 @@
import os.path as op
afd.organize_stanford_data()
+```
-################################################################
-# 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.
+## 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.
+```{code-cell} ipython3
tracking_params = dict(
odf_model="csd",
n_seeds=1,
@@ -30,18 +44,18 @@
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.
+## 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.
+```{code-cell} ipython3
pve = afm.PVEImages(
afm.ThresholdedScalarImage(
"dti_fa",
@@ -56,26 +70,26 @@
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.
+## 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.
+```{code-cell} ipython3
templates = afd.read_templates(as_img=False)
old_vof_paf_cst_slf_definitions = abd.BundleDict({
'Left Corticospinal': {
@@ -154,13 +168,13 @@
'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 bundles in the old way
+In pyAFQ 2.x, the callosal bundles were cleaned using mahalnobis
+instead of isolation forest.
+```{code-cell} ipython3
callosal_templates =\
afd.read_callosum_templates(as_img=False)
callosal_bd = abd.BundleDict({
@@ -225,18 +239,18 @@
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.
+## 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.
+```{code-cell} ipython3
myafq = GroupAFQ(
bids_path=op.join(afd.afq_home, 'stanford_hardi'),
dwi_preproc_pipeline='vistasoft',
@@ -247,3 +261,4 @@
bundle_info=bundle_info)
myafq.export_all()
+```
diff --git a/docs/source/examples/howto_examples/recobundles.md b/docs/source/examples/howto_examples/recobundles.md
new file mode 100644
index 000000000..b47465dff
--- /dev/null
+++ b/docs/source/examples/howto_examples/recobundles.md
@@ -0,0 +1,71 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Using RecoBundles for bundle recognition
+
+For bundle recognition, pyAFQ defaults to use the waypoint ROI approach
+described in [^Yeatman2012]. However, as an alternative approach, pyAFQ also
+supports using the RecoBundles algorithm [^Garyfallidis2017], which uses an
+atlas of bundles in streamlines. This example shows how to
+use RecoBundles for bundle recognition.
+
+The code closely resembles the code used in the GroupAFQ tutorial.
+
+```{code-cell} ipython3
+import os.path as op
+import AFQ.data.fetch as afd
+from AFQ.api.group import GroupAFQ
+import AFQ.api.bundle_dict as abd
+
+afd.organize_stanford_data(clear_previous_afq="track")
+
+tracking_params = dict(n_seeds=25000,
+ random_seeds=True,
+ rng_seed=42)
+```
+
+## Defining the segmentation params
+We also refer to bundle recognition as the "segmentation" of the tractogram.
+Parameters of this process are set through a dictionary input to the
+`segmentation_params` argument of the GroupAFQ object. In this case, we
+use `abd.reco_bd(16)`, which tells pyAFQ to use the RecoBundles
+algorithm for bundle recognition. This uses 16 bundles, there is also
+an atlas `abd.reco_bd(80)` which uses 80 bundles.
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ output_dir=op.join(afd.afq_home, 'stanford_hardi', 'derivatives',
+ 'recobundles'),
+ bids_path=op.join(afd.afq_home, 'stanford_hardi'),
+ # Set the algorithm to use RecoBundles for bundle recognition:
+ bundle_info=abd.reco_bd(16),
+ dwi_preproc_pipeline='vistasoft',
+ t1_preproc_pipeline='freesurfer',
+ tracking_params=tracking_params,
+ viz_backend_spec='plotly_no_mp4')
+
+fig_files = myafq.export_all()
+```
+
+## References
+
+[^Garyfallidis2017]: Garyfallidis, Eleftherios, Marc-Alexandre Côté,
+ Francois Rheault, Jasmeen Sidhu, Janice Hau, Laurent
+ Petit, David Fortin, Stephen Cunanne, and Maxime
+ Descoteaux. 2017. “Recognition of White Matter Bundles
+ Using Local and Global Streamline-Based Registration and
+ Clustering.” NeuroImage 170: 283-295.
diff --git a/docs/source/examples/howto_examples/use_subject_space_rois_from_freesurfer.md b/docs/source/examples/howto_examples/use_subject_space_rois_from_freesurfer.md
new file mode 100644
index 000000000..ee6511d30
--- /dev/null
+++ b/docs/source/examples/howto_examples/use_subject_space_rois_from_freesurfer.md
@@ -0,0 +1,163 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Using Subject Space ROIs from Freesurfer
+
+An example using the AFQ API to find bundles
+as defined by endpoint ROIs from freesurfer.
+This example can be modified to work with ROIs
+in subject space from pipelines other than freesurfer.
+
+```{code-cell} ipython3
+import os.path as op
+
+import nibabel as nib
+import plotly
+import numpy as np
+
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+from AFQ.definitions.image import RoiImage
+import AFQ.api.bundle_dict as abd
+```
+
+## Get some example data
+
+Retrieves High angular resolution diffusion imaging (HARDI) dataset from
+Stanford's Vista Lab
+
+See https://purl.stanford.edu/ng782rw8378 for details on dataset.
+
+The data for the first subject and first session are downloaded locally
+(by default into the users home directory) under:
+
+`.dipy/stanford_hardi/`
+
+Anatomical data (`anat`) and Diffusion-weighted imaging data (`dwi`) are
+then extracted, formatted to be BIDS compliant, and placed in the AFQ
+data directory (by default in the users home directory) under:
+
+`AFQ_data/stanford_hardi/`
+
+This data represents the required preprocessed diffusion data necessary for
+initializing the GroupAFQ object (which we will do next)
+
+The clear_previous_afq is used to remove any previous runs of the afq object
+stored in the AFQ_data/stanford_hardi/ BIDS directory. Set it to None if
+you want to use the results of previous runs. Setting it to "track"
+as here will only clear derivatives that depend on the tractography stage
+(i.e., bundle delination and tract profile calculation),
+as well as the tractography itself, to save time on recomputation.
+If you want to only clear derivatives that depend on bundle delineation,
+and keep the tractography, you can set clear_previous_afq to
+"recog" instead.
+
+```{code-cell} ipython3
+afd.organize_stanford_data(clear_previous_afq="track")
+```
+
+## Generate left thalamus ROI from freesurfer segmentation file
+1. Load the segmentation file that was generated by Freesurfer for
+ the specific subject.
+2. Identify the left thalamus within the file, which has the label
+ number 41
+3. Create a Nifti image representing the left thalamus ROI:
+ - Assign a value of 1 to the voxels that Freesurfer
+ has labeled as 41 (i.e., the left thalamus).
+ - Assign a value of 0 to all other voxels.
+This binary mask format is the expected input for pyAFQ when
+dealing with subject space ROIs. If it's already in binary format,
+there is no need to do this step.
+
+```{code-cell} ipython3
+freesurfer_subject_folder = op.join(
+ afd.afq_home, "stanford_hardi",
+ "derivatives", "freesurfer",
+ "sub-01", "ses-01",
+ "anat")
+
+seg_file = nib.load(op.join(
+ freesurfer_subject_folder, "sub-01_ses-01_seg.nii.gz"))
+left_thal = seg_file.get_fdata() == 41
+nib.save(
+ nib.Nifti1Image(
+ left_thal.astype(np.float32),
+ seg_file.affine),
+ op.join(
+ freesurfer_subject_folder,
+ "sub-01_ses-01_desc-leftThal_mask.nii.gz"))
+
+# Fetch LV1 ROI
+# which was already generated using the process above
+afd.fetch_stanford_hardi_lv1()
+```
+
+## Set tractography parameters (optional)
+We make this tracking_params which we will pass to the GroupAFQ object
+which specifies that we want 10,000 seeds randomly distributed
+only within the endpoint ROIs and not throughout the white matter.
+This is controlled by passing
+`"seed_mask": RoiImage()` in the `tracking_params` dict.
+
+We only do this to make this example faster and consume less space.
+
+```{code-cell} ipython3
+tracking_params = dict(n_seeds=10000,
+ random_seeds=True,
+ rng_seed=42,
+ seed_mask=RoiImage(use_endpoints=True))
+```
+
+## Define custom `BundleDict` object
+In a typical `BundleDict` object, ROIs are passed as paths to Nifti files.
+Here, we define ROIs as dictionaries instead, containing BIDS filters.
+Then pyAFQ can find the respective ROI for each subject and session.
+
+```{code-cell} ipython3
+bundles = abd.BundleDict({
+ "L_OR": {
+ "start": {
+ "scope": "freesurfer",
+ "suffix": "mask",
+ "desc": "leftThal"},
+ "end": {
+ "scope": "freesurfer",
+ "suffix": "anat",
+ "desc": "LV1"
+ },
+ "cross_midline": False,
+ "space": "subject"
+ }})
+```
+
+## Initialize a GroupAFQ object:
+
+Creates a GroupAFQ object, that encapsulates tractometry,
+passing in our custom bundle info. Then we run the pipeline
+and generate a visualization of the bundle we found.
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'stanford_hardi'),
+ dwi_preproc_pipeline='vistasoft',
+ t1_preproc_pipeline='freesurfer',
+ tracking_params=tracking_params,
+ bundle_info=bundles)
+
+bundle_html = myafq.export("indiv_bundles_figures")
+plotly.io.show(bundle_html["01"]["L_OR"])
+```
diff --git a/examples/howto_examples/vof_example.py b/docs/source/examples/howto_examples/vof_example.md
similarity index 78%
rename from examples/howto_examples/vof_example.py
rename to docs/source/examples/howto_examples/vof_example.md
index 32ef4e1b0..ec26bf744 100644
--- a/examples/howto_examples/vof_example.py
+++ b/docs/source/examples/howto_examples/vof_example.md
@@ -1,7 +1,21 @@
-"""
-====================================
-How to segment out only some bundles
-====================================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How to segment out only some bundles
The pyAFQ software can be configured to find all of its default set of white
matter pathways, or bundles. Alternatively, it can be configured to find only
@@ -9,8 +23,7 @@
bundles that you are interested in, though note that ARC, pARC and VOF are all
also part of the set of bundles that are segmented per default.
-"""
-
+```{code-cell} ipython3
import AFQ.api.bundle_dict as abd
import AFQ.data.fetch as afd
from AFQ.api.group import GroupAFQ
@@ -27,7 +40,6 @@
"Left Vertical Occipital", "Right Vertical Occipital"]
bundle_dict = abd.default_bd()[bundle_names]
-
myafq = GroupAFQ(
op.join(afd.afq_home, 'stanford_hardi'),
bundle_info=bundle_dict,
@@ -39,12 +51,17 @@
"seed_mask": RoiImage(use_waypoints=True, use_endpoints=True),
}
)
+```
+```{code-cell} ipython3
for b_name in bundle_names:
b_len = len(aus.SegmentedSFT.fromfile(myafq.export("bundles")[
"01"]).get_bundle(b_name))
if b_len < 1:
raise ValueError(f"{b_name} not found")
+```
+```{code-cell} ipython3
myafq.export("all_bundles_figure")["01"][0]
+```
diff --git a/examples/tutorial_examples/README.rst b/docs/source/examples/tutorial_examples/index.rst
similarity index 91%
rename from examples/tutorial_examples/README.rst
rename to docs/source/examples/tutorial_examples/index.rst
index 2eafbe353..2f8d47c03 100644
--- a/examples/tutorial_examples/README.rst
+++ b/docs/source/examples/tutorial_examples/index.rst
@@ -10,4 +10,7 @@ tutorial, and will save results as it goes along.
.. toctree::
- :maxdepth: 2
\ No newline at end of file
+ :maxdepth: 2
+ :glob:
+
+ *
diff --git a/docs/source/examples/tutorial_examples/plot_001_group_afq_api.md b/docs/source/examples/tutorial_examples/plot_001_group_afq_api.md
new file mode 100644
index 000000000..3c4eab878
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_001_group_afq_api.md
@@ -0,0 +1,349 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Getting started with pyAFQ - GroupAFQ
+
+There are two ways to [use pyAFQ](/tutorials/index): through the
+command line interface, and by writing Python code. This tutorial will walk you
+through the basics of the latter, using pyAFQ's Python Application Programming
+Interface (API).
+
+```{code-cell} ipython3
+import os.path as op
+
+import matplotlib.pyplot as plt
+import nibabel as nib
+import plotly
+import pandas as pd
+
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+import AFQ.viz.altair as ava
+import AFQ.definitions.image as afm
+```
+
+## Example data
+
+pyAFQ can be called using GroupAFQ to handle data
+organized in a BIDS compliant directory.
+If this is not the case, refer to the Participant AFQ example.
+To get users started with this tutorial, we will download some example
+data and organize it in a BIDS compliant way (for more details on how
+BIDS is used in pyAFQ, refer to [plot_006_bids_layout](plot_006_bids_layout)).
+
+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.
+
+The clear_previous_afq is used to remove any previous runs of the afq object
+stored in the `~/AFQ_data/HBN/` BIDS directory. Set it to None if
+you want to use the results of previous runs.
+
+```{code-cell} ipython3
+bids_path = afd.fetch_hbn_preproc(
+ ["NDARAA948VFH"],
+ clear_previous_afq="all")[1]
+```
+
+## Set tractography parameters (optional)
+We make create a `tracking_params` variable, which we will pass to the
+GroupAFQ 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.
+
+```{code-cell} ipython3
+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.
+
+```{code-cell} ipython3
+pve = afm.PVEImages(
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "CSF"}),
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "GM"}),
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "WM"}))
+```
+
+## 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.
+
+```{code-cell} ipython3
+brain_mask_definition = afm.ImageFile(
+ suffix="mask", filters={"desc": "brain", "scope": "qsiprep"})
+```
+
+## Initialize a GroupAFQ object:
+
+Creates a GroupAFQ object, that encapsulates tractometry. This object can be
+used to manage the entire [tractometry pipeline](/explanations/index), including:
+
+- Tractography
+- Registration
+- Segmentation
+- Cleaning
+- Profiling
+- Visualization
+
+This will also create an output folder for the corresponding AFQ derivatives
+in the AFQ data directory: `AFQ_data/HBN/derivatives/afq/`
+
+To initialize this object we will pass in the path location to our BIDS
+compliant data, the name of the preprocessing pipeline we want to use,
+the name of the t1 preprocessing pipeline we want to use (in this case,
+its the same, qsiprep [^3]), the participant labels we want to process
+(in this case, just a single subject), the PVE images we defined above, and
+the tracking parameters we defined above.
+
+```{code-cell} ipython3
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'HBN'),
+ dwi_preproc_pipeline='qsiprep',
+ t1_preproc_pipeline='qsiprep',
+ participant_labels=['NDARAA948VFH'],
+ pve=pve,
+ brain_mask_definition=brain_mask_definition,
+ tracking_params=tracking_params)
+```
+
+## Calculating DTI FA (Diffusion Tensor Imaging Fractional Anisotropy)
+
+The GroupAFQ 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 for all subjects in the dataset, 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 a dictionary, with the subject
+IDs as keys, and the filenames of the corresponding files as values.
+This means that to extract the filename corresponding to the FA of the first
+subject, we can do:
+
+```{code-cell} ipython3
+FA_fname = myafq.export("dti_fa", collapse=False)["NDARAA948VFH"]["HBNsiteRU"]
+
+# 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.
+:::
+
+```{code-cell} ipython3
+fig, ax = plt.subplots(1)
+ax.matshow(FA[:, :, FA.shape[-1] // 2], cmap='viridis')
+ax.axis("off")
+plt.savefig("FA.png")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from AFQ.utils.docs import embed_html, embed_image
+
+embed_image("FA.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).
+:::
+
+```{code-cell} ipython3
+myafq.export('profiles')
+```
+
+## 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 allow you to pan, zoom, and rotate.
+:::
+
+```{code-cell} ipython3
+:tags: [remove-execution]
+bundle_html = myafq.export("all_bundles_figure", collapse=False)
+plotly.io.show(bundle_html["NDARAA948VFH"]["HBNsiteRU"][0])
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+bundle_html = myafq.export("all_bundles_figure", collapse=False)
+embed_html(bundle_html["NDARAA948VFH"]["HBNsiteRU"][1])
+```
+
+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.
+
+```{code-cell} ipython3
+fig_files = myafq.export("tract_profile_plots", collapse=False)[
+ "NDARAA948VFH"]["HBNsiteRU"]
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(fig_files[0] + ".png")
+```
+
+We can even use altair to visualize the tract profiles in all
+of the bundles. We provide a more customizable interface for visualizing
+the tract profiles using altair.
+Again, to make this plot, it is required that you install with
+`pip install pyAFQ[plot]` so that you have the necessary dependencies.
+
+```{code-cell} ipython3
+profiles_df = myafq.combine_profiles()
+altair_df = ava.combined_profiles_df_to_altair_df(
+ profiles_df,
+ tissue_properties=[
+ "dti_fa",
+ "dti_md",
+ "t1w_over_b0",
+ "msdki_msd",
+ "msdki_msk"])
+altair_chart = ava.altair_df_to_chart(altair_df)
+altair_chart.save("profiles_chart.html")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_html("profiles_chart.html")
+```
+
+## 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.
+
+```{code-cell} ipython3
+myafq.export("citations")
+```
+
+We can check the number of streamlines per bundle, to make sure
+every bundle is found with a reasonable amount of streamlines.
+
+```{code-cell} ipython3
+bundle_counts = pd.read_csv(
+ myafq.export("sl_counts", collapse=False)[
+ "NDARAA948VFH"]["HBNsiteRU"], index_col=[0])
+for ind in bundle_counts.index:
+ if ind == "Total Recognized":
+ threshold = 2500
+ else:
+ threshold = 20
+ if bundle_counts["n_streamlines"][ind] < threshold:
+ raise ValueError((
+ "Small number of streamlines found "
+ f"for bundle(s):\n{bundle_counts}"))
+```
+
+## Alternative way to initialize GroupAFQ when using QSIPrep data
+As a final note, if you are using QSIPrep preprocessed data,
+you can also initialize the GroupAFQ object using the
+`from_qsiprep` class method. This method will automatically set
+the appropriate BIDS filters to find the preprocessed DWI data.
+Additionally, it will find and use the brain masks and PVE images
+that QSIPrep generates. Outside of BIDS filters, the arguments
+are the same as those used when initializing the GroupAFQ object
+directly.
+
+```{code-cell} ipython3
+myafq = GroupAFQ.from_qsiprep(
+ qsi_dir=op.join(afd.afq_home, 'HBN'),
+ participant_labels=['NDARAA948VFH'],
+ tracking_params=tracking_params)
+```
+
+## 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.
diff --git a/docs/source/examples/tutorial_examples/plot_002_participant_afq_api.md b/docs/source/examples/tutorial_examples/plot_002_participant_afq_api.md
new file mode 100644
index 000000000..91340c140
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_002_participant_afq_api.md
@@ -0,0 +1,307 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Getting started with pyAFQ - ParticipantAFQ
+
+```{code-cell} ipython3
+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
+```
+
+## 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.
+
+```{code-cell} ipython3
+afd.fetch_hbn_preproc(["NDARAA948VFH"])
+```
+
+## 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 ([plot_001_group_afq_api](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.
+
+```{code-cell} ipython3
+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.
+
+```{code-cell} ipython3
+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.
+
+```{code-cell} ipython3
+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.
+
+```{code-cell} ipython3
+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 [tractometry pipeline](/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.
+
+```{code-cell} ipython3
+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.
+
+```{code-cell} ipython3
+FA_fname = myafq.export("dti_fa")
+```
+
+We will then use `nibabel` to load the deriviative file and retrieve the
+data array.
+
+```{code-cell} ipython3
+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.
+:::
+
+```{code-cell} ipython3
+fig, ax = plt.subplots(1)
+ax.matshow(FA[:, :, FA.shape[-1] // 2], cmap="viridis")
+ax.axis("off")
+plt.savefig("FA.png")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from AFQ.utils.docs import embed_html, embed_image
+
+embed_image("FA.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).
+:::
+
+```{code-cell} ipython3
+myafq.export("profiles")
+```
+
+## 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 allow you to pan, zoom, and rotate.
+:::
+
+```{code-cell} ipython3
+:tags: [remove-execution]
+bundle_html = myafq.export("all_bundles_figure")
+plotly.io.show(bundle_html[0])
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+bundle_html = myafq.export("all_bundles_figure")
+embed_html(bundle_html[1])
+```
+
+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.
+
+```{code-cell} ipython3
+fig_files = myafq.export("tract_profile_plots")
+```
+
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(fig_files[0] + ".png")
+```
+
+## 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.
+
+```{code-cell} ipython3
+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.
diff --git a/docs/source/examples/tutorial_examples/plot_003_rerun.md b/docs/source/examples/tutorial_examples/plot_003_rerun.md
new file mode 100644
index 000000000..eac69ceaa
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_003_rerun.md
@@ -0,0 +1,184 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# Re-running pyAFQ
+Sometimes you want to change arguments and re-run pyAFQ. This could be to
+try different parameters, to run on changed data, or re-run after updating
+parameters due to an error.
+
+pyAFQ saves derivatives as it goes, so if you re-run pyAFQ after changing
+parameters, it could use derivatives from previous runs with the
+old parameters.
+
+To solve this, use the `myafq.clobber()` or `myafq.cmd_outputs()` methods.
+These are equivalent methods that delete previous derivatives so you can
+re-run your pipeline.
+
+```{code-cell} ipython3
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+import AFQ.definitions.image as afm
+import os.path as op
+import os
+```
+
+We start with some example data generated from HBN, then setup our
+`myafq` object to demonstrate the clobber method.
+
+```{code-cell} ipython3
+afd.fetch_hbn_preproc(["NDARAA948VFH"])
+
+tracking_params = dict(n_seeds=100,
+ random_seeds=True,
+ rng_seed=2022,
+ trx=True)
+
+pve = afm.PVEImages(
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "CSF"}),
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "GM"}),
+ afm.ImageFile(
+ suffix="probseg", filters={"scope": "qsiprep", "label": "WM"}))
+
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'HBN'),
+ dwi_preproc_pipeline='qsiprep',
+ t1_preproc_pipeline='qsiprep',
+ participant_labels=['NDARAA948VFH'],
+ pve=pve,
+ tracking_params=tracking_params)
+```
+
+## Delete Everything
+
+To delete all pyAFQ outputs in the output directory, simply call:
+
+```python
+myafq.cmd_outputs()
+```
+
+or:
+
+```python
+myafq.clobber()
+```
+
+This is equivalent to running `rm -r` on all pyAFQ outputs. After this,
+you can re-run your pipeline from scratch.
+
+Here, we will delete everything and re-run with a different b0 threshold.
+The `b0_threshold` determines which b-values are considered b0.
+The default is 50.
+
+```{code-cell} ipython3
+myafq.cmd_outputs()
+
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'HBN'),
+ dwi_preproc_pipeline='qsiprep',
+ t1_preproc_pipeline='qsiprep',
+ participant_labels=['NDARAA948VFH'],
+ b0_threshold=100,
+ tracking_params=tracking_params,
+ pve=pve)
+
+myafq.export("b0")
+```
+
+## Delete Some Things
+
+To delete only specific types of derivatives while preserving others,
+use the `dependent_on` parameter:
+
+```python
+# Delete only tractography-dependent files
+myafq.cmd_outputs(dependent_on="track")
+
+# Delete only bundle recognition-dependent files
+myafq.cmd_outputs(dependent_on="recog")
+
+# Delete only profiling-dependent files
+myafq.cmd_outputs(dependent_on="prof")
+```
+
+You can also specify exceptions — files to preserve:
+
+```python
+# Delete all outputs except the tractography
+myafq.cmd_outputs(exceptions=["streamlines"])
+```
+
+Here, we will change the tractography parameters, but keep all derivatives
+not dependent on tractography. Typically this means keeping the mapping from
+MNI space and fitted models, but deleting recognized bundles and tract profiles.
+
+```{code-cell} ipython3
+myafq.clobber(dependent_on="track")
+
+tracking_params = dict(n_seeds=100,
+ random_seeds=True,
+ max_angle=60,
+ rng_seed=12,
+ trx=True)
+
+myafq = GroupAFQ(
+ bids_path=op.join(afd.afq_home, 'HBN'),
+ dwi_preproc_pipeline='qsiprep',
+ t1_preproc_pipeline='qsiprep',
+ participant_labels=['NDARAA948VFH'],
+ b0_threshold=100,
+ tracking_params=tracking_params,
+ pve=pve)
+
+myafq.export("streamlines")
+```
+
+## Move Some Things
+
+The `cmd_outputs` method is flexible and can perform other file operations
+besides deletion. For example, to copy files:
+
+```python
+# Copy only files dependent on tractography
+myafq.cmd_outputs(
+ cmd="cp",
+ dependent_on="track",
+ suffix="/path/to/backup/")
+
+# Note: The method automatically adds `-r` for "cp" and "rm" operations.
+```
+
+```{code-cell} ipython3
+# Create backup directory
+backup_dir = op.join(afd.afq_home, "HBN_backup")
+os.makedirs(backup_dir, exist_ok=True)
+
+# Move the outputs of AFQ to this directory
+myafq.cmd_outputs(cmd="mv", suffix=backup_dir)
+```
+
+## How It Works
+
+The method works by:
+1. Identifying all pyAFQ outputs in the output directory
+2. Filtering based on the `dependent_on` parameter (if provided)
+3. Removing any files listed in `exceptions`
+4. Executing the specified command (`rm`, `mv`, `cp` etc.) on the remaining files
+5. Resetting the workflow to ensure subsequent runs regenerate affected derivatives
+
+We plan to automate this process in the future.
diff --git a/docs/source/examples/tutorial_examples/plot_004_export.md b/docs/source/examples/tutorial_examples/plot_004_export.md
new file mode 100644
index 000000000..74a75756e
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_004_export.md
@@ -0,0 +1,222 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Exporting pyAFQ Results
+
+This example shows how to use the `export` methods to obtain results from
+the ParticipantAFQ object. The `export` methods are used to calculate
+derived quantities from the data, such as DKI parameters, tract profiles,
+and bundle segmentations.
+
+```{code-cell} ipython3
+import os
+import os.path as op
+import plotly
+
+from AFQ.api.participant import ParticipantAFQ
+import AFQ.data.fetch as afd
+import AFQ.definitions.image as afm
+```
+
+## Preparing the ParticipantAFQ object
+
+In this example, we will create a ParticipantAFQ object based on the
+[Participant AFQ API example](plot_002_participant_afq_api). Please refer to that
+example for a detailed description of the parameters.
+
+```{code-cell} ipython3
+afd.fetch_hbn_preproc(["NDARAA948VFH"])
+
+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")
+
+output_dir = op.join(afd.afq_home, "HBN",
+ "derivatives", "afq", "sub-NDARAA948VFH",
+ "ses-HBNsiteRU", "dwi")
+os.makedirs(output_dir, exist_ok=True)
+
+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")))
+
+# Initialize the ParticipantAFQ object
+myafq = ParticipantAFQ(
+ dwi_data_file=dwi_data_file,
+ bval_file=bval_file,
+ bvec_file=bvec_file,
+ t1_file=t1_file,
+ output_dir=output_dir,
+ pve=pve,
+ tracking_params={
+ "n_seeds": 10000,
+ "random_seeds": True,
+ "rng_seed": 2022,
+ "trx": True
+ },
+)
+```
+
+## The Export Method
+The ParticipantAFQ object has a method called `export`, which allows the user
+to calculate various derived quantities from the data.
+
+The `export` method can be called with a string argument that specifies the
+type of quantity to be calculated. For example, `myafq.export("OPTIONS")`.
+
+To list the available options, you can call the `export` method with the
+argument "help". This will return a list of all the available options for
+the `export` method.
+
+```{code-cell} ipython3
+myafq.export("help")
+```
+
+:::{note}
+Not all options are possible even if they are valid. This will depend on
+your dataset and pyAFQ API parameters. For example, you cannot calculate
+DKI model from single shell data. Please refer to the
+[tractography params documentation](/howto/tractography_params) for more documentation.
+:::
+
+## Calculating DTI FA (Diffusion Tensor Imaging Fractional Anisotropy)
+
+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.
+
+```{code-cell} ipython3
+FA_fname = myafq.export("dti_fa")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from AFQ.utils.docs import embed_image, embed_html
+
+embed_image(FA_fname)
+```
+
+## 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).
+:::
+
+```{code-cell} ipython3
+myafq.export("profiles")
+```
+
+## 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 allow you to pan, zoom, and rotate.
+:::
+
+```{code-cell} ipython3
+:tags: [remove-execution]
+bundle_html = myafq.export("all_bundles_figure")
+plotly.io.show(bundle_html[0])
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+bundle_html = myafq.export("all_bundles_figure")
+embed_html(bundle_html[1])
+```
+
+## The Export All Method
+There is a `export_all` method that will export all the results from the
+AFQ pipeline. Undeerneath the hood, `export_all` calls a series of `export`
+methods. This method was added for convenience, and is the recommended method
+to use when you want to export everything the results from the AFQ pipeline.
+
+This method will export the following results, if possible:
+- Transformation maps and files
+- Start and PVE mask images, associated diffusion scalar files
+- Tractography, segmented tractography in to bundles
+- Tract profiles, streamline counts, median tract length
+- Visuzalizations of the bundles and tract profiles
+
+```{code-cell} ipython3
+myafq.export_all()
+```
+
+## The Export Up To Method
+The `export_up_to` method allows you to export results up to a certain
+point (but not including) in the AFQ pipeline.
+
+For example, if you want to export all the results up to the bundle
+segmentation step, you can call the `export_up_to` method with the
+argument "bundles". This will export all the required derivatives
+prior to the bundle segmentation step, where you can then take the
+derivatives and debug your own custom segmentation pipeline.
+
+```{code-cell} ipython3
+myafq.export_up_to("bundles")
+```
diff --git a/docs/source/examples/tutorial_examples/plot_005_viz.md b/docs/source/examples/tutorial_examples/plot_005_viz.md
new file mode 100644
index 000000000..ac21617c3
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_005_viz.md
@@ -0,0 +1,482 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Visualizing AFQ derivatives
+
+Visualizing the results of a pyAFQ analysis is useful because it allows us to
+inspect the results of the analysis and to communicate the results to others.
+The pyAFQ pipeline produces a number of different kinds of outputs, including
+visualizations that can be used for quality control and for quick examination
+of the results of the analysis.
+
+However, when communicating the results of pyAFQ analysis, it is often useful
+to have more specific control over the visualization that is produced. In
+addition, it is often useful to have visualizations that are visually appealing
+and striking. In this tutorial, we will use the `fury `_
+library [^1] to visualize outputs of pyAFQ as publication-ready figures.
+
+```{code-cell} ipython3
+import os
+import os.path as op
+import nibabel as nib
+import numpy as np
+from math import radians
+
+from dipy.io.streamline import load_trk
+from dipy.tracking.streamline import transform_streamlines
+
+from fury import actor, window
+from fury.colormap import create_colormap
+
+
+import AFQ.data.fetch as afd
+from AFQ.viz.utils import PanelFigure
+```
+
+## Get some data from HBN POD2
+
+The Healthy Brain Network Preprocessed Open Diffusion Derivatives (HBN POD2)
+is a collection of resources based on the Healthy Brain Network dataset
+[^2], [^3]. HBN POD2 includes data derivatives - including pyAFQ derivatives -
+from more than 2,000 subjects. The data and the derivatives can be browsed at
+https://fcp-indi.s3.amazonaws.com/index.html#data/Projects/HBN/BIDS_curated/
+
+Here, we will visualize the results from one subject, together with their
+anatomy and using several variations. We start by downloading their
+pyAFQ-processed data using fetcher functions that download both the
+preprocessed data, as well as the pyAFQ-processed data (Note that this
+will take up about 1.75 GB of disk space):
+
+```{code-cell} ipython3
+afd.fetch_hbn_preproc(["NDARAA948VFH"])
+study_path = afd.fetch_hbn_afq(["NDARAA948VFH"])[1]
+
+deriv_path = op.join(
+ study_path, "derivatives")
+
+afq_path = op.join(
+ deriv_path,
+ 'afq',
+ 'sub-NDARAA948VFH',
+ 'ses-HBNsiteRU')
+
+bundle_path = op.join(afq_path,
+ 'bundles')
+```
+
+## Read data into memory
+The bundle coordinates from pyAFQ are always saved in the reference frame of
+the diffusion data from which they are generated, so we need an image file
+with the dMRI coordinates as a reference for loading the data (we could also
+use `"same"` here).
+
+
+
+```{code-cell} ipython3
+fa_img = nib.load(op.join(afq_path,
+ 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_model-DKI_FA.nii.gz'))
+fa = fa_img.get_fdata()
+sft_arc = load_trk(op.join(bundle_path,
+ 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_space-RASMM_model-CSD_desc-prob-afq-ARC_L_tractography.trk'), fa_img)
+sft_cst = load_trk(op.join(bundle_path,
+ 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_space-RASMM_model-CSD_desc-prob-afq-CST_L_tractography.trk'), fa_img)
+```
+
+## Transform into the T1w reference frame
+Our first goal is to visualize the bundles with a background of the
+T1-weighted image, which provides anatomical context. We read in this data and
+transform the bundle coordinates, first into the RASMM common coordinate frame
+and then subsequently into the coordinate frame of the T1-weighted data (if
+you find this confusing, you can brush up on this topic in the
+`nibabel documentation `_).
+
+```{code-cell} ipython3
+
+t1w_img = nib.load(op.join(deriv_path,
+ 'qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-preproc_T1w.nii.gz'))
+t1w = t1w_img.get_fdata()
+sft_arc.to_rasmm()
+sft_cst.to_rasmm()
+arc_t1w = transform_streamlines(sft_arc.streamlines,
+ np.linalg.inv(t1w_img.affine))
+cst_t1w = transform_streamlines(sft_cst.streamlines,
+ np.linalg.inv(t1w_img.affine))
+```
+
+## Visualizing bundles with principal direction coloring
+The first visualization we will create will have the streamlines colored
+according to their direction. The color of each streamline will be RGB encoded
+according to the RAS of the average orientation of its segments. This is the
+default behavior in fury.
+
+Fury uses "actors" to render different kind of graphics. For the bundles, we
+will use the `line` actor. These objects are wrappers around the `vtkActor
+class `_, so methods of
+that class (like `GetProperty()`) are available to use. We like to set the
+aesthetics of the streamlines, so that they are rendered as tubes and with
+slightly thicker line-width than the default. We create a function that sets
+these properties of the line actor via the `GetProperty` method. We will
+reuse this function later on, also setting the key-word arguments to the call
+to `actor.line`, but for now we use the default setting, which colors each
+streamline based on the RAS orientation, and we set the line width to 8.
+
+```{code-cell} ipython3
+
+arc_actor = actor.streamlines(arc_t1w, thickness=8)
+cst_actor = actor.streamlines(cst_t1w, thickness=8)
+```
+
+## Slicer actors
+The anatomical image is rendered using `slicer` actors. These are actors that
+visualize one slice of a three dimensional volume. We call the function on the
+T1-weighted data, selecting the x slice that is half-way through the x
+dimension of the image (`shape[0]`) and the z slice that is a third of a
+way through that x dimension of the image (`shape[-1]`).
+We set the visibility of the y slice to `False`
+
+```{code-cell} ipython3
+slicer = actor.data_slicer(t1w,
+ visibility=(
+ True, False, True),
+ initial_slices=(
+ t1w.shape[0] // 2,
+ t1w.shape[1] // 2,
+ t1w.shape[-1] // 3))
+```
+
+## Making a `scene`
+The next kind of fury object we will be working with is a `window.Scene`
+object. This is the (3D!) canvas on which we are drawing the actors. We
+initialize this object and call the `scene.add` method to add the actors.
+
+```{code-cell} ipython3
+scene = window.Scene()
+
+scene.add(arc_actor)
+scene.add(cst_actor)
+scene.add(slicer)
+```
+
+## Showing the visualization
+
+If you are working in an interactive session, you can call:
+
+```python
+window.show(scene, size=(1200, 1200))
+```
+
+to see what the visualization looks like. This would pop up a window that will
+show you the visualization as it is now. You can interact with this
+visualization using your mouse to rotate the image in 3D, and mouse+ctrl or
+mouse+shift to pan and rotate it in plane, respectively. Use the scroll up and
+scroll down in your mouse to zoom in and out. Once you have found a view of
+the data that you like, you can close the window (as long as its open, it is
+blocking execution of any further commands in the Python interpreter!)
+
++++
+
+## Record the visualization
+If you are not working in an interactive session, or you have already figured
+out the camera settings that work for you, you can go ahead and record the
+image into a png file. We use a pretty high resolution here (2400 by 2400) so
+that we get a nice crisp image. That also means the file is pretty large.
+
+```{code-cell} ipython3
+out_folder = op.join(afd.afq_home, "VizExample")
+os.makedirs(out_folder, exist_ok=True)
+
+def save_png(scene, name):
+ """Helper function to PNGs in this example."""
+ show_m = window.ShowManager(
+ scene=scene, window_type="offscreen",
+ size=(2400, 2400)
+ )
+ window.update_camera(show_m.screens[0].camera, None, scene)
+ show_m.screens[0].controller.rotate((0, radians(-90)), None)
+ show_m.render()
+ show_m.window.draw()
+ show_m.snapshot(op.join(out_folder, name))
+
+save_png(scene, 'arc_cst1.png')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from AFQ.utils.docs import embed_image
+
+embed_image(op.join(afd.afq_home, "VizExample", "arc_cst1.png"))
+```
+
+## Setting bundle colors
+The default behavior produces a nice image! But we often want to be able to
+differentiate streamlines based on the bundle to which they belong. For this,
+we will set the color of each streamline, based on the bundle. We often use
+the Tableau20 color palette to set the colors for the different bundles. The
+`actor.line` initializer takes `colors` as a keyword argument and one of the
+options to pass here is an RGB triplet, which will determine the color of all
+of the streamlines in that actor. We can get the Tableau20 RGB triplets from
+Matplotlib's colormap module. We initialize our bundle actors again and clear
+the scene before adding these new actors and adding back the slice actors. We
+then call `record` to create this new figure.
+
+```{code-cell} ipython3
+from matplotlib.cm import tab20
+color_arc = tab20.colors[18]
+color_cst = tab20.colors[2]
+
+arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc)
+cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst)
+
+scene.clear()
+
+scene.add(arc_actor)
+scene.add(cst_actor)
+scene.add(slicer)
+
+save_png(scene, 'arc_cst2.png')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(op.join(afd.afq_home, "VizExample", "arc_cst2.png"))
+```
+
+## Adding core bundles with tract profiles
+Next, we'd like to add a representation of the core of each bundle and plot
+the profile of some property along the length of the bundle. Another option
+for setting streamline colors is for each point along a streamline to have a a
+different color. Here, we will do this with only one streamline, which
+represents the core of the fiber -- it's median trajectory, and with a profile
+of the FA. This requires a few distinct steps. The first is adding another
+actor for each of the core fibers. We determine the core bundle as the median
+of the coordinates of the streamlines after each streamline is resampled to
+100 equi-distant points. In the next step, we extract the FA profiles for each
+of the bundles, using the `afq_profile` function. Before we do this, we make
+sure that the streamlines are back in the voxel coordinate frame of the FA
+volume. The core bundle and FA profile are put together into a new line actor,
+which we initialize as a thick tube (line width of 40) and with a call to
+create a colormap from the FA profiles (we can choose which color-map to use
+here. In this case, we chose `'viridis'`). Note that because we are passing
+only one streamline into the line actor, we have to pass it inside of square
+brackets (`[]`). This is because the `actor.line` initializer expects a
+sequence of streamlines as input. Before plotting things again, we initialize
+our bundle line actors again. This time, we set the opacity of the lines to
+0.1, so that they do not occlude the view of the core bundle visualizations.
+
+:::{note}
+In this case, the profile is FA, but you can use any sequence of 100
+numbers in place of the FA profiles: group differences, p-values, etc.
+:::
+
+```{code-cell} ipython3
+from dipy.tracking.streamline import set_number_of_points
+core_arc = np.median(np.asarray(set_number_of_points(arc_t1w, 100)), axis=0)
+core_cst = np.median(np.asarray(set_number_of_points(cst_t1w, 100)), axis=0)
+
+
+from dipy.stats.analysis import afq_profile
+sft_arc.to_vox()
+sft_cst.to_vox()
+arc_profile = afq_profile(fa, sft_arc.streamlines, affine=np.eye(4))
+cst_profile = afq_profile(fa, sft_cst.streamlines, affine=np.eye(4))
+
+core_arc_actor = actor.streamlines(
+ [core_arc],
+ thickness=40,
+ colors=create_colormap(arc_profile, name='viridis')
+)
+
+core_cst_actor = actor.streamlines(
+ [core_cst],
+ thickness=40,
+ colors=create_colormap(cst_profile, name='viridis')
+)
+
+scene.clear()
+
+arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc, opacity=0.1)
+cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst, opacity=0.1)
+
+scene.add(arc_actor)
+scene.add(cst_actor)
+scene.add(slicer)
+scene.add(core_arc_actor)
+scene.add(core_cst_actor)
+
+save_png(scene, 'arc_cst3.png')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(op.join(afd.afq_home, "VizExample", "arc_cst3.png"))
+```
+
+## Adding ROIs
+Another element that we can add into the visualization are volume renderings
+of regions of interest. For example, the waypoint ROIs that were used to
+define the bundles. Here, we will add the waypoints that were used to define
+the arcuate fasciculus in this subject. We start by reading this data in.
+Because it is represented in the space of the diffusion data, it too needs to
+be resampled into the T1-weighted space, before being visualized. After
+resampling, we might have some values between 0 and 1 because of the
+interpolation from the low resolution of the diffusion into the high
+resolution of the T1-weighted. We will include in the volume rendering any
+values larger than 0. The main change from the previous visualizations is the
+addition of a `contour_from_roi` actor for each of the ROIs. We select another
+color from the Tableau 20 palette to represent this, and use an opacity of
+0.5.
+
+:::{note}
+In this case, the surface rendered is of the waypoint ROIs, but very
+similar code can be used to render other surfaces, provided a volume that
+contains that surface. For example, the gray-white matter boundary could
+be visualized provided an array with a binary representation of the volume
+enclosed in this boundary.
+:::
+
+```{code-cell} ipython3
+
+from dipy.align import resample
+
+waypoint1 = nib.load(
+ op.join(
+ afq_path,
+ "ROIs", "sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_desc-ROI-ARC_L-1-include.nii.gz"))
+
+waypoint2 = nib.load(
+ op.join(
+ afq_path,
+ "ROIs", "sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_desc-ROI-ARC_L-2-include.nii.gz"))
+
+waypoint1_xform = resample(waypoint1, t1w_img)
+waypoint2_xform = resample(waypoint2, t1w_img)
+waypoint1_data = waypoint1_xform.get_fdata() > 0
+waypoint2_data = waypoint2_xform.get_fdata() > 0
+
+scene.clear()
+
+arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc)
+cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst)
+
+scene.add(arc_actor)
+scene.add(cst_actor)
+scene.add(slicer)
+
+surface_color = tab20.colors[0]
+
+waypoint1_actor = actor.contour_from_roi(waypoint1_data,
+ color=surface_color,
+ opacity=0.5)
+
+waypoint2_actor = actor.contour_from_roi(waypoint2_data,
+ color=surface_color,
+ opacity=0.5)
+
+
+scene.add(waypoint1_actor)
+scene.add(waypoint2_actor)
+
+save_png(scene, 'arc_cst4.png')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(op.join(afd.afq_home, "VizExample", "arc_cst4.png"))
+```
+
+## Visualizing tracts and tract profiles with a "glass brain"
+Displaying tracts together with slices of anatomical data is beautiful
+but sometimes poses a challenge because of occlusion. Another option is
+to visualize the data with a "glass brain". That is, a faint contour
+showing where the edges of the brain are, so that the tracts are visible
+and the anatomical context can be understood. One way to generate the
+contour of the brain is to use a brain mask generated from anatomical data.
+We set its display color to black (`[0, 0, 0]`) and its opacity to a very low
+value so that the tracts can easily be seen. This visualization is best with
+a bright background, so we set the background to white (`[1, 1, 1]`).
+In addition, we will demonstrate another way to show tract profile
+information (based on Luo et al. 2025 [^4]): here the tract profile is
+interpolated onto each streamline in the tract and mapped to a colormap.
+This vividly shows the variations in tract profile values over space.
+
+```{code-cell} ipython3
+brain_mask_img = nib.load(op.join(deriv_path,
+ 'qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-brain_mask.nii.gz'))
+
+brain_mask_data = brain_mask_img.get_fdata()
+
+scene.clear()
+
+brain_actor = actor.contour_from_roi(brain_mask_data,
+ color=[0, 0, 0],
+ opacity=0.05)
+
+scene.add(brain_actor)
+
+for sl in arc_t1w:
+ # interpolate the 100 tract profiles values to match the number of points
+ # in the streamline:
+ interpolated_values = np.interp(np.linspace(0, 1, len(sl)),
+ np.linspace(0, 1, len(arc_profile)),
+ arc_profile)
+ colors = create_colormap(interpolated_values, name='Spectral')
+ line_actor = actor.streamlines([sl], thickness=8, colors=colors)
+ scene.add(line_actor)
+
+scene.background = (1, 1, 1)
+save_png(scene, 'arc_cst5.png')
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image(op.join(afd.afq_home, "VizExample", "arc_cst5.png"))
+```
+
+## Making a Figure out of many fury panels
+We can also make a figure that contains multiple panels, each of which
+contains a different visualization. This is useful for communicating the
+results of an analysis. Here, we will make a figure with four panels, using
+some of the visualizations we have already created. We will use some
+convenient methods from pyAFQ.
+
+```{code-cell} ipython3
+pf = PanelFigure(3, 2, 6, 9)
+pf.add_img(op.join(out_folder, 'arc_cst1.png'), 0, 0)
+pf.add_img(op.join(out_folder, 'arc_cst2.png'), 1, 0)
+pf.add_img(op.join(out_folder, 'arc_cst3.png'), 0, 1)
+pf.add_img(op.join(out_folder, 'arc_cst5.png'), 1, 1)
+pf.format_and_save_figure(f"arc_cst_fig.png")
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+embed_image('arc_cst_fig.png')
+```
+
+## References
+
+[^1]: Garyfallidis et al., (2021). FURY: advanced scientific visualization.
+ Journal of Open Source Software, 6(64), 3384,
+ https://doi.org/10.21105/joss.03384
+
+[^2]: 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.
+
+[^3]: 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.
+
+[^4]: Luo A, et al. Two Axes of White Matter Development. In prep.
diff --git a/docs/source/examples/tutorial_examples/plot_006_bids_layout.md b/docs/source/examples/tutorial_examples/plot_006_bids_layout.md
new file mode 100644
index 000000000..0de54e1a6
--- /dev/null
+++ b/docs/source/examples/tutorial_examples/plot_006_bids_layout.md
@@ -0,0 +1,326 @@
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+mystnb:
+ execution_mode: "off"
+---
+
+# How pyAFQ uses BIDS
+
+The pyAFQ API relies heavily on the
+[Brain Imaging Data Standard (BIDS)](https://bids-specification.readthedocs.io/en/stable/),
+a widely used standard for organizing and describing neuroimaging data. This
+means that the software assumes that its inputs are organized according to the
+BIDS specification and its outputs conform where possible with BIDS.
+
+:::{note}
+Derivatives of processing diffusion MRI are not currently fully
+described in the existing BIDS specification, but describing these
+is part of an ongoing effort. Wherever possible, we conform with
+the draft implementation of the BIDS DWI derivatives available
+[here](https://bids-specification.readthedocs.io/en/wip-derivatives/05-derivatives/05-diffusion-derivatives.html).
+:::
+
+In this example, we will explore the use of BIDS in pyAFQ and see
+how BIDS allows us to extend and provide flexibility to the users
+of the software.
+
+```{code-cell} ipython3
+import os
+import os.path as op
+
+import AFQ.api.bundle_dict as abd
+from AFQ.api.group import GroupAFQ
+import AFQ.data.fetch as afd
+import AFQ.definitions.image as afm
+```
+
+To interact with and query BIDS datasets, we use
+[pyBIDS](https://bids-standard.github.io/pybids/), which we import here:
+
+```{code-cell} ipython3
+import bids
+from bids.layout import BIDSLayout
+```
+
+We start with some example data. The data we will use here is
+generated from the
+[Stanford HARDI dataset](https://purl.stanford.edu/ng782rw8378).
+The call below fetches
+this dataset and organized it within the `~/AFQ_data` folder in the BIDS
+format.
+
+```{code-cell} ipython3
+afd.organize_stanford_data(clear_previous_afq="all")
+```
+
+After doing that, we should have a folder that looks like this:
+
+| stanford_hardi
+| ├── dataset_description.json
+| └── derivatives
+| ├── freesurfer
+| │ ├── dataset_description.json
+| │ └── sub-01
+| │ └── ses-01
+| │ └── anat
+| │ ├── sub-01_ses-01_T1w.nii.gz
+| │ └── sub-01_ses-01_seg.nii.gz
+| └── vistasoft
+| ├── dataset_description.json
+| └── sub-01
+| └── ses-01
+| └── dwi
+| ├── sub-01_ses-01_dwi.bvals
+| ├── sub-01_ses-01_dwi.bvecs
+| └── sub-01_ses-01_dwi.nii.gz
+
+The top level directory (`stanford_hardi`) is our overall BIDS dataset folder.
+In many cases, this folder will include folders with raw data for each subject
+in the dataset. In this case, we do not include the raw data folders and only
+have the outputs of pipelines that were used to preprocess the data (e.g.,
+correct the data for subject motion, eddy currents, and so forth).
+In general, only the preprocessed diffusion data is required for pyAFQ to run.
+See the ["Organizing your data"](/howto/data) section of the
+documentation for more details.
+In this case, one folder contains derivative of the Freesurfer software and
+another folder contains the DWI data that has been preprocessed with the
+Vistasoft software.
+pyAFQ provides facilities to segment tractography results obtained
+using other software as well. For example, we often use
+[qsiprep](https://qsiprep.readthedocs.io/en/latest/) to preprocess
+our data and reconstruct tractographies with software such as
+[MRTRIX](https://www.mrtrix.org/). Here, we will demonstrate how to use
+these reconstructions in the pyAFQ segmentation and tractometry pipeline
+We fetch this data and add it as a separate pipeline
+The following code will download a previously-created tractography and
+organize it by adding it to the BIDS dataset folder and renaming them to be
+BIDS-compliant (e.g., `sub-01_ses_01_dwi_tractography.trk`).
+
+```{code-cell} ipython3
+afd.fetch_stanford_hardi_tractography()
+
+bids_path = op.join(op.expanduser('~'), 'AFQ_data', 'stanford_hardi')
+tractography_path = op.join(bids_path, 'derivatives', 'my_tractography')
+sub_path = op.join(tractography_path, 'sub-01', 'ses-01', 'dwi')
+
+seg_file = op.join(afd.afq_home, "stanford_hardi", "derivatives",
+ "freesurfer", "sub-01", "ses-01", "anat",
+ "sub-01_ses-01_seg.nii.gz")
+pve = afm.PVEImages(
+ afm.LabelledImageFile(
+ path=seg_file,
+ inclusive_labels=[0]),
+ afm.LabelledImageFile(
+ path=seg_file,
+ exclusive_labels=[0, 1, 2], combine="and"),
+ afm.LabelledImageFile(
+ path=seg_file,
+ inclusive_labels=[1, 2]))
+
+os.makedirs(sub_path, exist_ok=True)
+os.rename(
+ op.join(
+ op.expanduser('~'),
+ 'AFQ_data',
+ 'stanford_hardi_tractography',
+ 'full_segmented_cleaned_tractography.trk'),
+ op.join(
+ sub_path,
+ 'sub-01_ses-01-dwi_tractography.trk'))
+
+afd.to_bids_description(
+ tractography_path,
+ **{"Name": "my_tractography",
+ "PipelineDescription": {"Name": "my_tractography"},
+ "GeneratedBy": [{"Name": "my_tractography"}]})
+```
+
+After we do that, our dataset folder should look like this:
+
+| stanford_hardi
+| ├── dataset_description.json
+| └── derivatives
+| ├── freesurfer
+| │ ├── dataset_description.json
+| │ └── sub-01
+| │ └── ses-01
+| │ └── anat
+| │ ├── sub-01_ses-01_T1w.nii.gz
+| │ └── sub-01_ses-01_seg.nii.gz
+| ├── my_tractography
+| | ├── dataset_description.json
+| │ └── sub-01
+| │ └── ses-01
+| │ └── dwi
+| │ └── sub-01_ses-01-dwi_tractography.trk
+| └── vistasoft
+| ├── dataset_description.json
+| └── sub-01
+| └── ses-01
+| └── dwi
+| ├── sub-01_ses-01_dwi.bvals
+| ├── sub-01_ses-01_dwi.bvecs
+| └── sub-01_ses-01_dwi.nii.gz
+
+To explore the layout of these derivatives, we will initialize a
+`BIDSLayout` class instance to help us see what is in this dataset
+
+```{code-cell} ipython3
+layout = bids.BIDSLayout(bids_path, derivatives=True)
+```
+
+Because there is no raw data in this BIDS layout (only derivatives),
+pybids will report that there are no subjects and sessions:
+
+```{code-cell} ipython3
+print(layout)
+```
+
+But a query on the derivatives will reveal the different derivatives that
+are stored here:
+
+```{code-cell} ipython3
+print(layout.derivatives)
+```
+
+We can use a `bids.BIDSValidator` object to make sure that the
+files within our data set are BIDS-compliant. For example, we can
+extract the tractography derivatives part of our layout using:
+
+```{code-cell} ipython3
+my_tractography = layout.derivatives["my_tractography"]
+```
+
+This variable is also a BIDS layout object. This object has a `get`
+method, which allows us to query and find specific items within the
+layout. For example, we can ask for files that have a suffix consistent
+with tractography results:
+
+```{code-cell} ipython3
+tractography_files = my_tractography.get(suffix='tractography')
+```
+
+Or ask for files that have a `.trk` extension:
+
+```{code-cell} ipython3
+tractography_files = my_tractography.get(extension='.trk')
+```
+
+In this case, both of these would produce the same result.
+
+```{code-cell} ipython3
+tractography_file = tractography_files[0]
+print(tractography_file)
+```
+
+We can also get some more structured information about this file:
+
+```{code-cell} ipython3
+print(tractography_file.get_entities())
+```
+
+We can use a `bids.BIDSValidator` class instance to validate that
+this file is compliant with the specification. Note that the validator
+requires that the filename be provided relative to the root of the BIDS
+dataset, so we have to split the string that contains the full path
+of the tractography to extract only the part that is relative to the
+root of the entire BIDS `layout` object:
+
+```{code-cell} ipython3
+tractography_full_path = tractography_file.path
+tractography_relative_path = tractography_full_path.split(layout.root)[-1]
+
+validator = bids.BIDSValidator()
+print(validator.is_bids(tractography_relative_path))
+```
+
+Next, we specify the information we need to define the bundles that we are
+interested in segmenting. In this case, we are going to use a list of
+bundle names for the bundle info. These names refer to bundles for
+which we already have clear definitions of the information
+needed to segment them (e.g., waypoint ROIs and probability maps).
+For an example that includes custom definition of bundle info, see the
+[plot_callosal_tract_profile example](http://tractometry.org/pyAFQ/auto_examples/plot_callosal_tract_profile.html).
+
+```{code-cell} ipython3
+bundle_info = abd.default_bd()[
+ "Left Inferior Longitudinal",
+ "Right Inferior Longitudinal",
+ "Left Arcuate",
+ "Right Arcuate",
+ "Left Corticospinal",
+ "Right Corticospinal"]
+```
+
+Now, we can define our GroupAFQ object, pointing to the derivatives of the
+`'my_tractography'` pipeline as inputs. This is done by setting the
+`import_tract` key-word argument. We pass the
+`bundle_info` defined above. We also point to the preprocessed
+data that is in a `'dmriprep'` pipeline. Note that the pipeline name
+is not necessarily the name of the folder it is in; the pipeline name is
+defined in each pipeline's `dataset_description.json`. These data were
+preprocessed with 'vistasoft', so this is the pipeline we'll point to
+If we were using `'qsiprep'`, this is where we would pass that
+string instead. If we did that, AFQ would look for a derivatives
+folder called `'stanford_hardi/derivatives/qsiprep'` and find the
+preprocessed DWI data within it. Finally, to speed things up
+a bit, we also sub-sample the provided tractography. This is
+done by defining the segmentation_params dictionary input.
+To sub-sample to 10,000 streamlines, we define
+`'nb_streamlines' = 10000`.
+
+```{code-cell} ipython3
+my_afq = GroupAFQ(
+ bids_path,
+ dwi_preproc_pipeline='vistasoft',
+ t1_preproc_pipeline='freesurfer',
+ bundle_info=bundle_info,
+ import_tract={
+ "suffix": "tractography",
+ "scope": "my_tractography"
+ },
+ pve=pve,
+ segmentation_params={'nb_streamlines': 10000})
+```
+
+Finally, to run the segmentation and extract tract profiles, we call
+The `export_all` method. This creates all of the derivative outputs of
+AFQ within the 'stanford_hardi/derivatives/afq' folder.
+
+```{code-cell} ipython3
+my_afq.export_all()
+```
+
+A few common issues that can hinder BIDS from working properly are:
+
+1. Faulty `dataset_description.json` file. You need to make sure that the
+ file contains the right names for the pipeline. See above for an example
+ of that.
+2. File naming convention doesn't uniquely identify file with bids filters.
+
+```{code-cell} ipython3
+
+```
+
+The outputs of AFQ are also BIDS compatible. Here we demonstrate how
+to load the afq entities and show all files with the key-value pair
+desc-bundles
+
+```{code-cell} ipython3
+layout = BIDSLayout(bids_path)
+layout.add_derivatives(
+ f'{bids_path}/derivatives/afq',
+ config=['bids', 'derivatives'])
+print(layout.get(desc="bundles", return_type="filename"))
+```
diff --git a/examples/tutorial_examples/plot_007_rois.py b/docs/source/examples/tutorial_examples/plot_007_rois.md
similarity index 86%
rename from examples/tutorial_examples/plot_007_rois.py
rename to docs/source/examples/tutorial_examples/plot_007_rois.md
index c2623c07c..9da6d9f80 100644
--- a/examples/tutorial_examples/plot_007_rois.py
+++ b/docs/source/examples/tutorial_examples/plot_007_rois.md
@@ -1,7 +1,19 @@
-"""
-====================================================================
-Plotting Default Regions of Interest (ROIs) to Understand the Tracts
-====================================================================
+---
+file_format: mystnb
+jupytext:
+ text_representation:
+ extension: .md
+ format_name: myst
+kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+language_info:
+ name: python
+ pygments_lexer: ipython3
+---
+
+# Plotting Default Regions of Interest (ROIs) to Understand the Tracts
This script visualizes the default Regions of Interest (ROIs) for the
white matter tracts we recognize by default in pyAFQ. It loads predefined
tract templates into MNI space, extracts inclusion, exclusion, start, and
@@ -10,11 +22,12 @@
The visualization helps understand the spatial relationships between tracts and
their defining ROIs.
-"""
-####################################################
-# Import libraries, load the default tract templates
++++
+Import libraries, load the default tract templates
+
+```{code-cell} ipython3
import numpy as np
import matplotlib
@@ -26,11 +39,11 @@
templates = abd.default_bd() + abd.callosal_bd()
+```
+Define a function to visualize ROIs for a specific tract
-##########################################################
-# Define a function to visualize ROIs for a specific tract
-
+```{code-cell} ipython3
def visualize_tract_rois(tract_name):
"""
Visualize ROIs for a specific tract overlaid on the template brain.
@@ -163,10 +176,11 @@ def get_max_slice(roi_img, axis=0):
figures.append(fig)
return figures
+```
-#####################################
-# Create visualization for each tract
+Create visualization for each tract
+```{code-cell} ipython3
for bundle_name in templates.bundle_names:
print(f"Visualizing ROIs for tract: {bundle_name}")
@@ -174,3 +188,14 @@ def get_max_slice(roi_img, axis=0):
for ii, fig in enumerate(figs):
fig.savefig(f"{bundle_name}_{ii}.png")
plt.close(fig)
+```
+
+```{code-cell} ipython3
+:tags: [remove-input]
+from IPython.display import display
+from AFQ.utils.docs import embed_image
+
+for bundle_name in templates.bundle_names:
+ for ii, _ in enumerate(figs):
+ display(embed_image(f"{bundle_name}_{ii}.png"))
+```
diff --git a/docs/source/explanations/index.rst b/docs/source/explanations/index.rst
index 1773713bd..c0031d954 100644
--- a/docs/source/explanations/index.rst
+++ b/docs/source/explanations/index.rst
@@ -236,7 +236,7 @@ choice (bottom).
Running pyAFQ in practice
=========================
-`Getting started with pyAFQ - ParticipantAFQ `_
+`Getting started with pyAFQ - ParticipantAFQ <../examples/tutorial_examples/plot_002_participant_afq_api.html>`_
AFQ Insight Example
diff --git a/docs/source/howto/converter.rst b/docs/source/howto/converter.rst
index abea4d2bc..a99a4d558 100644
--- a/docs/source/howto/converter.rst
+++ b/docs/source/howto/converter.rst
@@ -13,7 +13,7 @@ specified in its dataset_description.json.
There is an example usage of import_tract with the
:class:`AFQ.api.group.GroupAFQ` object in
-`How pyAFQ uses BIDS `_.
+`How pyAFQ uses BIDS <../examples/tutorial_examples/plot_006_bids_layout.html>`_.
Matlab AFQ to Python AFQ conversion
diff --git a/docs/source/howto/data.rst b/docs/source/howto/data.rst
index ad7fb7a81..3195584f4 100644
--- a/docs/source/howto/data.rst
+++ b/docs/source/howto/data.rst
@@ -4,7 +4,7 @@ Organizing your data
~~~~~~~~~~~~~~~~~~~~
pyAFQ works with `BIDS compliant `_ diffusion data.
-While not required it is the prefered data format for neuroimaging interoperability.
+While not required it is the preferred data format for neuroimaging interoperability.
Anatomical data and segmentation are optional. If a T1-weighted anatomical image and its
segmentation are not provided, the software will use the diffusion data to
estimate the parts of the image that comprise the white matter.
@@ -59,7 +59,7 @@ diffusion data is required for pyAFQ::
├── sub-01_ses-01_dwi.bvec
└── sub-01_ses-01_dwi.nii.gz
-See `How pyAFQ uses BIDS `_
+See `How pyAFQ uses BIDS <../examples/tutorial_examples/plot_006_bids_layout.html>`_
for a more extensive example.
diff --git a/docs/source/howto/index.rst b/docs/source/howto/index.rst
index ed44c3b5c..e00992c1d 100644
--- a/docs/source/howto/index.rst
+++ b/docs/source/howto/index.rst
@@ -25,4 +25,4 @@ software.
tractography_params
segmentation_params
cleaning_params
- howto_examples/index
+ ../examples/howto_examples/index
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8162f5d3d..6c34b663e 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -14,14 +14,14 @@ of the major long-range brain white matter connections.
- This example shows you how to run pyAFQ on a BIDS dataset, where pyAFQ
uses the BIDS structure to find necessary files:
- `Getting started with pyAFQ - GroupAFQ `_
+ `Getting started with pyAFQ - GroupAFQ `_
- This example shows you how to run pyAFQ on any dataset,
where input file paths are given explicitly:
- `Getting started with pyAFQ - ParticipantAFQ `_
+ `Getting started with pyAFQ - ParticipantAFQ `_
- What is the difference between tractography and tractometry? See in the `explanations `_ page.
- For more detailed information on the variety of uses of pyAFQ, see the `how to `_ page. In particular, this one example is useful for understanding tractometry:
- - `Understanding the different stages of tractometry with videos `_
+ - `Understanding the different stages of tractometry with videos `_
- For a detailed description of the methods and objects used in pyAFQ, see the `reference documentation `_ page.
Here are some useful reference pages:
@@ -152,7 +152,7 @@ and by a grant from the
:maxdepth: 2
guides_index
- tutorials/tutorial_examples/index
- howto/howto_examples/index
+ examples/tutorial_examples/index
+ examples/howto_examples/index
developing/index
bib
diff --git a/docs/source/reference/bundledict.rst b/docs/source/reference/bundledict.rst
index 581e662bd..3041dfaf4 100644
--- a/docs/source/reference/bundledict.rst
+++ b/docs/source/reference/bundledict.rst
@@ -162,7 +162,7 @@ Examples
========
Custom bundle definitions such as the OR, and the standard BundleDict can be
combined through addition. For an example, see
-`Plotting the Optic Radiations `_.
+`Plotting the Optic Radiations <../examples/howto_examples/optic_radiations.html>`_.
Some tracts, such as the Vertical Occipital Fasciculus, may be defined relative
to other tracts. In those cases, the custom tract definitions should appear in the BundleDict
object after the reference tracts have been defined. These reference tracts can
diff --git a/docs/source/reference/fibertracts.rst b/docs/source/reference/fibertracts.rst
index 05339931c..7f871005a 100644
--- a/docs/source/reference/fibertracts.rst
+++ b/docs/source/reference/fibertracts.rst
@@ -122,7 +122,7 @@ Major Fiber Tracts
- :blue:`Blue`
- :cite:`Yeatman2012`
- Default
- - `Getting started with pyAFQ - GroupAFQ <../tutorials/tutorial_examples/plot_001_group_afq_api.html>`_
+ - `Getting started with pyAFQ - GroupAFQ <../examples/tutorial_examples/plot_001_group_afq_api.html>`_
* - Right Anterior Thalamic
- :lightblue:`Light Blue`
-
@@ -222,7 +222,7 @@ Major Fiber Tracts
- :darkgreen:`Dark Green`
- :cite:`Dougherty2007`
-
- - `Callosal bundles using AFQ API <../howto/howto_examples/plot_afq_callosal.html>`_
+ - `Callosal bundles using AFQ API <../examples/howto_examples/plot_afq_callosal.html>`_
* - Callosum Motor
- :skyblue:`Sky Blue`
-
@@ -262,7 +262,7 @@ Major Fiber Tracts
-
- :cite:`Jossinger2022`
- Cerebellar
- - `Delineating cerebellar peduncles <../howto/howto_examples/cerebellar_peduncles.html>`_
+ - `Delineating cerebellar peduncles <../examples/howto_examples/cerebellar_peduncles.html>`_
* - Right Inferior Cerebellar Peduncle
-
-
@@ -292,7 +292,7 @@ Major Fiber Tracts
-
- :cite:`Tzourio-Mazoyer2002`
- Acoustic Radiations
- - `Acoustic Radiations Example <../howto/howto_examples/acoustic_radiations.html>`_
+ - `Acoustic Radiations Example <../examples/howto_examples/acoustic_radiations.html>`_
* - Right Acoustic Radiation
-
-
@@ -302,7 +302,7 @@ Major Fiber Tracts
- :neonblue:`Neon Blue`
- :cite:`Caffarra2021`
- Optic Radiations
- - `Optic Radiations Example <../howto/howto_examples/optic_radiations.html>`_
+ - `Optic Radiations Example <../examples/howto_examples/optic_radiations.html>`_
* - Right Optic Radiation
- :lightneonblue:`Light Neon Blue`
-
@@ -322,7 +322,7 @@ Major Fiber Tracts
-
- :cite:`Sagi2024`
- Superior Longitudinal Sub-divisions
- - `SLF 1/2/3 Example <../howto/howto_examples/add_custom_bundle.html>`_
+ - `SLF 1/2/3 Example <../examples/howto_examples/add_custom_bundle.html>`_
* - Right Superior Longitudinal I
-
-
diff --git a/docs/source/reference/kwargs.rst b/docs/source/reference/kwargs.rst
index 48123e3e2..2b75a8c97 100644
--- a/docs/source/reference/kwargs.rst
+++ b/docs/source/reference/kwargs.rst
@@ -171,5 +171,5 @@ n_points_indiv: int or None
n_points to resample streamlines to before plotting. If None, no resampling is done. Default: 40
viz_backend_spec: str
- Which visualization backend to use. See Visualization Backends page in documentation for details https://tractometry.org/pyAFQ/reference/viz_backend.html One of {"fury", "plotly", "plotly_no_gif"}. Default: "plotly_no_gif"
+ Which visualization backend to use. See Visualization Backends page in documentation for details https://tractometry.org/pyAFQ/reference/viz_backend.html One of {"fury", "plotly", "plotly_no_mp4"}. Default: "plotly_no_mp4"
diff --git a/docs/source/reference/methods.rst b/docs/source/reference/methods.rst
index d8bdc092a..a5e520ae0 100644
--- a/docs/source/reference/methods.rst
+++ b/docs/source/reference/methods.rst
@@ -560,7 +560,7 @@ all_bundles_figure:
indiv_bundles_figures:
- list of full paths to html or gif files containing visualizations of individual bundles
+ list of full paths to html or mp4 files containing visualizations of individual bundles
tract_profile_plots:
diff --git a/docs/source/reference/viz_backend.rst b/docs/source/reference/viz_backend.rst
index eda95b68b..c9ca8ec18 100644
--- a/docs/source/reference/viz_backend.rst
+++ b/docs/source/reference/viz_backend.rst
@@ -7,25 +7,25 @@ How these figures are generated depends on the choice of visualization
backend. Currently, there are three choices:
#. plotly: use `Plotly `_ to generate interactive
- figures that are exported as html files and rotating GIFs.
- The rotating GIFs can take a long time to generate (~2 min/gif)
+ figures that are exported as html files and rotating mp4s.
+ The rotating mp4s can take a long time to generate (~2 min/mp4)
using this backend. No additional installations are
required to use this backend.
-#. plotly_no_gif: use `Plotly `_ to generate
+#. plotly_no_mp4: use `Plotly `_ to generate
interavtive figures that are exported as html files, but do not
- generate GIFs. No additional installations are required to use this
+ generate mp4s. No additional installations are required to use this
backend.
-#. fury: use `Fury `_ to generate rotating GIFs. Unlike
- our current setup in Plotly, Fury can generate GIFs quickly. To use this
+#. fury: use `Fury `_ to generate rotating mp4s. Unlike
+ our current setup in Plotly, Fury can generate mp4s quickly. To use this
backend, install pyAFQ with the optional fury requirements:
pip install pyAFQ[fury]
And install `libGL `_.
-By default, plotly_no_gif is used. Fury requires additional
+By default, plotly_no_mp4 is used. Fury requires additional
installations and does not make interactive figures, and Plotly takes a
-significant amount of time to generate rotating GIFs.
+significant amount of time to generate rotating mp4s.
Fury Dockerfile for Cloudknot
diff --git a/docs/source/tutorials/index.rst b/docs/source/tutorials/index.rst
index 4ee1aaa14..64f4058b6 100644
--- a/docs/source/tutorials/index.rst
+++ b/docs/source/tutorials/index.rst
@@ -12,10 +12,8 @@ Then, you are ready to run pyAFQ in one of the following ways:
1. The first is to write a program that uses `pyAFQ` as a software library.
Detailed tutorials for this are provided in the link at the bottom of the page.
-1. The second is as a program run in the command line. After installing the software, and organizing the data, run::
-
- pyAFQ /path/to/config.toml
-
+1. The second is as a program run in the command line. After installing the software
+ and organizing the data, run: `pyAFQ /path/to/config.toml`,
pointing the program to the location of a configuration file (see
:doc:`configuration file specification ` for an
explanation of this file). This will run whole-brain tractography, segment
@@ -26,4 +24,4 @@ Then, you are ready to run pyAFQ in one of the following ways:
.. toctree::
:maxdepth: 2
- tutorial_examples/index.rst
\ No newline at end of file
+ ../examples/tutorial_examples/index.rst
diff --git a/examples/howto_examples/acoustic_radiations.py b/examples/howto_examples/acoustic_radiations.py
deleted file mode 100644
index 43f4d0d72..000000000
--- a/examples/howto_examples/acoustic_radiations.py
+++ /dev/null
@@ -1,119 +0,0 @@
-"""
-===============================================================
-How to add new bundles into pyAFQ (Acoustic Radiations Example)
-===============================================================
-
-pyAFQ is designed to be customizable and extensible. This example shows how you
-can customize it to define a new bundle based on a definition of waypoint and
-endpoint ROIs of your design. In this case, we add the acoustic radiations.
-
-We start by importing some of the components that we need for this example and
-fixing the random seed for reproducibility
-
-"""
-
-import os.path as op
-import plotly
-import numpy as np
-
-from AFQ.api.group import GroupAFQ
-import AFQ.api.bundle_dict as abd
-import AFQ.data.fetch as afd
-from AFQ.definitions.image import ImageFile, RoiImage
-np.random.seed(1234)
-
-
-#############################################################################
-# Get dMRI data
-# ---------------
-# We will analyze one subject from the Healthy Brain Network Processed Open
-# Diffusion Derivatives dataset (HBN-POD2) [1]_, [2]_. We'll use a fetcher to
-# get preprocessed dMRI data for one of the >2,000 subjects in that study. The
-# data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
-# folder:
-
-study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
-
-#############################################################################
-# Define custom `BundleDict` object
-# --------------------------------
-# The `BundleDict` object holds information about "include" and "exclude" ROIs,
-# as well as endpoint ROIS, and whether the bundle crosses the midline. In this
-# case, the ROIs are all defined in the MNI template space that is used as the
-# default template space in pyAFQ, but, in principle, other template spaces
-# could be used.
-#
-# The ROIs for the case can be downloaded using a custom fetcher which saves
-# the ROIs to a folder and creates a dictionary of paths to the ROIs:
-
-ar_rois = afd.read_ar_templates()
-
-bundles = abd.BundleDict({
- "Left Acoustic Radiation": {
- "start": ar_rois["AAL_Thal_L"],
- "end": ar_rois["AAL_TempSup_L"],
- "cross_midline": False,
- },
- "Right Acoustic Radiation": {
- "start": ar_rois["AAL_Thal_R"],
- "end": ar_rois["AAL_TempSup_R"],
- "cross_midline": False
- }
-})
-
-
-#############################################################################
-# Define GroupAFQ object
-# ----------------------
-# HBN POD2 have been processed with qsiprep [3]_. This means that a brain mask
-# has already been computer for them. As you can see in other examples, these
-# data also have a mapping calculated for them, which can also be incorporated
-# into processing. However, in this case, we will let pyAFQ calculate its own
-# SyN-based mapping so that the `combine_bundle` method can be used below to
-# create a montage visualization.
-#
-# For tractography, we use CSD-based probabilistic tractography seeding
-# extensively (`n_seeds=4` means 81 seeds per voxel!), but only within the ROIs
-# and not throughout the white matter. This is controlled by passing
-# `"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
-# are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
-# initiates the pipeline.
-
-my_afq = GroupAFQ(
- bids_path=study_dir,
- dwi_preproc_pipeline="qsiprep",
- participant_labels=["NDARAA948VFH"],
- output_dir=op.join(study_dir, "derivatives", "afq_ar"),
- tracking_params={"n_seeds": 4,
- "directions": "prob",
- "odf_model": "CSD",
- "seed_mask": RoiImage(use_endpoints=True)},
- bundle_info=bundles)
-
-my_afq.export_all()
-
-#############################################################################
-# Interactive bundle visualization
-# --------------------------------
-# Another way to examine the outputs is to export the individual bundle
-# figures, which show the streamlines, as well as the ROIs used to define the
-# bundle. This is an html file, which contains an interactive figure that can
-# be navigated, zoomed, rotated, etc.
-
-bundle_html = my_afq.export("indiv_bundles_figures")
-plotly.io.show(bundle_html["NDARAA948VFH"]["Left Acoustic Radiation"])
-
-#############################################################################
-# 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.
diff --git a/examples/howto_examples/afq_callosal.py b/examples/howto_examples/afq_callosal.py
deleted file mode 100644
index 0dd1c667d..000000000
--- a/examples/howto_examples/afq_callosal.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-==========================
-Callosal bundles using AFQ API
-==========================
-An example using the AFQ API to find callosal bundles using the templates from:
-http://hdl.handle.net/1773/34926
-"""
-import os.path as op
-import matplotlib.pyplot as plt
-import nibabel as nib
-
-import plotly
-
-from AFQ.api.group import GroupAFQ
-import AFQ.api.bundle_dict as abd
-from AFQ.definitions.image import RoiImage
-import AFQ.data.fetch as afd
-
-##########################################################################
-# Get some example data
-# ---------------------
-#
-# Retrieves `Stanford HARDI dataset `_.
-#
-
-afd.organize_stanford_data(clear_previous_afq="track")
-
-##########################################################################
-# Set tractography parameters (optional)
-# ---------------------
-# We make this tracking_params which we will pass to the GroupAFQ object
-# which specifies that we want 100,000 seeds randomly distributed
-# in the ROIs of every bundle.
-#
-# We only do this to make this example faster and consume less space.
-
-tracking_params = dict(seed_mask=RoiImage(),
- n_seeds=25000,
- random_seeds=True,
- rng_seed=42)
-
-##########################################################################
-# Set segmentation parameters (optional)
-# ---------------------
-# We make this segmentation_params which we will pass to the GroupAFQ object
-# which specifies that we want to clip the extracted tract profiles
-# to only be between the two ROIs.
-#
-# We do this because tract profiles become less reliable as the bundles
-# approach the gray matter-white matter boundary. On some of the non-callosal
-# bundles, ROIs are not in a good position to clip edges. In these cases,
-# one can remove the first and last nodes in a tract profile.
-
-segmentation_params = {"clip_edges": True}
-
-##########################################################################
-# Initialize a GroupAFQ object:
-# -------------------------
-#
-# We specify bundle_info as the callosal bundles only
-# (`abd.callosal_bd`). If we want to segment both the callosum
-# and the other bundles, we would pass
-# `abd.callosal_bd() + abd.default_bd()`
-# instead. This would tell the GroupAFQ object to use bundles from both
-# the standard and callosal templates.
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'stanford_hardi'),
- dwi_preproc_pipeline='vistasoft',
- t1_preproc_pipeline='freesurfer',
- bundle_info=abd.callosal_bd(),
- tracking_params=tracking_params,
- segmentation_params=segmentation_params,
- viz_backend_spec='plotly_no_gif')
-
-# Calling export all produces all of the outputs of processing, including
-# tractography, scalar maps, tract profiles and visualizations:
-myafq.export_all()
-
-
-##########################################################################
-# Create Group Density Maps:
-# -------------------------
-#
-# pyAFQ can make density maps of streamline counts per subject/session
-# by calling `myafq.export("density_map")`. When using `GroupAFQ`, you can also
-# combine these into one file by calling `myafq.export_group_density()`.
-group_density = myafq.export_group_density()
-group_density = nib.load(group_density).get_fdata()
-fig, ax = plt.subplots(1)
-ax.matshow(
- group_density[:, :, group_density.shape[-1] // 2, 0],
- cmap='viridis')
-ax.axis("off")
-
-
-##########################################################################
-# Visualizing bundles and tract profiles:
-# ---------------------------------------
-# This would run the script and visualize the bundles using the plotly
-# interactive visualization, which should automatically open in a
-# new browser window.
-bundle_html = myafq.export("all_bundles_figure")
-plotly.io.show(bundle_html["01"][0])
diff --git a/examples/howto_examples/afq_fwdti.py b/examples/howto_examples/afq_fwdti.py
deleted file mode 100644
index c1a3a8144..000000000
--- a/examples/howto_examples/afq_fwdti.py
+++ /dev/null
@@ -1,162 +0,0 @@
-"""
-==========================
-How to use Free water DTI
-==========================
-
-The free-water DTI model [1, 2]_ fits a two compartment model to dMRI data
-with more than one non-zero shell. One compartment is a spherical compartment
-with the diffusivity of water, which accounts for free water in the tissue.
-The other compartment is the standard diffusion tensor.
-
-In this example, we will compare the results of the fwDTI model and the
-standard DTI model.
-
-"""
-import os.path as op
-
-import matplotlib.pyplot as plt
-import nibabel as nib
-
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-
-from AFQ.definitions.image import ImageFile, RoiImage
-import AFQ.api.bundle_dict as abd
-
-import pandas as pd
-
-#############################################################################
-# Get some data
-# --------------
-# In this example, we will look at one subject from the Healthy Brain Network
-# Processed Open Diffusion Derivatives dataset (HBN-POD2) [3, 4]_. The data in
-# this study were collected with a multi-shell sequence, meaning that most
-# subjects in this study have data with more than one non-zero b-value. This
-# means that we can fit the fwDTI model to their data.
-#
-# We'll use a fetcher to get preprocessd dMRI data for one of the >2,000
-# subjects in that study. The data gets organized into a BIDS-compatible
-# format in the `~/AFQ_data/HBN` folder.
-
-study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
-
-#############################################################################
-# Define an AFQ object
-# --------------------
-# In addition to preprocessd dMRI data, HBN-POD2 contains brain mask and mapping
-# information for each subject. We can use this information in our pipeline, by
-# inserting this information as `mapping_definition` inputs to the `GroupAFQ` class
-# initializer. When initializing this object, we will also ask for the fwDTI scalars
-# to be computed. For expedience, we will limit our investigation to the bilateral
-# arcuate fasciculus and track only around that bundle. If you would like to do this
-# for all bundles, you would remove the `bundle_dict` and `tracking_params` inputs
-# to the initializer that
-# are provided below.
-
-bundle_names = ["Left Arcuate", "Right Arcuate"]
-bundle_dict = abd.default_bd()[bundle_names]
-
-myafq = GroupAFQ(
- bids_path=study_dir,
- dwi_preproc_pipeline='qsiprep',
- output_dir=op.join(study_dir, "derivatives", "afq_fwdti"),
- bundle_info=bundle_dict,
- tracking_params={
- "n_seeds": 50000,
- "random_seeds": True,
- "seed_mask": RoiImage(use_waypoints=True, use_endpoints=True),
- },
- scalars=["fwdti_fa", "fwdti_md", "fwdti_fwf", "dti_fa", "dti_md"])
-
-#############################################################################
-# Compare fwDTI and DTI maps
-# -------------------------
-# First, we take a look at the maps for the FA and MD calculated using the two
-# models
-
-fwFA = nib.load(myafq.export("fwdti_fa")["NDARAA948VFH"]).get_fdata()
-FA = nib.load(myafq.export("dti_fa")["NDARAA948VFH"]).get_fdata()
-
-fig, ax = plt.subplots(1, 2)
-ax[0].matshow(FA[:, :, FA.shape[-1] // 2], cmap='gray')
-ax[0].axis("off")
-
-ax[1].matshow(fwFA[:, :, fwFA.shape[-1] // 2], cmap='gray')
-ax[1].axis("off")
-
-
-fwMD = nib.load(myafq.export("fwdti_md")["NDARAA948VFH"]).get_fdata()
-MD = nib.load(myafq.export("dti_md")["NDARAA948VFH"]).get_fdata()
-
-fig, ax = plt.subplots(1, 2)
-ax[0].matshow(MD[:, :, MD.shape[-1] // 2], cmap='gray', vmax=0.005)
-ax[0].axis("off")
-
-ax[1].matshow(fwMD[:, :, fwMD.shape[-1] // 2], cmap='gray', vmax=0.005)
-ax[1].axis("off")
-
-
-#############################################################################
-# Free-water fraction map
-# -------------------------
-# In addition to the standard tensor scalars, provided by the fwDTI model, this
-# model also computes a free-water fraction, which is a number between 0 and 1
-# that assesses the fraction of the voxel signal that is explained by the free
-# water compartment.
-
-fwf = nib.load(myafq.export("fwdti_fwf")["NDARAA948VFH"]).get_fdata()
-fig, ax = plt.subplots()
-ax.matshow(fwf[:, :, fwf.shape[-1] // 2], cmap='gray')
-ax.axis("off")
-
-#############################################################################
-# Comparing bundle profiles
-# -------------------------
-# Exporting the profiles will create a CSV file that contains information about
-# node-by-node values of the scalars computed with both models. Here, we read in
-# this information with Pandas and plot a comparison. As you can see, when free
-# water is accounted for with the fwDTI model, FA along the bundle is higher and
-# MD is lower than that estimated with the standard DTI model.
-
-profiles_csv = myafq.export("profiles")['NDARAA948VFH']
-profiles = pd.read_csv(profiles_csv)
-
-fig, ax = plt.subplots(3, 2)
-for ii, bundle in enumerate(["Left Arcuate", "Right Arcuate"]):
- ax[0, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_fa"],
- label="fwDTI")
- ax[0, ii].plot(profiles[profiles["tractID"] == bundle]["dti_fa"],
- label="DTI")
- ax[0, ii].set_ylabel("FA")
- ax[0, ii].legend()
- ax[1, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_md"],
- label="fwDTI")
- ax[1, ii].plot(profiles[profiles["tractID"] == bundle]["dti_md"],
- label="DTI")
- ax[1, ii].set_ylabel("MD")
- ax[1, ii].legend()
- ax[2, ii].plot(profiles[profiles["tractID"] == bundle]["fwdti_fwf"])
- ax[2, ii].set_ylabel("Free water fraction")
- ax[2, ii].set_xlabel("Distance along the bundle (A => P)")
-
-
-#############################################################################
-# References
-# ----------
-#
-# .. [1] Hoy AR, Koay CG, Kecskemeti SR, Alexander AL. Optimization of a free
-# water elimination two-compartment model for diffusion tensor imaging.
-# Neuroimage. 2014;103:323-333.
-#
-# .. [2] Henriques RN, Rokem A, Garyfallidis E, St-Jean S, Peterson ET, Correia
-# MM. [Re] Optimization of a free water elimination two-compartment model
-# for diffusion tensor imaging. bioRxiv. February 2017:108795.
-# doi:10.1101/108795
-#
-# .. [3] 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.
-#
-# .. [4] 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.
diff --git a/examples/howto_examples/baby_afq.py b/examples/howto_examples/baby_afq.py
deleted file mode 100644
index 04adb55c9..000000000
--- a/examples/howto_examples/baby_afq.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
-=============================================
-BabyAFQ : tractometry for infant dMRI data
-=============================================
-
-The following is an example of tractometry for infant bundles. This example and
-resulting pyAFQ support for pediatric bundles was inspired by and largely due
-to the work of Grotheer et al. [Grotheer2021]_, as implemented in
-[Grotheer2023]_.
-
-.. note::
- Because it is time and disk-space consuming, this example
- is not run when the pyAFQ documentation is built. To run this example
- yourself, you can download the contents of this file as an
- executable `.py` file or as a Jupyter notebook from the links at the bottom
- of the page.
-
-"""
-import os.path as op
-import plotly
-import wget
-import zipfile
-
-from AFQ.api.group import GroupAFQ
-import AFQ.api.bundle_dict as abd
-import AFQ.data.fetch as afd
-
-##########################################################################
-# Baby dMRI data
-# -------------------------
-#
-# Infant MRI data are quite different from data acquired in grownup
-# participants, and even from children that are just a few years older.
-# First, there is the rather obvious difference in size. Baby brains are
-# approximately 25% the size of grownup brains at birth. But there are also
-# often less known differences in brain tissue properties. For example, the
-# myelin content of white matter is much lower in infants than in grownups.
-# This is important because the diffusion signal that we measure with dMRI is
-# sensitive to the myelin content, and it means that the dMRI signal differs
-# quite a bit in newborn infants. For the purpose of delineating the major
-# white matter pathways, it is also important to know that their shape,
-# location and curvature is different in infants than in grownups. For example,
-# the arcuate fasciculus is much more curved in infants than in grownups.
-# Because of this, we use a different set of templates for infant brains than
-# for grownup brains. These templates were created and validated by
-# Mareike Grotheer and colleagues in [Grotheer2021]_. They are available for
-# download as part of the pyAFQ software, as we will show below.
-#
-# In this example, we will demonstrate the use of pyAFQ on data from one
-# infant. The data, provided by Kalanit Grill Spector's
-# `Stanford Vision and Perception Neuroscience Lab `,
-# and was previously published in [Grotheer2021]_.
-# The data is available to download on
-# `Figshare `.
-# You can download it from there and unzip it into ~/AFQ_Data/baby_example/
-# (Note that this is 2.69GB of data, so it can take a while to download). Or
-# you can download it and unzip it using the following block of code.
-
-data_folder = op.join(op.expanduser('~'), "AFQ_data/")
-baby_zip = op.join(data_folder, "baby_example.zip")
-if not op.exists(baby_zip):
- print("Downloading processed pediatric data; this could take a while...")
- wget.download(
- "https://figshare.com/ndownloader/files/38053692",
- baby_zip)
-
-with zipfile.ZipFile(baby_zip, 'r') as zip_ref:
- zip_ref.extractall(op.join(data_folder, "baby_example"))
-
-##########################################################################
-# Initialize a GroupAFQ object:
-# -----------------------------
-#
-# Now that the data is downloaded and organized in a BIDS-compliant structure,
-# we can start running pyAFQ on it. We start by initializing a GroupAFQ object.
-# This object manages all of the data transformations and computations
-# conducted by the software, based on its initial configuration, which we set
-# up below.
-#
-# A few special things to note here:
-#
-# 1. The data were preprocessed using the `vistasoft` pipeline, so we set
-# `dwi_preproc_pipeline = "vistasoft"`.
-# 2. We use the UNC neonatal template, which can be read on a call to the
-# `read_pediatric_templates` function in `AFQ.data.fetch`.
-# 3. We use the `baby_bd` to define the bundles that we want to
-# segment. This dictionary is different from the default behavior in that it
-# uses the waypoint ROIs from [Grotheer2021]_.
-# 4. In this case, tractography has already been run using
-# `MRTRIX `, and is accessed using the
-# `import_tract` key-word argument.
-
-myafq = GroupAFQ(
- bids_path=op.join(op.expanduser('~'),
- "AFQ_data/baby_example/example_bids_subject"),
- dwi_preproc_pipeline="vistasoft",
- reg_template_spec=afd.read_pediatric_templates(
- )["UNCNeo-withCerebellum-for-babyAFQ"],
- reg_subject_spec="b0",
- bundle_info=abd.baby_bd(),
- import_tract={
- "suffix": "tractography", "scope": "mrtrix"},
-)
-
-##########################################################################
-# Running the pipeline
-# --------------------
-#
-# A call to the `export` function will trigger the pyAFQ pipeline. This will
-# run tractography, bundle segmentation, and bundle cleaning. The results will
-# be saved in the `~/AFQ_data/baby_example/derivatives/afq` folder. This can
-# take a while to run, depending on your computer.
-# In this case, we call `export` with the `all_bundles_figure` option. This is
-# because visualizations are created after most other parts of the pipeline
-# have been run. This means that when this call is done, you should have many
-# of the derivative results in the output folder, including the tractography,
-# segmentation, and tract profile results, as well as the visualizations.
-
-viz = myafq.export("all_bundles_figure")
-
-##########################################################################
-# Viewing the results
-# -------------------
-# One way to view the results is to open the file named
-# `sub-01_ses-01_dwi_space-RASMM_model-probCSD_algo-AFQ_desc-viz_dwi.html`
-# in your browser. This is a visualization of the tractography and segmentation
-# results for all of the bundles. You can navigate this visualization by
-# clicking on the different bundles in the legend on the right side of the
-# screen. You can also zoom in and out using the mouse wheel, and rotate the
-# view by clicking and dragging with the mouse. You can also view the FA tract
-# profiles in a plot on the left side of the page.
-#
-# If the baby bundles appear dark in the html visualization due to low FA values, you
-# can reduce the upper limit of the range in the `sbv_lims_bundles` option when
-# building your GroupAFQ object (e.g. `GroupAFQ(..., sbv_lims_bundles=[0, 0.5])`).
-
-
-##########################################################################
-# References:
-# -------------------------
-# .. [Grotheer2021] Grotheer, Mareike, Mona Rosenke, Hua Wu, Holly Kular,
-# Francesca R. Querdasi, Vaidehi S. Natu, Jason D. Yeatman,
-# and Kalanit Grill-Spector. "White matter myelination during
-# early infancy is linked to spatial gradients and myelin
-# content at birth." Nature communications 13: 997.
-#
-# .. [Grotheer2023] Grotheer, Mareike, David Bloom, John Kruper,
-# Adam Richie-Halford, Stephanie Zika,
-# Vicente A. Aguilera González, Jason D. Yeatman,
-# Kalanit Grill-Spector, and Ariel Rokem. "Human white matter
-# myelinates faster in utero than ex utero." Proceedings
-# of the National Academy of Sciences 120: e2303491120.
diff --git a/examples/howto_examples/cloudknot_example.py b/examples/howto_examples/cloudknot_example.py
deleted file mode 100644
index 9faa354f7..000000000
--- a/examples/howto_examples/cloudknot_example.py
+++ /dev/null
@@ -1,163 +0,0 @@
-"""
-==========================================
-Using cloudknot to run pyAFQ on AWS batch:
-==========================================
-One of the purposes of ``pyAFQ`` is to analyze large-scale openly-available
-datasets, such as those in the
-`Human Connectome Project `_.
-
-To analyze these datasets, large amounts of compute are needed.
-One way to gain access to massive computational power is by using
-cloud computing. Here, we will demonstrate
-how to use ``pyAFQ`` in the Amazon Web Services cloud.
-
-We will rely on the `AWS Batch Service `_ ,
-and we will submit work into AWS Batch using software that our group
-developed called `Cloudknot `_.
-"""
-
-##########################################################################
-# Import cloudknot and set the AWS region within which computations will take
-# place. Setting a region is important, because if the data that you are
-# analyzing is stored in `AWS S3 `_ in a
-# particular region, it is best to run the computation in that region as well.
-# That is because AWS charges for inter-region transfer of data.
-import cloudknot as ck
-ck.set_region('us-east-1')
-
-##########################################################################
-# Define the function to use
-# --------------------------
-# ``Cloudknot`` uses the single program multiple data paradigm of computing.
-# This means that the same function will be run on multiple different inputs.
-# For example, a ``pyAFQ`` processing function run
-# on multiple different subjects in a dataset.
-# Below, we define the function that we will use. Notice that
-# ``Cloudknot`` functions include the import statements of the dependencies
-# used. This is necessary so that ``Cloudknot`` knows
-# what dependencies to install into AWS Batch to run this function.
-
-
-def afq_process_subject(subject):
- # define a function that each job will run
- # In this case, each process does a single subject
- import s3fs
- # all imports must be at the top of the function
- # cloudknot installs the appropriate packages from pip
- from s3bids.utils import S3BIDSStudy
- from AFQ.api.group import GroupAFQ
- import AFQ.definitions.image as afm
-
- # Download the given subject to your local machine from s3
- # Can find subjects more easily if they are specified in a
- # BIDS participants.tsv file, even if it is sparse
- study_ixi = S3BIDSStudy(
- "my_study",
- "my_study_bucket",
- "my_study_prefix",
- subjects=[subject],
- use_participants_tsv=True,
- anon=False)
- study_ixi.download(
- "local_bids_dir",
- include_derivs=["pipeline_name"])
-
- # define the api AFQ object
- myafq = GroupAFQ(
- "local_bids_dir",
- dwi_preproc_pipeline="pipeline_name",
- viz_backend_spec='plotly') # this will generate both interactive html and GIFs # noqa
-
- # export_all runs the entire pipeline and creates many useful derivates
- myafq.export_all()
-
- # upload the results to some location on s3
- myafq.upload_to_s3(
- s3fs.S3FileSystem(),
- "my_study_bucket/my_study_prefix/derivatives/afq")
-
-
-##########################################################################
-# Here we provide a list of subjects that we have selected to process
-# to randomly select 3 subjects without replacement, instead do:
-# subjects = [[1], [2], [3]]
-# see the docstring for S3BIDSStudy.__init__ for more information
-subjects = ["123456", "123457", "123458"]
-
-##########################################################################
-# Defining a ``Knot`` instance
-# ---------------------------------
-# We instantiate a class instance of the :class:`ck.Knot` class.
-# This object will be used to run your jobs.
-# The object is instantiated with the `'AmazonS3FullAccess'` policy,
-# so that it can write the results
-# out to S3, into a bucket that you have write permissions on.
-# Setting the `bid_percentage` key-word makes AWS Batch use
-# `spot EC2 instances `_ for the
-# computation. This can result in substantial cost-savings, as spot compute
-# instances can cost much less than on-demand instances.
-# However, not that spot instances can also
-# be evicted, so if completing all of the work is very time-sensitive,
-# do not set this key-word argument. Using the `image_github_installs`
-# key-word argument will install pyAFQ from GitHub.
-# You can also specify other forks and branches to install from.
-knot = ck.Knot(
- name='afq-process-subject-201009-0',
- func=afq_process_subject,
- base_image='python:3.11',
- image_github_installs="https://github.com/tractometry/pyAFQ.git",
- pars_policies=('AmazonS3FullAccess',),
- bid_percentage=100)
-
-##########################################################################
-# Launching the computation
-# --------------------------------
-# The :meth:`map` method of the :class:`Knot object maps each of the inputs
-# provided as a sequence onto the function and executes the function on each
-# one of them in parallel.
-result_futures = knot.map(subjects)
-
-##########################################################################
-# Once computations have started, you can call the following
-# function to view the progress of jobs::
-#
-# knot.view_jobs()
-#
-# You can also view the status of a specific job::
-#
-# knot.jobs[0].status
-
-
-##########################################################################
-# When all jobs are finished, remember to use the :meth:`clobber` method to
-# destroy all of the AWS resources created by the :class:`Knot`
-result_futures.result()
-knot.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
-
-##########################################################################
-# In a second :class:`Knot` object, we use a function that takes the
-# resulting profiles of each subject and combines them into one csv file.
-
-
-def afq_combine_profiles(dummy_argument):
- from AFQ.api import download_and_combine_afq_profiles
- download_and_combine_afq_profiles(
- "my_study_bucket", "my_study_prefix")
-
-
-knot2 = ck.Knot(
- name='afq_combine_subjects-201009-0',
- func=afq_combine_profiles,
- base_image='python:3.11',
- image_github_installs="https://github.com/tractometry/pyAFQ.git",
- pars_policies=('AmazonS3FullAccess',),
- bid_percentage=100)
-
-##########################################################################
-# This knot is called with a dummy argument, which is not used within the
-# function itself. The `job_type` key-word argument is used to signal to
-# ``Cloudknot`` that only one job is submitted rather than the default
-# array of jobs.
-result_futures2 = knot2.map(["dummy_argument"], job_type="independent")
-result_futures2.result()
-knot2.clobber(clobber_pars=True, clobber_repo=True, clobber_image=True)
diff --git a/examples/howto_examples/optic_radiations.py b/examples/howto_examples/optic_radiations.py
deleted file mode 100644
index 7388c9d65..000000000
--- a/examples/howto_examples/optic_radiations.py
+++ /dev/null
@@ -1,155 +0,0 @@
-"""
-===========================================================
-How to add new bundles into pyAFQ(Optic Radiations Example)
-===========================================================
-
-pyAFQ is designed to be customizable and extensible. This example shows how you
-can customize it to define a new bundle based on a definition of waypoint and
-endpoint ROIs of your design.
-
-In this case, we add the optic radiations, based on work by Caffara et al. [1]_,
-[2]_. The optic radiations (OR) are the primary projection of visual information
-from the lateral geniculate nucleus of the thalamus to the primary visual
-cortex. Studying the optic radiations with dMRI provides a linkage between white
-matter tissue properties, visual perception and behavior, and physiological
-responses of the visual cortex to visual stimulation.
-
-We start by importing some of the components that we need for this example and
-fixing the random seed for reproducibility
-
-"""
-
-import os.path as op
-import plotly
-import numpy as np
-import shutil
-
-from AFQ.api.group import GroupAFQ
-import AFQ.api.bundle_dict as abd
-import AFQ.data.fetch as afd
-from AFQ.definitions.image import ImageFile, RoiImage
-import AFQ.utils.streamlines as aus
-np.random.seed(1234)
-
-
-#############################################################################
-# Get dMRI data
-# ---------------
-# We will analyze one subject from the Healthy Brain Network Processed Open
-# Diffusion Derivatives dataset (HBN-POD2) [3]_, [4]_. We'll use a fetcher to
-# get preprocessed dMRI data for one of the >2,000 subjects in that study. The
-# data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
-# folder:
-
-study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
-
-#############################################################################
-# Define custom `BundleDict` object
-# --------------------------------
-# The `BundleDict` object holds information about "include" and "exclude" ROIs,
-# as well as endpoint ROIS, and whether the bundle crosses the midline. In this
-# case, the ROIs are all defined in the MNI template space that is used as the
-# default template space in pyAFQ, but, in principle, other template spaces
-# could be used.
-#
-# The ROIs for the case can be downloaded using a custom fetcher which saves
-# the ROIs to a folder and creates a dictionary of paths to the ROIs:
-
-or_rois = afd.read_or_templates()
-
-bundles = abd.OR_bd()
-
-#############################################################################
-# Custom bundle definitions such as the OR, and the standard BundleDict can be
-# combined through addition. To get both the OR and the standard bundles, we
-# would execute the following code::
-#
-# bundles = bundles + abd.default_bd()
-#
-# In this case, we will skip this and generate just the OR.
-
-
-#############################################################################
-# Define GroupAFQ object
-# ----------------------
-# HBN POD2 have been processed with qsiprep [5]_. This means that a brain mask
-# has already been computer for them. As you can see in other examples, these
-# data also have a mapping calculated for them, which can also be incorporated
-# into processing. However, in this case, we will let pyAFQ calculate its own
-# SyN-based mapping so that the `combine_bundle` method can be used below to
-# create a montage visualization.
-#
-# For tractography, we use CSD-based probabilistic tractography seeding
-# extensively (`n_seeds=4` means 81 seeds per voxel!), but only within the ROIs
-# and not throughout the white matter. This is controlled by passing
-# `"seed_mask": RoiImage()` in the `tracking_params` dict. The custom bundles
-# are passed as `bundle_info=bundles`. The call to `my_afq.export_all()`
-# initiates the pipeline.
-
-my_afq = GroupAFQ(
- bids_path=study_dir,
- dwi_preproc_pipeline="qsiprep",
- participant_labels=["NDARAA948VFH"],
- output_dir=op.join(study_dir, "derivatives", "afq_or"),
- tracking_params={"n_seeds": 4,
- "directions": "prob",
- "odf_model": "CSD",
- "seed_mask": RoiImage()},
- bundle_info=bundles)
-
-my_afq.export_all()
-
-#############################################################################
-# Visualize a montage
-# ----------------------
-# One way to examine the output of the pyAFQ pipeline is by creating a montage
-# of images of a particular bundle across a group of participants (or, in this
-# case, the one participant that was analyzed).
-#
-# .. note::
-#
-# The montage file is copied to the present working directory so that it gets
-# properly rendered into the web-page containing this example. It is not
-# necessary to do this when running this type of analysis.
-
-my_afq.combine_bundle("Left Optic Radiation")
-montage = my_afq.group_montage(
- "Left Optic Radiation",
- (1, 1), "Axial", "left")
-shutil.copy(montage[0], op.split(montage[0])[-1])
-
-#############################################################################
-# Interactive bundle visualization
-# --------------------------------
-# Another way to examine the outputs is to export the individual bundle
-# figures, which show the streamlines, as well as the ROIs used to define the
-# bundle. This is an html file, which contains an interactive figure that can
-# be navigated, zoomed, rotated, etc.
-
-bundle_html = my_afq.export("indiv_bundles_figures")
-plotly.io.show(bundle_html["NDARAA948VFH"]["Left Optic Radiation"])
-
-#############################################################################
-# References
-# ----------
-# .. [1] Caffarra S, Joo SJ, Bloom D, Kruper J, Rokem A, Yeatman JD. Development
-# of the visual white matter pathways mediates development of
-# electrophysiological responses in visual cortex. Hum Brain Mapp.
-# 2021;42(17):5785-5797.
-#
-# .. [2] Caffarra S, Kanopka K, Kruper J, Richie-Halford A, Roy E, Rokem A,
-# Yeatman JD. Development of the alpha rhythm is linked to visual white
-# matter pathways and visual detection performance. bioRxiv.
-# doi:10.1101/2022.09.03.506461
-#
-# .. [3] 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.
-#
-# .. [4] 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.
-#
-# .. [5] 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.
diff --git a/examples/howto_examples/optic_tract.py b/examples/howto_examples/optic_tract.py
deleted file mode 100644
index ce760e9dc..000000000
--- a/examples/howto_examples/optic_tract.py
+++ /dev/null
@@ -1,176 +0,0 @@
-"""
-===============================================================
-How to track the Optic Tract and Posterior Optic Nerve in pyAFQ
-===============================================================
-
-pyAFQ is designed to be customizable and extensible, even to
-relatively small bundles. This example will be based on the work
-of Kruper et al. [1]_. Here, part of the trick is that most preprocessing
-pipelines will cut off a portion or all of the optic nerve. So, we
-only attempt to track the most posterior portion of the optic nerve,
-the optic tract, and the optic chiasm. In addition, we will use our own
-custom PVE maps from the T1-weighted image to help with segmentation.
-
-"""
-
-import plotly
-import os.path as op
-
-import AFQ.api.bundle_dict as abd
-from AFQ.api.group import GroupAFQ
-
-import AFQ.data.fetch as afd
-from AFQ.definitions.image import RoiImage
-import AFQ.definitions.image as afm
-
-#############################################################################
-# Get dMRI data
-# ---------------
-# We will analyze one subject from the Healthy Brain Network Processed Open
-# Diffusion Derivatives dataset (HBN-POD2) [2]_, [3]_. We'll use a fetcher to
-# get preprocessed dMRI data for one of the >2,000 subjects in that study. The
-# data gets organized into a BIDS-compatible format in the `~/AFQ_data/HBN`
-# folder:
-
-study_dir = afd.fetch_hbn_preproc(["NDARAA948VFH"])[1]
-
-#############################################################################
-# Define Optic Tract and Optic Nerve `BundleDict` object
-# --------------------------------
-# The `BundleDict` object holds information about the ROIs used to define the
-# optic tract and optic nerve bundles. In this case, there is also a curvature
-# criterion applied to the optic tract bundles to help separate them from
-# other nearby bundles. Additionally, qb_thresh is set to 6 to use a clustering
-# approach to cleaning as opposed to our standard Mahalanobis distance approach.
-
-oton_rois = afd.read_oton_templates(as_img=False)
-
-otoc_bd = abd.BundleDict({
- "Left Optic": {
- "include": [
- oton_rois["left_OT_1"],
- oton_rois["left_OT_0"],
- oton_rois["left_ON_0"],
- ],
- "curvature": {"path": oton_rois["left_OTOC_curve"], "thresh": 20, "cut": True},
- "qb_thresh": 6
- },
- "Right Optic": {
- "include": [
- oton_rois["right_OT_1"],
- oton_rois["right_OT_0"],
- oton_rois["right_ON_0"],
- ],
- "curvature": {"path": oton_rois["right_OTOC_curve"], "thresh": 20, "cut": True},
- "qb_thresh": 6
- },
- "Left Optic Cross": {
- "include": [
- oton_rois["left_OT_1"],
- oton_rois["left_OT_0"],
- oton_rois["right_ON_0"],
- ],
- "qb_thresh": 6
- },
- "Right Optic Cross": {
- "include": [
- oton_rois["right_OT_1"],
- oton_rois["right_OT_0"],
- oton_rois["left_ON_0"],
- ],
- "qb_thresh": 6
- },
-})
-
-#############################################################################
-# Tractography and segmentation parameters
-# ----------------------------------------
-# Here, we define some custom parameters for tractography and segmentation.
-# For tractography, we use a higher max_angle to account for the
-# sharp turn the optic tract makes around the midbrain. Additionally, we seed
-# densely around the ROIs.
-# For segmentation, we use more lenient cleaning parameters to account for
-# the small size of these bundles.
-
-tractography_params = {
- "seed_mask": RoiImage(),
- "n_seeds": 20,
- "random_seeds": False,
- "max_angle": 60,
- "trx": True,
-}
-
-segmentation_params = {
- "cleaning_params": {
- "distance_threshold": 2,
- "length_threshold": 3,
- }
-}
-
-#############################################################################
-# Define PVE images for segmentation
-# ----------------------------------
-# Finally, we define the PVE images that will be used to guide tracking.
-# For these bundles in particular, this is the trickiest part. Portions of
-# the optic nerve often have low FA, fall outside of the brain mask, or are
-# simply misclassified as gray matter or CSF. In this case, we threshold
-# on the T1-weighted image using manually set thresholds. We only divide it into
-# gray and white matter and accept all streamlines (we do not attempt to filter
-# out streamlines terminating in the CSF; these are normally handled in the bundle
-# recognition and cleaning steps). In your case, this can also be done manually,
-# or done using DIPY's Markov Random Field (MRF;
-# https://docs.dipy.org/stable/examples_built/segmentation/tissue_classification.html).
-# Just remember to either use the unmasked T1 (as we do here) or ensure that
-# the brain mask includes the optic nerve.
-#
-# In the event that tractography fails,
-# you can also check pyAFQ's outputs to see the PVE images and white matter gray
-# matter interface (wmgmi) that pyAFQ used for tractography. You can then adjust
-# pyAFQ parameters, delete these files, and re-run accordingly.
-
-pve = afm.PVEImages(
- afm.ThresholdedScalarImage(
- "t1_file",
- upper_bound=0),
- afm.ThresholdedScalarImage(
- "t1_file",
- upper_bound=180),
- afm.ThresholdedScalarImage(
- "t1_file",
- lower_bound=180))
-
-#############################################################################
-# Define GroupAFQ object
-# ----------------------
-# Finally, we define the `GroupAFQ` object and export all the results.
-
-my_afq = GroupAFQ(
- bids_path=study_dir,
- dwi_preproc_pipeline="qsiprep",
- participant_labels=["NDARAA948VFH"],
- output_dir=op.join(study_dir, "derivatives", "afq_otoc"),
- pve=pve,
- tracking_params=tractography_params,
- segmentation_params=segmentation_params,
- bundle_info=otoc_bd)
-
-my_afq.export_all()
-
-bundle_html = my_afq.export("all_bundles_figure")
-plotly.io.show(bundle_html["NDARAA948VFH"])
-
-#############################################################################
-# References
-# ----------
-# .. [1] Kruper, John, and Ariel Rokem. "Automatic fast and reliable
-# recognition of a small brain white matter bundle." International
-# Workshop on Computational Diffusion MRI. Cham: Springer Nature
-# Switzerland, 2023.
-#
-# .. [2] 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.
-#
-# .. [3] 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.
diff --git a/examples/howto_examples/pyAFQ_with_GPU.py b/examples/howto_examples/pyAFQ_with_GPU.py
deleted file mode 100644
index af234c236..000000000
--- a/examples/howto_examples/pyAFQ_with_GPU.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""
-============================================
-Running pyAFQ using the GPU for tractography
-============================================
-Running pyAFQ using the GPU for tractography is as simple as
-(1) Installing GPUStreamlines using `pip install` and
-(2) passing in the ``jit_backend`` parameter when you create your
- GroupAFQ object.
-To install GPUStreamlines, do:
- `pip install git+https://github.com/dipy/GPUStreamlines.git`
-That's step 1 complete! The rest of this example is the same as the GroupAFQ
-example except with the ``jit_backend`` parameter set.
-"""
-
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-import os.path as op
-import plotly
-
-##########################################################################
-# We start with some example data. The data we will use here is
-# generated from the
-# `Stanford HARDI dataset `_.
-# We then setup our myafq object which we will use to demonstrate
-# the clobber method.
-
-afd.organize_stanford_data()
-
-
-##########################################################################
-# Set tractography parameters
-# ---------------------------
-# We make create a `tracking_params` variable to define the parameters for tractography.
-# The only parameter we need to set to use the GPU is `jit_backend`,
-# which we set to "cuda". Other backends include: "metal", "webgpu", or "numba".
-# Numba is the default.
-# Note that the GPU backend will only run for probabilistic tracking,
-# which is the default.
-
-tracking_params = dict(n_seeds=1e7,
- random_seeds=True,
- rng_seed=2025,
- jit_backend="cuda",
- trx=True)
-
-######################
-# Running with the GPU
-# --------------------
-# Then, run pyAFQ normally.
-# That's it!
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'stanford_hardi'),
- dwi_preproc_pipeline='vistasoft',
- t1_preproc_pipeline='freesurfer',
- tracking_params=tracking_params)
-
-bundle_html = myafq.export("all_bundles_figure")
-plotly.io.show(bundle_html["01"][0])
diff --git a/examples/howto_examples/recobundles.py b/examples/howto_examples/recobundles.py
deleted file mode 100644
index 992388fce..000000000
--- a/examples/howto_examples/recobundles.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
-=========================================
-Using RecoBundles for bundle recognition
-=========================================
-
-For bundle recognition, pyAFQ defaults to use the waypoint ROI approach
-described in [Yeatman2012]_. However, as an alternative approach, pyAFQ also
-supports using the RecoBundles algorithm [Garyfallidis2017]_, which uses an
-atlas of bundles in streamlines. This example shows how to
-use RecoBundles for bundle recognition.
-
-The code closely resembles the code used in :ref:`sphx_glr_tutorial_examples_plot_001-plot_afq_api.py`.
-
-"""
-
-import os.path as op
-import AFQ.data.fetch as afd
-from AFQ.api.group import GroupAFQ
-import AFQ.api.bundle_dict as abd
-
-afd.organize_stanford_data(clear_previous_afq="track")
-
-tracking_params = dict(n_seeds=25000,
- random_seeds=True,
- rng_seed=42)
-
-
-##########################################################################
-# Defining the segmentation params
-# --------------------------------
-# We also refer to bundle recognition as the "segmentation" of the tractogram.
-# Parameters of this process are set through a dictionary input to the
-# `segmentation_params` argument of the GroupAFQ object. In this case, we
-# use `abd.reco_bd(16)`, which tells pyAFQ to use the RecoBundles
-# algorithm for bundle recognition. This uses 16 bundles, there is also
-# an atlas `abd.reco_bd(80)` which uses 80 bundles.
-
-myafq = GroupAFQ(
- output_dir=op.join(afd.afq_home, 'stanford_hardi', 'derivatives',
- 'recobundles'),
- bids_path=op.join(afd.afq_home, 'stanford_hardi'),
- # Set the algorithm to use RecoBundles for bundle recognition:
- bundle_info=abd.reco_bd(16),
- dwi_preproc_pipeline='vistasoft',
- t1_preproc_pipeline='freesurfer',
- tracking_params=tracking_params,
- viz_backend_spec='plotly_no_gif')
-
-fig_files = myafq.export_all()
-
-##########################################################################
-# References:
-# -------------------------
-# .. [Garyfallidis2017] Garyfallidis, Eleftherios, Marc-Alexandre Côté,
-# Francois Rheault, Jasmeen Sidhu, Janice Hau, Laurent
-# Petit, David Fortin, Stephen Cunanne, and Maxime
-# Descoteaux. 2017.“Recognition of White Matter Bundles
-# Using Local and Global Streamline-Based Registration and
-# Clustering.”NeuroImage 170: 283-295.
diff --git a/examples/howto_examples/use_subject_space_rois_from_freesurfer.py b/examples/howto_examples/use_subject_space_rois_from_freesurfer.py
deleted file mode 100644
index b0322874f..000000000
--- a/examples/howto_examples/use_subject_space_rois_from_freesurfer.py
+++ /dev/null
@@ -1,147 +0,0 @@
-"""
-========================================
-Using Subject Space ROIs from Freesurfer
-========================================
-
-An example using the AFQ API to find bundles
-as defined by endpoint ROIs from freesurfer.
-This example can be modified to work with ROIs
-in subject space from pipelines other than freesurfer.
-"""
-import os.path as op
-
-import nibabel as nib
-import plotly
-import numpy as np
-
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-from AFQ.definitions.image import RoiImage
-import AFQ.api.bundle_dict as abd
-
-##########################################################################
-# Get some example data
-# ---------------------
-#
-# Retrieves High angular resolution diffusion imaging (HARDI) dataset from
-# Stanford's Vista Lab
-#
-# see https://purl.stanford.edu/ng782rw8378 for details on dataset.
-#
-# The data for the first subject and first session are downloaded locally
-# (by default into the users home directory) under:
-#
-# ``.dipy/stanford_hardi/``
-#
-# Anatomical data (``anat``) and Diffusion-weighted imaging data (``dwi``) are
-# then extracted, formatted to be BIDS compliant, and placed in the AFQ
-# data directory (by default in the users home directory) under:
-#
-# ``AFQ_data/stanford_hardi/``
-#
-# This data represents the required preprocessed diffusion data necessary for
-# initializing the GroupAFQ object (which we will do next)
-#
-# The clear_previous_afq is used to remove any previous runs of the afq object
-# stored in the AFQ_data/stanford_hardi/ BIDS directory. Set it to None if
-# you want to use the results of previous runs. Setting it to "track"
-# as here will only clear derivatives that depend on the tractography stage
-# (i.e., bundle delination and tract profile calculation),
-# as well as the tractography itself, to save time on recomputation.
-# If you want to only clear derivatives that depend on bundle delineation,
-# and keep the tractography, you can set clear_previous_afq to
-# "recog" instead.
-
-afd.organize_stanford_data(clear_previous_afq="track")
-
-##########################################################################
-# Generate left thalamus ROI from freesurfer segmentation file
-# ------------------------------------------------------------
-# 1. Load the segmentation file that was generated by Freesurfer for
-# the specific subject.
-# 2. Identify the left thalamus within the file, which has the label
-# number 41
-# 3. Create a Nifti image representing the left thalamus ROI:
-# - Assign a value of 1 to the voxels that Freesurfer
-# has labeled as 41 (i.e., the left thalamus).
-# - Assign a value of 0 to all other voxels.
-# This binary mask format is the expected input for pyAFQ when
-# dealing with subject space ROIs. If it's already in binary format,
-# there is no need to do this step.
-
-freesurfer_subject_folder = op.join(
- afd.afq_home, "stanford_hardi",
- "derivatives", "freesurfer",
- "sub-01", "ses-01",
- "anat")
-
-seg_file = nib.load(op.join(
- freesurfer_subject_folder, "sub-01_ses-01_seg.nii.gz"))
-left_thal = seg_file.get_fdata() == 41
-nib.save(
- nib.Nifti1Image(
- left_thal.astype(np.float32),
- seg_file.affine),
- op.join(
- freesurfer_subject_folder,
- "sub-01_ses-01_desc-leftThal_mask.nii.gz"))
-
-# Fetch LV1 ROI
-# which was already generated using the process above
-afd.fetch_stanford_hardi_lv1()
-
-##########################################################################
-# Set tractography parameters (optional)
-# ---------------------
-# We make this tracking_params which we will pass to the GroupAFQ object
-# which specifies that we want 10,000 seeds randomly distributed
-# only within the endpoint ROIs and not throughout the white matter.
-# This is controlled by passing
-# `"seed_mask": RoiImage()` in the `tracking_params` dict.
-#
-# We only do this to make this example faster and consume less space.
-
-tracking_params = dict(n_seeds=10000,
- random_seeds=True,
- rng_seed=42,
- seed_mask=RoiImage(use_endpoints=True))
-
-#############################################################################
-# Define custom `BundleDict` object
-# --------------------------------
-# In a typical `BundleDict` object, ROIs are passed as paths to Nifti files.
-# Here, we define ROIs as dictionaries instead, containing BIDS filters.
-# Then pyAFQ can find the respective ROI for each subject and session.
-
-bundles = abd.BundleDict({
- "L_OR": {
- "start": {
- "scope": "freesurfer",
- "suffix": "mask",
- "desc": "leftThal"},
- "end": {
- "scope": "freesurfer",
- "suffix": "anat",
- "desc": "LV1"
- },
- "cross_midline": False,
- "space": "subject"
- }})
-
-##########################################################################
-# Initialize a GroupAFQ object:
-# -------------------------
-#
-# Creates a GroupAFQ object, that encapsulates tractometry,
-# passing in our custom bundle info. Then we run the pipeline
-# and generate a visualization of the bundle we found.
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'stanford_hardi'),
- dwi_preproc_pipeline='vistasoft',
- t1_preproc_pipeline='freesurfer',
- tracking_params=tracking_params,
- bundle_info=bundles)
-
-bundle_html = myafq.export("indiv_bundles_figures")
-plotly.io.show(bundle_html["01"]["L_OR"])
diff --git a/examples/tutorial_examples/plot_001_group_afq_api.py b/examples/tutorial_examples/plot_001_group_afq_api.py
deleted file mode 100644
index be6e29bb3..000000000
--- a/examples/tutorial_examples/plot_001_group_afq_api.py
+++ /dev/null
@@ -1,308 +0,0 @@
-"""
-======================================
-Getting started with pyAFQ - GroupAFQ
-======================================
-
-There are two ways to :doc:`use pyAFQ `: through the
-command line interface, and by writing Python code. This tutorial will walk you
-through the basics of the latter, using pyAFQ's Python Application Programming
-Interface (API).
-
-"""
-import os.path as op
-
-import matplotlib.pyplot as plt
-import nibabel as nib
-import plotly
-import pandas as pd
-
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-import AFQ.viz.altair as ava
-import AFQ.definitions.image as afm
-
-##########################################################################
-# Example data
-# ------------
-# pyAFQ can be called using GroupAFQ to handle data
-# organized in a BIDS compliant directory.
-# If this is not the case, refer to the Participant AFQ example.
-# To get users started with this tutorial, we will download some example
-# data and organize it in a BIDS compliant way (for more details on how
-# BIDS is used in pyAFQ, refer to :doc:`plot_006_bids_layout`).
-#
-# 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.
-#
-# The clear_previous_afq is used to remove any previous runs of the afq object
-# stored in the `~/AFQ_data/HBN/` BIDS directory. Set it to None if
-# you want to use the results of previous runs.
-
-bids_path = afd.fetch_hbn_preproc(
- ["NDARAA948VFH"],
- clear_previous_afq="all")[1]
-
-##########################################################################
-# Set tractography parameters (optional)
-# ---------------------------------------
-# We make create a `tracking_params` variable, which we will pass to the
-# GroupAFQ 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(
- suffix="probseg", filters={"scope": "qsiprep", "label": "CSF"}),
- afm.ImageFile(
- suffix="probseg", filters={"scope": "qsiprep", "label": "GM"}),
- afm.ImageFile(
- suffix="probseg", filters={"scope": "qsiprep", "label": "WM"}))
-
-##########################################################################
-# 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(
- suffix="mask", filters={"desc": "brain", "scope": "qsiprep"})
-
-##########################################################################
-# Initialize a GroupAFQ object:
-# -------------------------
-#
-# Creates a GroupAFQ object, that encapsulates tractometry. This object can be
-# used to manage the entire :doc:`/explanations/index`, including:
-#
-# - Tractography
-# - Registration
-# - Segmentation
-# - Cleaning
-# - Profiling
-# - Visualization
-#
-# This will also create an output folder for the corresponding AFQ derivatives
-# in the AFQ data directory: ``AFQ_data/HBN/derivatives/afq/``
-#
-# To initialize this object we will pass in the path location to our BIDS
-# compliant data, the name of the preprocessing pipeline we want to use,
-# the name of the t1 preprocessing pipeline we want to use (in this case,
-# its the same, qsiprep [3]), the participant labels we want to process
-# (in this case, just a single subject), the PVE images we defined above, and
-# the tracking parameters we defined above.
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'HBN'),
- dwi_preproc_pipeline='qsiprep',
- t1_preproc_pipeline='qsiprep',
- participant_labels=['NDARAA948VFH'],
- pve=pve,
- brain_mask_definition=brain_mask_definition,
- tracking_params=tracking_params)
-
-##########################################################################
-# Calculating DTI FA (Diffusion Tensor Imaging Fractional Anisotropy)
-# ------------------------------------------------------------------
-# The GroupAFQ 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 for all subjects in the dataset, 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 a dictionary, with the subject
-# IDs as keys, and the filenames of the corresponding files as values.
-# This means that to extract the filename corresponding to the FA of the first
-# subject, we can do:
-
-FA_fname = myafq.export("dti_fa", collapse=False)["NDARAA948VFH"]["HBNsiteRU"]
-
-# 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")
-
-##########################################################################
-# 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')
-
-##########################################################################
-# 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", collapse=False)
-plotly.io.show(bundle_html["NDARAA948VFH"]["HBNsiteRU"][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", collapse=False)[
- "NDARAA948VFH"]["HBNsiteRU"]
-
-##########################################################################
-# .. figure:: {{ fig_files[0] }}
-#
-
-
-##########################################################################
-# We can even use altair to visualize the tract profiles in all
-# of the bundles. We provide a more customizable interface for visualizing
-# the tract profiles using altair.
-# Again, to make this plot, it is required that you install with
-# `pip install pyAFQ[plot]` so that you have the necessary dependencies.
-#
-profiles_df = myafq.combine_profiles()
-altair_df = ava.combined_profiles_df_to_altair_df(
- profiles_df,
- tissue_properties=[
- "dti_fa",
- "dti_md",
- "t1w_over_b0",
- "msdki_msd",
- "msdki_msk"])
-altair_chart = ava.altair_df_to_chart(altair_df)
-altair_chart.display()
-
-##########################################################################
-# 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")
-
-##########################################################################
-# We can check the number of streamlines per bundle, to make sure
-# every bundle is found with a reasonable amount of streamlines.
-
-bundle_counts = pd.read_csv(
- myafq.export("sl_counts", collapse=False)[
- "NDARAA948VFH"]["HBNsiteRU"], index_col=[0])
-for ind in bundle_counts.index:
- if ind == "Total Recognized":
- threshold = 2500
- else:
- threshold = 20
- if bundle_counts["n_streamlines"][ind] < threshold:
- raise ValueError((
- "Small number of streamlines found "
- f"for bundle(s):\n{bundle_counts}"))
-
-##########################################################################
-# Alternative way to initialize GroupAFQ when using QSIPrep data
-# --------------------------------------------------------------
-# As a final note, if you are using QSIPrep preprocessed data,
-# you can also initialize the GroupAFQ object using the
-# `from_qsiprep` class method. This method will automatically set
-# the appropriate BIDS filters to find the preprocessed DWI data.
-# Additionally, it will find and use the brain masks and PVE images
-# that QSIPrep generates. Outside of BIDS filters, the arguments
-# are the same as those used when initializing the GroupAFQ object
-# directly.
-
-myafq = GroupAFQ.from_qsiprep(
- qsi_dir=op.join(afd.afq_home, 'HBN'),
- participant_labels=['NDARAA948VFH'],
- tracking_params=tracking_params)
-
-#############################################################################
-# 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.
diff --git a/examples/tutorial_examples/plot_002_participant_afq_api.py b/examples/tutorial_examples/plot_002_participant_afq_api.py
deleted file mode 100644
index 408e3870e..000000000
--- a/examples/tutorial_examples/plot_002_participant_afq_api.py
+++ /dev/null
@@ -1,268 +0,0 @@
-"""
-======================================
-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
-
-##########################################################################
-# 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"])
-
-##########################################################################
-# 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")
-
-##########################################################################
-# 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")
-
-##########################################################################
-# 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.
diff --git a/examples/tutorial_examples/plot_003_rerun.py b/examples/tutorial_examples/plot_003_rerun.py
deleted file mode 100644
index 818f97324..000000000
--- a/examples/tutorial_examples/plot_003_rerun.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-================
-Re-running pyAFQ
-================
-Sometimes you want to change arguments and re-run pyAFQ. This could be to
-try different parameters, to run on changed data, or re-run after updating
-parameters due to an error.
-
-pyAFQ saves derivatives as it goes, so if you re-run pyAFQ after changing
-parameters, it could use derivatives from previous runs with the
-old parameters.
-
-To solve this, use the myafq.clobber() or myafq.cmd_outputs() methods. They
-are the same methods. They will delete previous derivatives so you can
-re-run your pipeline.
-"""
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-import AFQ.definitions.image as afm
-import os.path as op
-import os
-
-
-##########################################################################
-# We start with some example data. The data we will use here is
-# generated from HBN
-# We then setup our myafq object which we will use to demonstrate
-# the clobber method.
-
-afd.fetch_hbn_preproc(["NDARAA948VFH"])
-
-tracking_params = dict(n_seeds=100,
- random_seeds=True,
- rng_seed=2022,
- trx=True)
-
-pve = afm.PVEImages(
- afm.ImageFile(
- suffix="probseg", filters={"scope": "qsiprep", "label": "CSF"}),
- afm.ImageFile(
- suffix="probseg", filters={"scope": "qsiprep", "label": "GM"}),
- afm.ImageFile(
- suffix="probseg", filters={"scope": "qsiprep", "label": "WM"}))
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'HBN'),
- dwi_preproc_pipeline='qsiprep',
- t1_preproc_pipeline='qsiprep',
- participant_labels=['NDARAA948VFH'],
- pve=pve,
- tracking_params=tracking_params)
-
-###################
-# Delete Everything
-# -----------------
-# To delete all pyAFQ outputs in the output directory, simply call::
-#
-# myafq.cmd_outputs()
-#
-# or::
-#
-# myafq.clobber()
-#
-# This is equivalent to running ``rm -r`` on all pyAFQ outputs. After this,
-# you can re-run your pipeline from scratch.
-#
-# Here, we will delete everything and re-run with a different b0 threshold.
-# The b0_threshold determines which b-values are considered b0.
-# The default is 50.
-
-myafq.cmd_outputs()
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'HBN'),
- dwi_preproc_pipeline='qsiprep',
- t1_preproc_pipeline='qsiprep',
- participant_labels=['NDARAA948VFH'],
- b0_threshold=100,
- tracking_params=tracking_params,
- pve=pve)
-
-myafq.export("b0")
-
-####################
-# Delete Some Things
-# ------------------
-# To delete only specific types of derivatives while preserving others,
-# use the ``dependent_on`` parameter::
-#
-# # Delete only tractography-dependent files
-# myafq.cmd_outputs(dependent_on="track")
-#
-# # Delete only bundle recognition-dependent files
-# myafq.cmd_outputs(dependent_on="recog")
-#
-# # Delete only profiling-dependent files
-# myafq.cmd_outputs(dependent_on="prof")
-#
-# You can also specify exceptions - files to preserve::
-#
-# # Delete all outputs except the tractography
-# myafq.cmd_outputs(exceptions=["streamlines"])
-#
-# Here, we will change the tractography parameters, but we want to keep all
-# derivatives not dependent on tractography. Typically, this means keeping
-# The mapping from MNI space and fitted models, but deleting recognized
-# bundles and tract profiles.
-
-myafq.clobber(dependent_on="track")
-
-tracking_params = dict(n_seeds=100,
- random_seeds=True,
- max_angle=60,
- rng_seed=12,
- trx=True)
-
-myafq = GroupAFQ(
- bids_path=op.join(afd.afq_home, 'HBN'),
- dwi_preproc_pipeline='qsiprep',
- t1_preproc_pipeline='qsiprep',
- participant_labels=['NDARAA948VFH'],
- b0_threshold=100,
- tracking_params=tracking_params,
- pve=pve)
-
-myafq.export("streamlines")
-
-##################
-# Move Some Things
-# ----------------
-# The ``cmd_outputs`` method is flexible and can perform other file operations
-# besides deletion. For example, to copy files::
-
-# # Copy only files dependent on tractography
-# myafq.cmd_outputs(
-# cmd="cp",
-# dependent_on="track",
-# suffix="/path/to/backup/")
-
-# Note: The method automatically adds ``-r``
-# for "cp" and "rm" operations.
-
-# Create backup directory
-backup_dir = op.join(afd.afq_home, "HBN_backup")
-os.makedirs(backup_dir, exist_ok=True)
-
-# Move the outupts of AFQ to this directory
-myafq.cmd_outputs(cmd="mv", suffix=backup_dir)
-
-##############
-# How It Works
-# ------------
-# The method works by:
-# 1. Identifying all pyAFQ outputs in the output directory
-# 2. Filtering based on the ``dependent_on`` parameter (if provided)
-# 3. Removing any files listed in ``exceptions``
-# 4. Executing the specified command (``rm``, ``mv``, ``cp`` etc.) on the remaining files
-# 5. Resetting the workflow to ensure subsequent runs regenerate affected derivatives
-#
-# We plan to automate this process in the future.
diff --git a/examples/tutorial_examples/plot_004_export.py b/examples/tutorial_examples/plot_004_export.py
deleted file mode 100644
index 667321178..000000000
--- a/examples/tutorial_examples/plot_004_export.py
+++ /dev/null
@@ -1,192 +0,0 @@
-"""
-======================================
-Exporting pyAFQ Results
-======================================
-
-This example shows how to use the ``export`` methods to obtain results from
-the ParticipantAFQ object. The ``export`` methods are used to calculate
-derived quantities from the data, such as DKI parameters, tract profiles,
-and bundle segmentations.
-
-"""
-import os
-import os.path as op
-import plotly
-
-from AFQ.api.participant import ParticipantAFQ
-import AFQ.data.fetch as afd
-import AFQ.definitions.image as afm
-
-##########################################################################
-# Preparing the ParticipantAFQ object
-# -------------------------
-# In this example, we will create a ParticipantAFQ object based on the
-# :doc:`plot_002_participant_afq_api` example. Please refer to that
-# example for a detailed description of the parameters.
-
-afd.fetch_hbn_preproc(["NDARAA948VFH"])
-
-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")
-
-output_dir = op.join(afd.afq_home, "HBN",
- "derivatives", "afq", "sub-NDARAA948VFH",
- "ses-HBNsiteRU", "dwi")
-os.makedirs(output_dir, exist_ok=True)
-
-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")))
-
-# Initialize the ParticipantAFQ object
-myafq = ParticipantAFQ(
- dwi_data_file=dwi_data_file,
- bval_file=bval_file,
- bvec_file=bvec_file,
- t1_file=t1_file,
- output_dir=output_dir,
- pve=pve,
- tracking_params={
- "n_seeds": 10000,
- "random_seeds": True,
- "rng_seed": 2022,
- "trx": True
- },
-)
-
-##########################################################################
-# The Export Method
-# ------------------------------------------------------------------
-# The ParticipantAFQ object has a method called ``export``, which allows the user
-# to calculate various derived quantities from the data.
-#
-# The ``export`` method can be called with a string argument that specifies the
-# type of quantity to be calculated. For example, ``myafq.export("OPTIONS")``.
-#
-# To list the available options, you can call the ``export`` method with the
-# argument "help". This will return a list of all the available options for
-# the ``export`` method.
-
-myafq.export("help")
-
-##########################################################################
-# .. note::
-#
-# No all options are possible even if they are valid. This will depend on
-# you dataset and pyAFQ API parameters. For example, you cannot calculate
-# DKI model from single shell data. Please refer to
-# :doc:`/howto/tractography_params` for more documentation.
-
-
-##########################################################################
-# Calculating DTI FA (Diffusion Tensor Imaging Fractional Anisotropy)
-# ------------------------------------------------------------------
-# 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")
-
-
-##########################################################################
-# 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")
-
-##########################################################################
-# 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])
-
-
-##########################################################################
-# The Export All Method
-# -----------------------------------------------------
-# There is a ``export_all`` method that will export all the results from the
-# AFQ pipeline. Undeerneath the hood, ``export_all`` calls a series of ``export``
-# methods. This method was added for convenience, and is the recommended method
-# to use when you want to export everything the results from the AFQ pipeline.
-#
-# This method will export the following results, if possible:
-# - Transformation maps and files
-# - Start and PVE mask images, associated diffusion scalar files
-# - Tractography, segmented tractography in to bundles
-# - Tract profiles, streamline counts, median tract length
-# - Visuzalizations of the bundles and tract profiles
-
-myafq.export_all()
-
-##########################################################################
-# The Export Up To Method
-# -----------------------------------------------------
-# The ``export_up_to`` method allows you to export results up to a certain
-# point (but not including) in the AFQ pipeline.
-#
-# For example, if you want to export all the results up to the bundle
-# segmentation step, you can call the ``export_up_to`` method with the
-# argument "bundles". This will export all the required derivatives
-# prior to the bundle segmentation step, where you can then take the
-# derivatives and debug your own custom segmentation pipeline.
-
-myafq.export_up_to("bundles")
diff --git a/examples/tutorial_examples/plot_005_viz.py b/examples/tutorial_examples/plot_005_viz.py
deleted file mode 100644
index 5b9c68133..000000000
--- a/examples/tutorial_examples/plot_005_viz.py
+++ /dev/null
@@ -1,436 +0,0 @@
-"""
-============================
-Visualizing AFQ derivatives
-============================
-
-Visualizing the results of a pyAFQ analysis is useful because it allows us to
-inspect the results of the analysis and to communicate the results to others.
-The pyAFQ pipeline produces a number of different kinds of outputs, including
-visualizations that can be used for quality control and for quick examination
-of the results of the analysis.
-
-However, when communicating the results of pyAFQ analysis, it is often useful
-to have more specific control over the visualization that is produced. In
-addition, it is often useful to have visualizations that are visually appealing
-and striking. In this tutorial, we will use the `fury `_
-library [1]_ to visualize outputs of pyAFQ as publication-ready figures.
-
-"""
-
-import os
-import os.path as op
-import nibabel as nib
-import numpy as np
-from math import radians
-
-from dipy.io.streamline import load_trk
-from dipy.tracking.streamline import transform_streamlines
-
-from fury import actor, window
-from fury.colormap import create_colormap
-
-
-import AFQ.data.fetch as afd
-from AFQ.viz.utils import PanelFigure
-
-##############################################################################
-# Get some data from HBN POD2
-# ----------------------------
-# The Healthy Brain Network Preprocessed Open Diffusion Derivatives (HBN POD2)
-# is a collection of resources based on the Healthy Brain Network dataset
-# [2, 3]_. HBN POD2 includes data derivatives - including pyAFQ derivatives -
-# from more than 2,000 subjects. The data and the derivatives can be browsed at
-# https://fcp-indi.s3.amazonaws.com/index.html#data/Projects/HBN/BIDS_curated/
-#
-# Here, we will visualize the results from one subject, together with their
-# anatomy and using several variations. We start by downloading their
-# pyAFQ-processed data using fetcher functions that download both the
-# preprocessed data, as well as the pyAFQ-processed data (Note that this
-# will take up about 1.75 GB of disk space):
-
-afd.fetch_hbn_preproc(["NDARAA948VFH"])
-study_path = afd.fetch_hbn_afq(["NDARAA948VFH"])[1]
-
-deriv_path = op.join(
- study_path, "derivatives")
-
-afq_path = op.join(
- deriv_path,
- 'afq',
- 'sub-NDARAA948VFH',
- 'ses-HBNsiteRU')
-
-bundle_path = op.join(afq_path,
- 'bundles')
-
-
-#############################################################################
-# Read data into memory
-# ----------------------
-# The bundle coordinates from pyAFQ are always saved in the reference frame of
-# the diffusion data from which they are generated, so we need an image file
-# with the dMRI coordinates as a reference for loading the data (we could also
-# use `"same"` here).
-
-fa_img = nib.load(op.join(afq_path,
- 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_model-DKI_FA.nii.gz'))
-fa = fa_img.get_fdata()
-sft_arc = load_trk(op.join(bundle_path,
- 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_space-RASMM_model-CSD_desc-prob-afq-ARC_L_tractography.trk'), fa_img)
-sft_cst = load_trk(op.join(bundle_path,
- 'sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_space-RASMM_model-CSD_desc-prob-afq-CST_L_tractography.trk'), fa_img)
-
-#############################################################################
-# Transform into the T1w reference frame
-# --------------------------------------
-# Our first goal is to visualize the bundles with a background of the
-# T1-weighted image, which provides anatomical context. We read in this data and
-# transform the bundle coordinates, first into the RASMM common coordinate frame
-# and then subsequently into the coordinate frame of the T1-weighted data (if
-# you find this confusing, you can brush up on this topic in the
-# `nibabel documentation `_).
-
-
-t1w_img = nib.load(op.join(deriv_path,
- 'qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-preproc_T1w.nii.gz'))
-t1w = t1w_img.get_fdata()
-sft_arc.to_rasmm()
-sft_cst.to_rasmm()
-arc_t1w = transform_streamlines(sft_arc.streamlines,
- np.linalg.inv(t1w_img.affine))
-cst_t1w = transform_streamlines(sft_cst.streamlines,
- np.linalg.inv(t1w_img.affine))
-
-
-#############################################################################
-# Visualizing bundles with principal direction coloring
-# -----------------------------------------------------
-# The first visualization we will create will have the streamlines colored
-# according to their direction. The color of each streamline will be RGB encoded
-# according to the RAS of the average orientation of its segments. This is the
-# default behavior in fury.
-#
-# Fury uses "actors" to render different kind of graphics. For the bundles, we
-# will use the `line` actor. These objects are wrappers around the `vtkActor
-# class `_, so methods of
-# that class (like `GetProperty()`) are available to use. We like to set the
-# aesthetics of the streamlines, so that they are rendered as tubes and with
-# slightly thicker line-width than the default. We create a function that sets
-# these properties of the line actor via the `GetProperty` method. We will
-# reuse this function later on, also setting the key-word arguments to the call
-# to `actor.line`, but for now we use the default setting, which colors each
-# streamline based on the RAS orientation, and we set the line width to 8.
-
-
-arc_actor = actor.streamlines(arc_t1w, thickness=8)
-cst_actor = actor.streamlines(cst_t1w, thickness=8)
-
-
-#############################################################################
-# Slicer actors
-# -------------
-# The anatomical image is rendered using `slicer` actors. These are actors that
-# visualize one slice of a three dimensional volume. We call the function on the
-# T1-weighted data, selecting the x slice that is half-way through the x
-# dimension of the image (`shape[0]`) and the z slice that is a third of a
-# way through that x dimension of the image (`shape[-1]`).
-# We set the visibility of the y slice to `False`
-
-slicer = actor.data_slicer(t1w,
- visibility=(
- True, False, True),
- initial_slices=(
- t1w.shape[0] // 2,
- t1w.shape[1] // 2,
- t1w.shape[-1] // 3))
-
-#############################################################################
-# Making a `scene`
-# -----------------
-# The next kind of fury object we will be working with is a `window.Scene`
-# object. This is the (3D!) canvas on which we are drawing the actors. We
-# initialize this object and call the `scene.add` method to add the actors.
-
-scene = window.Scene()
-
-scene.add(arc_actor)
-scene.add(cst_actor)
-scene.add(slicer)
-
-#############################################################################
-# Showing the visualization
-# -------------------------
-# If you are working in an interactive session, you can call::
-#
-# window.show(scene, size=(1200, 1200))
-#
-# to see what the visualization looks like. This would pop up a window that will
-# show you the visualization as it is now. You can interact with this
-# visualization using your mouse to rotate the image in 3D, and mouse+ctrl or
-# mouse+shift to pan and rotate it in plane, respectively. Use the scroll up and
-# scroll down in your mouse to zoom in and out. Once you have found a view of
-# the data that you like, you can close the window (as long as its open, it is
-# blocking execution of any further commands in the Python interpreter!)
-
-#############################################################################
-# Record the visualization
-# -------------------------
-# If you are not working in an interactive session, or you have already figured
-# out the camera settings that work for you, you can go ahead and record the
-# image into a png file. We use a pretty high resolution here (2400 by 2400) so
-# that we get a nice crisp image. That also means the file is pretty large.
-
-out_folder = op.join(afd.afq_home, "VizExample")
-os.makedirs(out_folder, exist_ok=True)
-
-def save_png(scene, name):
- """Helper function to PNGs in this example."""
- show_m = window.ShowManager(
- scene=scene, window_type="offscreen",
- size=(2400, 2400)
- )
- window.update_camera(show_m.screens[0].camera, None, scene)
- show_m.screens[0].controller.rotate((0, radians(-90)), None)
- show_m.render()
- show_m.window.draw()
- show_m.snapshot(op.join(out_folder, name))
-
-save_png(scene, 'arc_cst1.png')
-
-
-############################################################################
-# Setting bundle colors
-# ---------------------
-# The default behavior produces a nice image! But we often want to be able to
-# differentiate streamlines based on the bundle to which they belong. For this,
-# we will set the color of each streamline, based on the bundle. We often use
-# the Tableau20 color palette to set the colors for the different bundles. The
-# `actor.line` initializer takes `colors` as a keyword argument and one of the
-# options to pass here is an RGB triplet, which will determine the color of all
-# of the streamlines in that actor. We can get the Tableau20 RGB triplets from
-# Matplotlib's colormap module. We initialize our bundle actors again and clear
-# the scene before adding these new actors and adding back the slice actors. We
-# then call `record` to create this new figure.
-
-from matplotlib.cm import tab20
-color_arc = tab20.colors[18]
-color_cst = tab20.colors[2]
-
-arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc)
-cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst)
-
-scene.clear()
-
-scene.add(arc_actor)
-scene.add(cst_actor)
-scene.add(slicer)
-
-save_png(scene, 'arc_cst2.png')
-
-#############################################################################
-# Adding core bundles with tract profiles
-# ---------------------------------------
-# Next, we'd like to add a representation of the core of each bundle and plot
-# the profile of some property along the length of the bundle. Another option
-# for setting streamline colors is for each point along a streamline to have a a
-# different color. Here, we will do this with only one streamline, which
-# represents the core of the fiber -- it's median trajectory, and with a profile
-# of the FA. This requires a few distinct steps. The first is adding another
-# actor for each of the core fibers. We determine the core bundle as the median
-# of the coordinates of the streamlines after each streamline is resampled to
-# 100 equi-distant points. In the next step, we extract the FA profiles for each
-# of the bundles, using the `afq_profile` function. Before we do this, we make
-# sure that the streamlines are back in the voxel coordinate frame of the FA
-# volume. The core bundle and FA profile are put together into a new line actor,
-# which we initialize as a thick tube (line width of 40) and with a call to
-# create a colormap from the FA profiles (we can choose which color-map to use
-# here. In this case, we chose `'viridis'`). Note that because we are passing
-# only one streamline into the line actor, we have to pass it inside of square
-# brackets (`[]`). This is because the `actor.line` initializer expects a
-# sequence of streamlines as input. Before plotting things again, we initialize
-# our bundle line actors again. This time, we set the opacity of the lines to
-# 0.1, so that they do not occlude the view of the core bundle visualizations.
-#
-# .. note::
-# In this case, the profile is FA, but you can use any sequence of 100
-# numbers in place of the FA profiles: group differences, p-values, etc.
-#
-
-from dipy.tracking.streamline import set_number_of_points
-core_arc = np.median(np.asarray(set_number_of_points(arc_t1w, 100)), axis=0)
-core_cst = np.median(np.asarray(set_number_of_points(cst_t1w, 100)), axis=0)
-
-
-from dipy.stats.analysis import afq_profile
-sft_arc.to_vox()
-sft_cst.to_vox()
-arc_profile = afq_profile(fa, sft_arc.streamlines, affine=np.eye(4))
-cst_profile = afq_profile(fa, sft_cst.streamlines, affine=np.eye(4))
-
-core_arc_actor = actor.streamlines(
- [core_arc],
- thickness=40,
- colors=create_colormap(arc_profile, name='viridis')
-)
-
-core_cst_actor = actor.streamlines(
- [core_cst],
- thickness=40,
- colors=create_colormap(cst_profile, name='viridis')
-)
-
-scene.clear()
-
-arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc, opacity=0.1)
-cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst, opacity=0.1)
-
-scene.add(arc_actor)
-scene.add(cst_actor)
-scene.add(slicer)
-scene.add(core_arc_actor)
-scene.add(core_cst_actor)
-
-save_png(scene, 'arc_cst3.png')
-
-#############################################################################
-# Adding ROIs
-# -----------
-# Another element that we can add into the visualization are volume renderings
-# of regions of interest. For example, the waypoint ROIs that were used to
-# define the bundles. Here, we will add the waypoints that were used to define
-# the arcuate fasciculus in this subject. We start by reading this data in.
-# Because it is represented in the space of the diffusion data, it too needs to
-# be resampled into the T1-weighted space, before being visualized. After
-# resampling, we might have some values between 0 and 1 because of the
-# interpolation from the low resolution of the diffusion into the high
-# resolution of the T1-weighted. We will include in the volume rendering any
-# values larger than 0. The main change from the previous visualizations is the
-# addition of a `contour_from_roi` actor for each of the ROIs. We select another
-# color from the Tableau 20 palette to represent this, and use an opacity of
-# 0.5.
-#
-# .. note::
-# In this case, the surface rendered is of the waypoint ROIs, but very
-# similar code can be used to render other surfaces, provided a volume that
-# contains that surface. For example, the gray-white matter boundary could
-# be visualized provided an array with a binary representation of the volume # enclosed in this boundary
-
-
-from dipy.align import resample
-
-waypoint1 = nib.load(
- op.join(
- afq_path,
- "ROIs", "sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_desc-ROI-ARC_L-1-include.nii.gz"))
-
-waypoint2 = nib.load(
- op.join(
- afq_path,
- "ROIs", "sub-NDARAA948VFH_ses-HBNsiteRU_acq-64dir_space-T1w_desc-preproc_dwi_desc-ROI-ARC_L-2-include.nii.gz"))
-
-waypoint1_xform = resample(waypoint1, t1w_img)
-waypoint2_xform = resample(waypoint2, t1w_img)
-waypoint1_data = waypoint1_xform.get_fdata() > 0
-waypoint2_data = waypoint2_xform.get_fdata() > 0
-
-scene.clear()
-
-arc_actor = actor.streamlines(arc_t1w, thickness=8, colors=color_arc)
-cst_actor = actor.streamlines(cst_t1w, thickness=8, colors=color_cst)
-
-scene.add(arc_actor)
-scene.add(cst_actor)
-scene.add(slicer)
-
-surface_color = tab20.colors[0]
-
-waypoint1_actor = actor.contour_from_roi(waypoint1_data,
- color=surface_color,
- opacity=0.5)
-
-waypoint2_actor = actor.contour_from_roi(waypoint2_data,
- color=surface_color,
- opacity=0.5)
-
-
-scene.add(waypoint1_actor)
-scene.add(waypoint2_actor)
-
-save_png(scene, 'arc_cst4.png')
-
-#############################################################################
-# Visualizing tracts and tract profiles with a "glass brain"
-# ----------------------------------------------------------
-# Displaying tracts together with slices of anatomical data is beautiful
-# but sometimes poses a challenge because of occlusion. Another option is
-# to visualize the data with a "glass brain". That is, a faint contour
-# showing where the edges of the brain are, so that the tracts are visible
-# and the anatomical context can be understood. One way to generate the
-# contour of the brain is to use a brain mask generated from anatomical data.
-# We set its display color to black (`[0, 0, 0]`) and its opacity to a very low
-# value so that the tracts can easily be seen. This visualization is best with
-# a bright background, so we set the background to white (`[1, 1, 1]`).
-# In addition, we will demonstrate another way to show tract profile
-# information (based on Luo et al. 2025 [4]_): here the tract profile is
-# interpolated onto each streamline in the tract and mapped to a colormap.
-# This vividly shows the variations in tract profile values over space.
-
-brain_mask_img = nib.load(op.join(deriv_path,
- 'qsiprep/sub-NDARAA948VFH/anat/sub-NDARAA948VFH_desc-brain_mask.nii.gz'))
-
-brain_mask_data = brain_mask_img.get_fdata()
-
-scene.clear()
-
-brain_actor = actor.contour_from_roi(brain_mask_data,
- color=[0, 0, 0],
- opacity=0.05)
-
-scene.add(brain_actor)
-
-for sl in arc_t1w:
- # interpolate the 100 tract profiles values to match the number of points
- # in the streamline:
- interpolated_values = np.interp(np.linspace(0, 1, len(sl)),
- np.linspace(0, 1, len(arc_profile)),
- arc_profile)
- colors = create_colormap(interpolated_values, name='Spectral')
- line_actor = actor.streamlines([sl], thickness=8, colors=colors)
- scene.add(line_actor)
-
-scene.background = (1, 1, 1)
-save_png(scene, 'arc_cst5.png')
-
-#############################################################################
-# Making a Figure out of many fury panels
-# ---------------------------------------
-# We can also make a figure that contains multiple panels, each of which
-# contains a different visualization. This is useful for communicating the
-# results of an analysis. Here, we will make a figure with four panels, using
-# some of the visualizations we have already created. We will use some
-# convenient methods from pyAFQ.
-
-pf = PanelFigure(3, 2, 6, 9)
-pf.add_img(op.join(out_folder, 'arc_cst1.png'), 0, 0)
-pf.add_img(op.join(out_folder, 'arc_cst2.png'), 1, 0)
-pf.add_img(op.join(out_folder, 'arc_cst3.png'), 0, 1)
-pf.add_img(op.join(out_folder, 'arc_cst5.png'), 1, 1)
-pf.format_and_save_figure(f"arc_cst_fig.png")
-
-#############################################################################
-# References
-# ----------
-# .. [1] Garyfallidis et al., (2021). FURY: advanced scientific visualization.
-# Journal of Open Source Software, 6(64), 3384,
-# https://doi.org/10.21105/joss.03384
-#
-# .. [2] 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.
-#
-# .. [3] 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.
-#
-# .. [4] Luo A, et al. Two Axes of White Matter Development. In prep.
-
diff --git a/examples/tutorial_examples/plot_006_bids_layout.py b/examples/tutorial_examples/plot_006_bids_layout.py
deleted file mode 100644
index 2327beb6e..000000000
--- a/examples/tutorial_examples/plot_006_bids_layout.py
+++ /dev/null
@@ -1,297 +0,0 @@
-"""
-====================
-How pyAFQ uses BIDS
-====================
-
-The pyAFQ API relies heavily on the
-`Brain Imaging Data Standard (BIDS) `_,
-a widely used standard for organizing and describing neuroimaging data. This
-means that the software assumes that its inputs are organized according to the
-BIDS specification and its outputs conform where possible with BIDS.
-
-.. note::
-
- Derivatives of processing diffusion MRI are not currently fully
- described in the existing BIDS specification, but describing these
- is part of an ongoing effort. Wherever possible, we conform with
- the draft implementation of the BIDS DWI derivatives available
- `here `_
-
-In this example, we will explore the use of BIDS in pyAFQ and see
-how BIDS allows us to extend and provide flexibility to the users
-of the software.
-"""
-
-import os
-import os.path as op
-
-import AFQ.api.bundle_dict as abd
-from AFQ.api.group import GroupAFQ
-import AFQ.data.fetch as afd
-import AFQ.definitions.image as afm
-
-
-##########################################################################
-# To interact with and query BIDS datasets, we use
-# `pyBIDS `_, which we import here:
-
-import bids
-from bids.layout import BIDSLayout
-
-##########################################################################
-# We start with some example data. The data we will use here is
-# generated from the
-# `Stanford HARDI dataset `_.
-# The call below fetches
-# this dataset and organized it within the `~/AFQ_data` folder in the BIDS
-# format.
-
-afd.organize_stanford_data(clear_previous_afq="all")
-
-##########################################################################
-# After doing that, we should have a folder that looks like this:
-#
-# | stanford_hardi
-# | ├── dataset_description.json
-# | └── derivatives
-# | ├── freesurfer
-# | │ ├── dataset_description.json
-# | │ └── sub-01
-# | │ └── ses-01
-# | │ └── anat
-# | │ ├── sub-01_ses-01_T1w.nii.gz
-# | │ └── sub-01_ses-01_seg.nii.gz
-# | └── vistasoft
-# | ├── dataset_description.json
-# | └── sub-01
-# | └── ses-01
-# | └── dwi
-# | ├── sub-01_ses-01_dwi.bvals
-# | ├── sub-01_ses-01_dwi.bvecs
-# | └── sub-01_ses-01_dwi.nii.gz
-#
-# The top level directory (`stanford_hardi`) is our overall BIDS dataset folder.
-# In many cases, this folder will include folders with raw data for each subject
-# in the dataset. In this case, we do not include the raw data folders and only
-# have the outputs of pipelines that were used to preprocess the data (e.g.,
-# correct the data for subject motion, eddy currents, and so forth).
-# In general, only the preprocessed diffusion data is required for pyAFQ to run.
-# See the :doc:`"Organizing your data" ` section of the
-# documentation for more details.
-# In this case, one folder contains derivative of the Freesurfer software and
-# another folder contains the DWI data that has been preprocessed with the
-# Vistasoft software.
-# pyAFQ provides facilities to segment tractography results obtained
-# using other software as well. For example, we often use
-# `qsiprep `_ to preprocess
-# our data and reconstruct tractographies with software such as
-# `MRTRIX `_. Here, we will demonstrate how to use
-# these reconstructions in the pyAFQ segmentation and tractometry pipeline
-# We fetch this data and add it as a separate pipeline
-# The following code will download a previously-created tractography and
-# organize it by adding it to the BIDS dataset folder and renaming them to be
-# BIDS-compliant (e.g., `sub-01_ses_01_dwi_tractography.trk`).
-
-afd.fetch_stanford_hardi_tractography()
-
-bids_path = op.join(op.expanduser('~'), 'AFQ_data', 'stanford_hardi')
-tractography_path = op.join(bids_path, 'derivatives', 'my_tractography')
-sub_path = op.join(tractography_path, 'sub-01', 'ses-01', 'dwi')
-
-seg_file = op.join(afd.afq_home, "stanford_hardi", "derivatives",
- "freesurfer", "sub-01", "ses-01", "anat",
- "sub-01_ses-01_seg.nii.gz")
-pve = afm.PVEImages(
- afm.LabelledImageFile(
- path=seg_file,
- inclusive_labels=[0]),
- afm.LabelledImageFile(
- path=seg_file,
- exclusive_labels=[0, 1, 2], combine="and"),
- afm.LabelledImageFile(
- path=seg_file,
- inclusive_labels=[1, 2]))
-
-os.makedirs(sub_path, exist_ok=True)
-os.rename(
- op.join(
- op.expanduser('~'),
- 'AFQ_data',
- 'stanford_hardi_tractography',
- 'full_segmented_cleaned_tractography.trk'),
- op.join(
- sub_path,
- 'sub-01_ses-01-dwi_tractography.trk'))
-
-afd.to_bids_description(
- tractography_path,
- **{"Name": "my_tractography",
- "PipelineDescription": {"Name": "my_tractography"},
- "GeneratedBy": [{"Name": "my_tractography"}]})
-
-
-###########################################################################
-# After we do that, our dataset folder should look like this:
-#
-# | stanford_hardi
-# | ├── dataset_description.json
-# | └── derivatives
-# | ├── freesurfer
-# | │ ├── dataset_description.json
-# | │ └── sub-01
-# | │ └── ses-01
-# | │ └── anat
-# | │ ├── sub-01_ses-01_T1w.nii.gz
-# | │ └── sub-01_ses-01_seg.nii.gz
-# | ├── my_tractography
-# | | ├── dataset_description.json
-# | │ └── sub-01
-# | │ └── ses-01
-# | │ └── dwi
-# | │ └── sub-01_ses-01-dwi_tractography.trk
-# | └── vistasoft
-# | ├── dataset_description.json
-# | └── sub-01
-# | └── ses-01
-# | └── dwi
-# | ├── sub-01_ses-01_dwi.bvals
-# | ├── sub-01_ses-01_dwi.bvecs
-# | └── sub-01_ses-01_dwi.nii.gz
-#
-# To explore the layout of these derivatives, we will initialize a
-# :class:`BIDSLayout` class instance to help us see what is in this dataset
-
-layout = bids.BIDSLayout(bids_path, derivatives=True)
-
-##########################################################################
-# Because there is no raw data in this BIDS layout (only derivatives),
-# pybids will report that there are no subjects and sessions:
-
-print(layout)
-
-##########################################################################
-# But a query on the derivatives will reveal the different derivatives that
-# are stored here:
-
-print(layout.derivatives)
-
-##########################################################################
-# We can use a :class:`bids.BIDSValidator` object to make sure that the
-# files within our data set are BIDS-compliant. For example, we can
-# extract the tractography derivatives part of our layout using:
-
-my_tractography = layout.derivatives["my_tractography"]
-
-##########################################################################
-# This variable is also a BIDS layout object. This object has a ``get``
-# method, which allows us to query and find specific items within the
-# layout. For example, we can ask for files that have a suffix consistent
-# with tractography results:
-
-tractography_files = my_tractography.get(suffix='tractography')
-
-##########################################################################
-# Or ask for files that have a ``.trk`` extension:
-
-tractography_files = my_tractography.get(extension='.trk')
-
-##########################################################################
-# In this case, both of these would produce the same result.
-
-tractography_file = tractography_files[0]
-print(tractography_file)
-
-##########################################################################
-# We can also get some more structured information about this file:
-
-print(tractography_file.get_entities())
-
-
-##########################################################################
-# We can use a :class:`bids.BIDSValidator` class instance to validate that
-# this file is compliant with the specification. Note that the validator
-# requires that the filename be provided relative to the root of the BIDS
-# dataset, so we have to split the string that contains the full path
-# of the tractography to extract only the part that is relative to the
-# root of the entire BIDS ``layout`` object:
-
-tractography_full_path = tractography_file.path
-tractography_relative_path = tractography_full_path.split(layout.root)[-1]
-
-validator = bids.BIDSValidator()
-print(validator.is_bids(tractography_relative_path))
-
-##########################################################################
-# Next, we specify the information we need to define the bundles that we are
-# interested in segmenting. In this case, we are going to use a list of
-# bundle names for the bundle info. These names refer to bundles for
-# which we already have clear definitions of the information
-# needed to segment them (e.g., waypoint ROIs and probability maps).
-# For an example that includes custom definition of bundle info, see the
-# `plot_callosal_tract_profile example `_.
-
-bundle_info = abd.default_bd()[
- "Left Inferior Longitudinal",
- "Right Inferior Longitudinal",
- "Left Arcuate",
- "Right Arcuate",
- "Left Corticospinal",
- "Right Corticospinal"]
-
-##########################################################################
-# Now, we can define our GroupAFQ object, pointing to the derivatives of the
-# `'my_tractography'` pipeline as inputs. This is done by setting the
-# `import_tract` key-word argument. We pass the
-# `bundle_info` defined above. We also point to the preprocessed
-# data that is in a `'dmriprep'` pipeline. Note that the pipeline name
-# is not necessarily the name of the folder it is in; the pipeline name is
-# defined in each pipeline's `dataset_description.json`. These data were
-# preprocessed with 'vistasoft', so this is the pipeline we'll point to
-# If we were using `'qsiprep'`, this is where we would pass that
-# string instead. If we did that, AFQ would look for a derivatives
-# folder called `'stanford_hardi/derivatives/qsiprep'` and find the
-# preprocessed DWI data within it. Finally, to speed things up
-# a bit, we also sub-sample the provided tractography. This is
-# done by defining the segmentation_params dictionary input.
-# To sub-sample to 10,000 streamlines, we define
-# `'nb_streamlines' = 10000`.
-
-my_afq = GroupAFQ(
- bids_path,
- dwi_preproc_pipeline='vistasoft',
- t1_preproc_pipeline='freesurfer',
- bundle_info=bundle_info,
- import_tract={
- "suffix": "tractography",
- "scope": "my_tractography"
- },
- pve=pve,
- segmentation_params={'nb_streamlines': 10000})
-
-##########################################################################
-# Finally, to run the segmentation and extract tract profiles, we call
-# The `export_all` method. This creates all of the derivative outputs of
-# AFQ within the 'stanford_hardi/derivatives/afq' folder.
-
-my_afq.export_all()
-
-
-##########################################################################
-# A few common issues that can hinder BIDS from working properly are:
-#
-# 1. Faulty `dataset_description.json` file. You need to make sure that the
-# file contains the right names for the pipeline. See above for an example
-# of that.
-# 2. File naming convention doesn't uniquely identify file with bids filters.
-
-
-##########################################################################
-# The outputs of AFQ are also BIDS compatible. Here we demonstrate how
-# to load the afq entities and show all files with the key-value pair
-# desc-bundles
-
-layout = BIDSLayout(bids_path)
-layout.add_derivatives(
- f'{bids_path}/derivatives/afq',
- config=['bids', 'derivatives'])
-print(layout.get(desc="bundles", return_type="filename"))
diff --git a/setup.cfg b/setup.cfg
index 9e6008590..220446d43 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,7 +49,7 @@ install_requires =
# plotly libraries
plotly==5.12.0
kaleido==0.2.1
- imageio>=2.0.0
+ imageio[ffmpeg,pyav]>=2.0.0
Pillow
matplotlib
altair
@@ -65,7 +65,7 @@ dev =
pytest
pytest-cov
flake8
- sphinx_gallery
+ myst-nb
sphinx_rtd_theme
numpydoc==1.2
sphinx-autoapi
@@ -74,10 +74,10 @@ dev =
pydata-sphinx-theme
sphinx-design
sphinxcontrib-bibtex
- myst-nb
wget
ruff>=0.14.10
pre-commit
+ ipython
fury =
fury>=2.0.0a7
fsl =