Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slf123new #997

Merged
merged 26 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6577885
added new tutorial on SLF 1/2/3
jyeatman Aug 22, 2023
9cfbd7b
added more subjects to the example and properly pull ROIs from repo
jyeatman Aug 22, 2023
fc42453
finished up SLF example and visualization
jyeatman Aug 23, 2023
4f87787
minor improvements to group visuzliation
jyeatman Aug 23, 2023
aeecf29
udated tutorial documentation
jyeatman Aug 23, 2023
595b8cb
skip this in docs test suite
36000 Aug 23, 2023
818bb69
add wget to setup.cfg
36000 Aug 23, 2023
62485c5
update docs in tutorial
36000 Aug 23, 2023
4cb917b
fill out the new montage methods
36000 Aug 23, 2023
053a3c4
update calls to montage
36000 Aug 23, 2023
219d498
bf
36000 Aug 23, 2023
11db419
finalized SLF tutorial
jyeatman Aug 23, 2023
e1bf228
fixed visualization angles
jyeatman Aug 23, 2023
d88d3b4
changed cleaning parameters
jyeatman Aug 24, 2023
99b0337
updated to 12 good HBN subjects
jyeatman Aug 25, 2023
f3cbee8
updated parameters and fixed formatting as noted by arokem
jyeatman Aug 28, 2023
33c2df2
made a note about where ROIs come from
jyeatman Aug 28, 2023
9402c7d
Update AFQ/api/group.py
jyeatman Aug 28, 2023
7b1a511
Update examples/tutorial_examples/add_custom_bundle.py
arokem Aug 31, 2023
e67e85f
Update examples/tutorial_examples/add_custom_bundle.py
arokem Aug 31, 2023
bff5cf5
Update examples/tutorial_examples/plot_optic_radiations.py
arokem Aug 31, 2023
105e52c
Update examples/tutorial_examples/add_custom_bundle.py
arokem Aug 31, 2023
3e5fcdc
Update examples/tutorial_examples/add_custom_bundle.py
arokem Aug 31, 2023
c8f97a1
Update examples/tutorial_examples/add_custom_bundle.py
arokem Aug 31, 2023
c9ec511
Edits to example
arokem Aug 31, 2023
3dc3d48
Small edits to the OR example.
arokem Aug 31, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 39 additions & 26 deletions AFQ/api/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def cmd_outputs(self, cmd="rm", dependent_on=None, exceptions=[],

clobber = cmd_outputs # alias for default of cmd_outputs

def montage(self, bundle_name, size, view, slice_pos=None):
def montage(self, bundle_name, size, view, direc, slice_pos=None):
"""
Generate montage file(s) of a given bundle at a given angle.

Expand All @@ -561,7 +561,10 @@ def montage(self, bundle_name, size, view, slice_pos=None):
size : tuple of int
The number of columns and rows for each file.
view : str
Which view to display. Can be one of Sagittal, Coronal, or Axial.
Which view to display. Can be one of sagittal, coronal, or axial.
direc : str
Which direction to views. Can be one of left, right, top, bottom,
front, back
slice_pos : float, or None
If float, indicates the fractional position along the
perpendicular axis to the slice. Currently only works with plotly.
Expand All @@ -571,9 +574,19 @@ def montage(self, bundle_name, size, view, slice_pos=None):
-------
list of filenames of montage images
"""
if view not in ["Sagittal", "Coronal", "Axial"]:
direc = direc.lower()
view = view.lower()
if view in ["sagital", "saggital"]:
self.logger.warning("You don't know how to spell sagggitttal!")
view = "sagittal"

if view not in ["sagittal", "coronal", "axial"]:
raise ValueError(
"View must be one of: sagittal, coronal, or axial")

if direc not in ["left", "right", "top", "bottom", "front", "back"]:
raise ValueError(
"View must be one of: Sagittal, Coronal, or Axial")
"View must be one of: left, right, top, bottom, front, back")

tdir = tempfile.gettempdir()

Expand Down Expand Up @@ -603,21 +616,22 @@ def montage(self, bundle_name, size, view, slice_pos=None):

