-
Notifications
You must be signed in to change notification settings - Fork 182
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
Tutorial on using ellipsoid actor to visualize tensor ellipsoids for DTI #818
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
07e7d32
updated fetcher with new dmri data
tvcastillod 4a0822a
added tutorial introduction
tvcastillod fde8eb5
added base code implementation
tvcastillod a530c0c
Merge branch 'master' into dt-tutorial
tvcastillod 80d3fc8
updated first part of tutorial
tvcastillod adc8342
updated second part of tutorial
tvcastillod a301515
updated tutorial with show manager
tvcastillod 89534bd
added missing visualizations
tvcastillod 6a52e1a
included visual comparison
tvcastillod d5220df
adjustment of whole brain visualization
tvcastillod 8e97473
corrected text description in tensor comparison
tvcastillod File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,329 @@ | ||
""" | ||
=============================================================================== | ||
Display Tensor Ellipsoids for DTI using tensor_slicer vs ellipsoid actor | ||
=============================================================================== | ||
This tutorial is intended to show two ways of displaying diffusion tensor | ||
ellipsoids for DTI visualization. The first is using the usual | ||
``tensor_slicer`` that allows us to slice many tensors as ellipsoids. The | ||
second is the generic ``ellipsoid`` actor that can be used to display different | ||
amount of ellipsoids. | ||
|
||
We start by importing the necessary modules: | ||
""" | ||
import itertools | ||
|
||
import numpy as np | ||
|
||
from dipy.io.image import load_nifti | ||
|
||
from fury import window, actor, ui | ||
from fury.actor import _fa, _color_fa | ||
from fury.data import fetch_viz_dmri, read_viz_dmri | ||
from fury.primitive import prim_sphere | ||
|
||
############################################################################### | ||
# Now, we fetch and load the data needed to display the Diffusion Tensor | ||
# Images. | ||
|
||
fetch_viz_dmri() | ||
|
||
############################################################################### | ||
# The tensor ellipsoids are expressed as eigenvalues and eigenvectors which are | ||
# the decomposition of the diffusion tensor that describes the water diffusion | ||
# within a voxel. | ||
|
||
slice_evecs, _ = load_nifti(read_viz_dmri('slice_evecs.nii.gz')) | ||
slice_evals, _ = load_nifti(read_viz_dmri('slice_evals.nii.gz')) | ||
roi_evecs, _ = load_nifti(read_viz_dmri('roi_evecs.nii.gz')) | ||
roi_evals, _ = load_nifti(read_viz_dmri('roi_evals.nii.gz')) | ||
whole_brain_evecs, _ = load_nifti(read_viz_dmri('whole_brain_evecs.nii.gz')) | ||
whole_brain_evals, _ = load_nifti(read_viz_dmri('whole_brain_evals.nii.gz')) | ||
|
||
############################################################################### | ||
# Using tensor_slicer actor | ||
# ========================= | ||
# First we must define the 3 parameters needed to use the ``tensor_slicer`` | ||
# actor, which correspond to the eigenvalues, the eigenvectors, and the sphere. | ||
# For the sphere we use ``prim_sphere`` which provide vertices and triangles of | ||
# the spheres. These are labeled as 'repulsionN' with N been the number of | ||
# vertices that made up the sphere, which have a standard number of 100, 200, | ||
# and 724 vertices. | ||
|
||
vertices, faces = prim_sphere('repulsion100', True) | ||
|
||
|
||
############################################################################### | ||
# As we need to provide a sphere object we create a class Sphere to which we | ||
# assign the values obtained from vertices and faces. | ||
|
||
class Sphere: | ||
def __init__(self, vertices, faces): | ||
self.vertices = vertices | ||
self.faces = faces | ||
|
||
|
||
sphere100 = Sphere(vertices, faces) | ||
|
||
############################################################################### | ||
# Now we are ready to create the ``tensor_slicer`` actor with the values of a | ||
# brain slice. We also define the scale so that the tensors are not so large | ||
# and overlap each other. | ||
|
||
tensor_slice = actor.tensor_slicer(evals=slice_evals, evecs=slice_evecs, | ||
sphere=sphere100, scale=.3) | ||
|
||
############################################################################### | ||
# Next, we set up a new scene to add and visualize the tensor ellipsoids | ||
# created. | ||
|
||
scene = window.Scene() | ||
scene.background([255, 255, 255]) | ||
scene.add(tensor_slice) | ||
|
||
# Create show manager | ||
showm = window.ShowManager(scene, size=(600, 600)) | ||
|
||
# Enables/disables interactive visualization | ||
interactive = False | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(showm.scene, size=(600, 600), out_path='tensor_slice_100.png') | ||
|
||
############################################################################### | ||
# If we zoom in at the scene to see with detail the tensor ellipsoids displayed | ||
# with the different spheres, we get the following results. | ||
|
||
scene.roll(10) | ||
scene.pitch(90) | ||
showm = window.ShowManager(scene, size=(600, 600), order_transparent=True) | ||
showm.scene.zoom(50) | ||
|
||
if interactive: | ||
showm.render() | ||
showm.start() | ||
|
||
window.record(showm.scene, out_path='tensor_slice_100_zoom.png', | ||
size=(600, 300), reset_camera=False) | ||
|
||
tvcastillod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
############################################################################### | ||
# To render the same tensor slice using a different sphere we redefine the | ||
# vertices and faces of the sphere using prim_sphere with other sphere | ||
# specification, as 'repulsion200' or 'repulsion724'. | ||
# | ||
# Now we clear the scene for the next visualization, and revert the scene | ||
# rotations. | ||
|
||
showm.scene.clear() | ||
showm.scene.pitch(-90) | ||
showm.scene.roll(-10) | ||
|
||
|
||
############################################################################### | ||
# Using ellipsoid actor | ||
# ===================== | ||
# In order to use the ``ellipsoid`` actor to display the same tensor slice we | ||
# need to set additional parameters. For this purpose, we define a helper | ||
# function to facilitate the correct setting of the parameters before passing | ||
# them to the actor. | ||
|
||
def get_params(evecs, evals): | ||
# We define the centers which corresponds to the ellipsoids positions. | ||
valid_mask = np.abs(evecs).max(axis=(-2, -1)) > 0 | ||
indices = np.nonzero(valid_mask) | ||
centers = np.asarray(indices).T | ||
|
||
# We need to pass the data of the axes and lengths of the ellipsoid as a | ||
# ndarray, so it is necessary to rearrange the data of the eigenvectors and | ||
# eigenvalues. | ||
fevecs = evecs[indices] | ||
fevals = evals[indices] | ||
|
||
# We need to define the colors of the ellipsoids following the default | ||
# coloring in tensor_slicer that is uses _color_fa that is a way to map | ||
# colors to each tensor based on the fractional anisotropy (FA) of each | ||
# diffusion tensor. | ||
colors = _color_fa(_fa(fevals), fevecs) | ||
|
||
return centers, fevecs, fevals, colors | ||
|
||
|
||
############################################################################### | ||
# With this we now have the values we need to define the centers, axes, | ||
# lengths, and colors of the ellipsoids. | ||
|
||
centers, evecs, evals, colors = get_params(slice_evecs, slice_evals) | ||
|
||
############################################################################### | ||
# Now, we can use the ``ellipsoid`` actor to create the tensor ellipsoids as | ||
# follows. | ||
|
||
tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, | ||
lengths=evals, scales=.6) | ||
showm.scene.add(tensors) | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(scene, size=(600, 600), out_path='tensor_slice_sdf.png') | ||
|
||
############################################################################### | ||
# Thus, one can see that the same result is obtained, however there is a | ||
# difference in the visual quality and this is because the ``ellipsoid`` actor | ||
# uses raymarching technique, so the objects that are generated are smoother | ||
# since they are not made with polygons but defined by an SDF function. Next we | ||
# can see in more detail the tensor ellipsoids generated. | ||
|
||
scene.roll(10) | ||
scene.pitch(90) | ||
showm = window.ShowManager(scene, size=(600, 600), order_transparent=True) | ||
showm.scene.zoom(50) | ||
|
||
if interactive: | ||
showm.render() | ||
showm.start() | ||
|
||
window.record(showm.scene, out_path='tensor_slice_sdf_zoom.png', | ||
size=(600, 300), reset_camera=False) | ||
|
||
showm.scene.clear() | ||
showm.scene.pitch(-90) | ||
showm.scene.roll(-10) | ||
|
||
############################################################################### | ||
# Visual quality comparison | ||
# ========================= | ||
# One can see that there is a different on the visual quality of both ways of | ||
# displaying tensors and this is because ``tensor_slicer`` uses polygons while | ||
# ``ellipsoid`` uses raymarching. Let's display both implementations at the | ||
# same time, so we can see this in more detail. | ||
# | ||
# We first set up the required data and create the actors. | ||
|
||
mevals = np.array([1.4, 1.0, 0.35]) * 10 ** (-3) | ||
mevecs = np.array([[2/3, -2/3, 1/3], [1/3, 2/3, 2/3], [2/3, 1/3, -2/3]]) | ||
|
||
evals = np.zeros((1, 1, 1, 3)) | ||
evecs = np.zeros((1, 1, 1, 3, 3)) | ||
|
||
evals[..., :] = mevals | ||
evecs[..., :, :] = mevecs | ||
|
||
vertices, faces = prim_sphere('repulsion200', True) | ||
sphere200 = Sphere(vertices, faces) | ||
vertices, faces = prim_sphere('repulsion724', True) | ||
sphere724 = Sphere(vertices, faces) | ||
|
||
tensor_100 = actor.tensor_slicer(evals=evals, evecs=evecs, | ||
sphere=sphere100, scale=1.0) | ||
tensor_200 = actor.tensor_slicer(evals=evals, evecs=evecs, | ||
sphere=sphere200, scale=1.0) | ||
tensor_724 = actor.tensor_slicer(evals=evals, evecs=evecs, | ||
sphere=sphere724, scale=1.0) | ||
|
||
centers, evecs, evals, colors = get_params(evecs=evecs, evals=evals) | ||
tensor_sdf = actor.ellipsoid(centers=centers, axes=evecs, lengths=evals, | ||
colors=colors, scales=2.0) | ||
|
||
############################################################################### | ||
# Next, we made use of `GridUI` which allows us to add the actors in a grid and | ||
# interact with them individually. | ||
|
||
objects = [tensor_100, tensor_200, tensor_724, tensor_sdf] | ||
text = [actor.vector_text('Tensor 100'), actor.vector_text('Tensor 200'), | ||
actor.vector_text('Tensor 724'), actor.vector_text('Tensor SDF')] | ||
|
||
grid_ui = ui.GridUI(actors=objects, captions=text, cell_padding=.1, | ||
caption_offset=(-0.7, -2.5, 0), dim=(1, 4)) | ||
|
||
scene = window.Scene() | ||
scene.background([255, 255, 255]) | ||
scene.zoom(3.5) | ||
scene.set_camera(position=(3.2, -20, 12), focal_point=(3.2, 0.0, 0.0)) | ||
showm = window.ShowManager(scene, size=(560, 200)) | ||
showm.scene.add(grid_ui) | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(showm.scene, size=(560, 200), out_path='tensor_comparison.png', | ||
reset_camera=False, magnification=2) | ||
|
||
showm.scene.clear() | ||
|
||
############################################################################### | ||
# Visualize a larger amount of data | ||
# ================================= | ||
# With ``tensor_slicer`` is possible to visualize more than one slice using | ||
# ``display_extent()``. Here we can see an example of a region of interest | ||
# (ROI) using a sphere of 100 vertices. | ||
|
||
tensor_roi = actor.tensor_slicer(evals=roi_evals, evecs=roi_evecs, | ||
sphere=sphere100, scale=.3) | ||
|
||
data_shape = roi_evals.shape[:3] | ||
tensor_roi.display_extent( | ||
0, data_shape[0], 0, data_shape[1], 0, data_shape[2]) | ||
|
||
showm.size = (600, 600) | ||
showm.scene.background([0, 0, 0]) | ||
showm.scene.add(tensor_roi) | ||
showm.scene.azimuth(87) | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(showm.scene, size=(600, 600), out_path='tensor_roi_100.png') | ||
|
||
showm.scene.clear() | ||
|
||
############################################################################### | ||
# We can do it also with a sphere of 200 vertices, but if we try to do it with | ||
# one of 724 the visualization can no longer be rendered. In contrast, we can | ||
# visualize the ROI with the ``ellipsoid`` actor without compromising the | ||
# quality of the visualization. | ||
|
||
centers, evecs, evals, colors = get_params(roi_evecs, roi_evals) | ||
|
||
tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, | ||
lengths=evals, scales=.6) | ||
showm.scene.add(tensors) | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(showm.scene, size=(600, 600), out_path='tensor_roi_sdf.png') | ||
|
||
showm.scene.clear() | ||
|
||
############################################################################### | ||
# In fact, although with a low performance, this actor allows us to visualize | ||
# the whole brain, which contains a much larger amount of data, to be exact | ||
# 184512 tensor ellipsoids are displayed at the same time. | ||
|
||
centers, evecs, evals, colors = get_params(whole_brain_evecs, | ||
whole_brain_evals) | ||
|
||
# We remove all the noise around the brain to have a better visualization. | ||
fil = [len(set(elem)) != 1 for elem in evals] | ||
centers = np.array(list(itertools.compress(centers, fil))) | ||
colors = np.array(list(itertools.compress(colors, fil))) | ||
evecs = np.array(list(itertools.compress(evecs, fil))) | ||
evals = np.array(list(itertools.compress(evals, fil))) | ||
|
||
tensors = actor.ellipsoid(centers=centers, colors=colors, axes=evecs, | ||
lengths=evals, scales=.6) | ||
|
||
scene = window.Scene() | ||
scene.add(tensors) | ||
scene.pitch(180) | ||
showm = window.ShowManager(scene, size=(600, 600)) | ||
|
||
if interactive: | ||
showm.start() | ||
|
||
window.record(showm.scene, size=(600, 600), reset_camera=False, | ||
out_path='tensor_whole_brain_sdf.png') | ||
|
||
showm.scene.clear() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you create an issue with this part of the tutorial. We should remove the need of a sphere object.