if slice_pos is not None:
slice_kwargs = {}
if view == "Sagittal":
if view == "sagittal":
slice_kwargs["x_pos"] = slice_pos
slice_kwargs["y_pos"] = None
slice_kwargs["z_pos"] = None
elif view == "Coronal":
elif view == "coronal":
slice_kwargs["x_pos"] = None
slice_kwargs["y_pos"] = slice_pos
slice_kwargs["z_pos"] = None
elif view == "Axial":
elif view == "axial":
slice_kwargs["x_pos"] = None
slice_kwargs["y_pos"] = None
slice_kwargs["z_pos"] = slice_pos

figure = viz_backend.visualize_volume(
b0,
best_scalar,
opacity=1.0,
flip_axes=flip_axes,
interact=False,
inline=False,
Expand All @@ -635,35 +649,34 @@ def montage(self, bundle_name, size, view, slice_pos=None):
figure=figure)

eye = {}
view_up = {}
if view == "Sagittal":
eye["x"] = 1
if view == "sagittal":
if direc == "left":
eye["x"] = -1
else:
eye["x"] = 1
eye["y"] = 0
eye["z"] = 0
view_up["x"] = 0
view_up["y"] = 1
view_up["z"] = 0
elif view == "Coronal":
elif view == "coronal":
eye["x"] = 0
eye["y"] = 1
if direc == "front":
eye["y"] = 1
else:
eye["y"] = -1
eye["z"] = 0
view_up["x"] = 0
view_up["y"] = 0
view_up["z"] = 1
elif view == "Axial":
elif view == "axial":
eye["x"] = 0
eye["y"] = 0
eye["z"] = 1
view_up["x"] = 1
view_up["y"] = 0
view_up["z"] = 0
if direc == "top":
eye["z"] = 1
else:
eye["z"] = -1

this_fname = tdir + f"/t{ii}.png"
if "plotly" in viz_backend.backend:

figure.update_layout(scene_camera=dict(
projection=dict(type="orthographic"),
up=view_up,
up={"x": 0, "y": 0, "z": 1},
eye=eye,
center=dict(x=0, y=0, z=0)))
figure.write_image(this_fname)
Expand All @@ -678,7 +691,7 @@ def montage(self, bundle_name, size, view, slice_pos=None):
figure.set_camera(
position=direc * data_shape,
focal_point=data_shape // 2,
view_up=tuple(view_up.values()))
view_up=(0, 0, 1))
figure.zoom(0.5)
window.snapshot(figure, fname=this_fname, size=(600, 600))

Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@
'examples_dirs': ['../../examples/howto_examples', '../../examples/tutorial_examples'],
# path where to save gallery generated examples
'gallery_dirs': ['howto/howto_examples', 'tutorials/tutorial_examples'],
'ignore_pattern': 'plot_baby_afq.py|cloudknot_hcp_example.py|cloudknot_example.py',
'ignore_pattern': 'plot_baby_afq.py|cloudknot_hcp_example.py|cloudknot_example.py|add_custom_bundle.py', # noqa
'image_scrapers': image_scrapers,
'reset_modules': (reset_progressbars),
'show_memory': True,
Expand Down
233 changes: 233 additions & 0 deletions examples/tutorial_examples/add_custom_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
"""
=================================================
Adding 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 Sami et al [1]_.

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 wget
import os
np.random.seed(1234)


#############################################################################
# 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:
jyeatman marked this conversation as resolved.
Show resolved Hide resolved

_, study_dir = afd.fetch_hbn_preproc([
'NDARKP893TWU',
'NDAREP505XAD',
'NDARKT540ZW0',
'NDARAG340ERT',
'NDAREM757NBG',
'NDARLL894HC3',
'NDARFY525TL2',
'NDARKV461KGZ',
'NDARUC851WHU',
'NDARMJ333WJM',
'NDARJG687YYX',
'NDARJA157YB3',
])

# 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
# a spot they live online but this code could be commented out and paths could be used to local ROIs on disk

roi_urls = ['https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/MFgL.nii.gz',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should explain what's going on here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emphasizing perhaps that these ROIs can be files that the user created themselves, but that in this case they are simply files stored somewhere on the internet (which happens to be another software library with a very similar name - this could get confusing for someone who doesn't know all the history...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arokem I just added this. Please edit as you see fit and I'll merge

'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/MFgR.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/PaL.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/PaR.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/PrgL.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/PrgR.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/SFgL.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/SFgR.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/SLFt_roi2_L.nii.gz',
'https://github.com/yeatmanlab/AFQ/raw/c762ca4c393f2105d4f444c44d9e4b4702f0a646/SLF123/ROIs/SLFt_roi2_R.nii.gz']
36000 marked this conversation as resolved.
Show resolved Hide resolved


#############################################################################
# Get ROIs and save to disk
# --------------------------------
# The goal of this tutorial is to demostrate how to segment new pathways based
# on ROIs that are saved to disk. We'll start off by donwloading some ROIs that are saved online

# Define and create the directory for the template ROIs
# op.expanduser("~") expands your ~ directory into the full string
arokem marked this conversation as resolved.
Show resolved Hide resolved
# and op.join joins these paths, to make ~/AFQ_data/SLF_ROIs/
template_dir = op.join(
op.expanduser("~"),
'AFQ_data/SLF_ROIs/')
os.makedirs(template_dir, exist_ok=True)

# Download the ROI files (the wget library functions like the `wget` unix command)
for roi_url in roi_urls:
wget.download(roi_url, template_dir)


#############################################################################
# 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
arokem marked this conversation as resolved.
Show resolved Hide resolved
# 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

bundles = abd.BundleDict({
"L_SLF1": {
"include": [
template_dir + 'SFgL.nii.gz',
template_dir + 'PaL.nii.gz'],
"exclude": [
template_dir + 'SLFt_roi2_L.nii.gz'],

"cross_midline": False,
},
"L_SLF2": {
"include": [
template_dir + 'MFgL.nii.gz',
template_dir + 'PaL.nii.gz'],
"exclude": [
template_dir + 'SLFt_roi2_L.nii.gz'],

"cross_midline": False,
},
"L_SLF3": {
"include": [
template_dir + 'PrgL.nii.gz',
template_dir + 'PaL.nii.gz'],
"exclude": [
template_dir + 'SLFt_roi2_L.nii.gz'],

"cross_midline": False,
}
})

#############################################################################
# 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.BundleDict()
#
# 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
# 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.

brain_mask_definition = ImageFile(
suffix="mask",
filters={'desc': 'brain',
'space': 'T1w',
'scope': 'qsiprep'})

my_afq = GroupAFQ(
bids_path=study_dir,
preproc_pipeline="qsiprep",
output_dir=op.join(study_dir, "derivatives", "afq_slf"),
brain_mask_definition=brain_mask_definition,
tracking_params={"n_seeds": 4,
"directions": "prob",
"odf_model": "CSD",
"seed_mask": RoiImage()},
clean_params={"clean_rounds": 20,
"length_threshold": 4,
"distance_threshold" : 2,
},
segmentation_params = {"parallel_segmentation": {"engine":"serial"}},
bundle_info=bundles)

# If you want to redo different stages you can use the clobber flag
arokem marked this conversation as resolved.
Show resolved Hide resolved
# To redo everying related to bundle recognition set my_afq.clobber(dependent_on='recog').
# This is useful when changing the bundles.
# The options for dependent_on are 'track' (to start over from tractography) or 'recog'
# to start over from bundle recognition
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. 2 rows 4 coluns) and finally is the view.
#
# .. 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
arokem marked this conversation as resolved.
Show resolved Hide resolved
# necessary to do this when running this type of analysis.

montage = my_afq.montage("L_SLF1", (3, 4), "Sagittal", "left", slice_pos=0.5)
shutil.copy(montage[0], op.split(montage[0])[-1])
montage = my_afq.montage("L_SLF2", (3, 4), "Sagittal", "left", slice_pos=0.5)
shutil.copy(montage[0], op.split(montage[0])[-1])
montage = my_afq.montage("L_SLF3", (3, 4), "Sagittal", "left", slice_pos=0.5)
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("all_bundles_figure")
# plotly.io.show(bundle_html["NDARAA948VFH"]['HBNsiteRU'])

#############################################################################
# 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
jyeatman marked this conversation as resolved.
Show resolved Hide resolved
# https://doi.org/10.21203/rs.3.rs-3282349/v1
#
# .. [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.
Loading