# -*- coding: utf-8 -*-
from dipy.reconst.shm import sh_to_sf, sh_to_sf_matrix
from fury import actor
import numpy as np
from scilpy.reconst.bingham import bingham_to_sf
from scilpy.viz.backends.fury import (create_contours_actor,
create_odf_actors,
create_peaks_actor,
set_display_extent)
from scilpy.viz.backends.vtk import contours_from_data
from scilpy.viz.color import generate_n_colors, lut_from_matplotlib_name
from scilpy.viz.utils import affine_from_offset
[docs]
def create_texture_slicer(texture, orientation, slice_index, *, mask=None,
value_range=None, opacity=1.0, offset=0.5,
lut=None, interpolation='nearest'):
"""
Create a texture displayed at a given offset (in the given orientation)
from the origin of the grid.
Parameters
----------
texture : np.ndarray (3d or 4d)
Texture image. Can be 3d for scalar data of 4d for RGB data, in which
case the values must be between 0 and 255.
orientation : str
Name of the axis to visualize. Choices are axial, coronal and sagittal.
slice_index : int
Index of the slice to visualize along the chosen orientation.
mask : np.ndarray, optional
Only the data inside the mask will be displayed. Defaults to None.
value_range : tuple (2,), optional
The range of values mapped to range [0, 1] for the texture image. If
None, it equals to (bg.min(), bg.max()). Defaults to None.
opacity : float
The opacity of the texture image. Opacity of 0.0 means transparent and
1.0 is completely visible. Defaults to 1.0.
offset : float
The offset of the texture image. Defaults to 0.5.
lut : str, vtkLookupTable, optional
Either a vtk lookup table or a matplotlib colormap name.
interpolation : str
Interpolation mode for the texture image. Choices are nearest or
linear. Defaults to nearest.
Returns
-------
slicer_actor : actor.slicer
Fury object containing the texture information.
"""
affine = affine_from_offset(orientation, offset)
if mask is not None:
texture[np.where(mask == 0)] = 0
if isinstance(lut, str):
_vl = value_range
if _vl is None:
_vl = (texture.min(), texture.max())
lut = lut_from_matplotlib_name(lut, _vl)
slicer_actor = actor.slicer(texture, value_range=value_range,
affine=affine, opacity=opacity,
lookup_colormap=lut,
interpolation=interpolation)
set_display_extent(slicer_actor, orientation, texture.shape, slice_index)
return slicer_actor
[docs]
def create_contours_slicer(data, contour_values, orientation, slice_index,
smoothing_radius=0., opacity=1., linewidth=3.,
color=[255, 0, 0]):
"""
Create an isocontour slicer at specifed contours values.
Parameters
----------
data : np.ndarray
Data from which to extract contours (mask, binary image, labels).
contour_values : list
Values at which to extract isocontours.
orientation : str
Name of the axis to visualize. Choices are axial, coronal and sagittal.
slice_index : int
Index of the slice to visualize along the chosen orientation.
smoothing_radius : float
Pre-smoothing to apply to the image before
computing the contour (in pixels).
opacity: float
Opacity of the contour.
linewidth : float
Thickness of the contour line.
color : tuple, list of int
Color of the contour in RGB [0, 255].
Returns
-------
contours_slicer : actor.slicer
Fury object containing the contours information.
"""
data = np.rot90(data.take([slice_index], orientation).squeeze())
contours_polydata = contours_from_data(data, contour_values,
smoothing_radius)
contours_slicer = create_contours_actor(contours_polydata, opacity,
linewidth, color)
# Equivalent of set_display_extent for polydata actors
position = [0, 0, 0]
position[orientation] = slice_index
if orientation == 0:
contours_slicer.SetOrientation(90, 0, 90)
elif orientation == 1:
contours_slicer.SetOrientation(90, 0, 0)
contours_slicer.SetPosition(*position)
return contours_slicer
[docs]
def create_peaks_slicer(data, orientation, slice_index, *, peak_values=None,
mask=None, color=None, peaks_width=1.0,
opacity=1.0, symmetric=False):
"""
Create a peaks slicer actor rendering a slice of the input peaks.
Parameters
----------
data : np.ndarray
Peaks data.
orientation : str
Name of the axis to visualize. Choices are axial, coronal and sagittal.
slice_index : int
Index of the slice to visualize along the chosen orientation.
peak_values : np.ndarray, optional
Peaks values. Defaults to None.
mask : np.ndarray, optional
Only the data inside the mask will be displayed. Defaults to None.
color : tuple (3,), optional
Color used for peaks. If None, a RGB colormap is used. Defaults to
None.
peaks_width : float
Width of peaks segments. Defaults to 1.0.
opacity : float
Opacity of the peaks. Defaults to 1.0.
symmetric : bool
If True, peaks are drawn for both peaks_dirs and -peaks_dirs. Else,
peaks are only drawn for directions given by peaks_dirs. Defaults to
False.
Returns
-------
slicer_actor : actor.peak_slicer
Fury object containing the peaks information.
"""
# Reshape peaks volume to XxYxZxNx3
data = data.reshape(data.shape[:3] + (-1, 3))
norm = np.linalg.norm(data, axis=-1)
# Only send non-empty data slices to render
zero_norms = np.sum(norm.reshape((-1, norm.shape[-1])), axis=0) == 0
if zero_norms.all():
raise ValueError('Peak slicer received an empty volume to render.')
data = data[..., ~zero_norms, :]
norm = norm[..., ~zero_norms]
# Normalize input data
data[norm > 0] /= norm[norm > 0].reshape((-1, 1))
# Instantiate peaks slicer
peaks_slicer = create_peaks_actor(data, mask, opacity=opacity,
linewidth=peaks_width, color=color,
lut_values=peak_values,
symmetric=symmetric)
set_display_extent(peaks_slicer, orientation, data.shape, slice_index)
return peaks_slicer
[docs]
def create_odf_slicer(sh_fodf, orientation, slice_index, sphere, sh_order,
sh_basis, full_basis, scale, sh_variance=None,
mask=None, nb_subdivide=None, radial_scale=False,
norm=False, colormap=None, variance_k=1,
variance_color=(255, 255, 255), is_legacy=True):
"""
Create a ODF slicer actor displaying a fODF slice. The input volume is a
3-dimensional grid containing the SH coefficients of the fODF for each
voxel at each voxel, with the grid dimension having a size of 1 along the
axis corresponding to the selected orientation.
Parameters
----------
sh_fodf : np.ndarray
Spherical harmonics of fODF data.
orientation : str
Name of the axis to visualize. Choices are axial, coronal and sagittal.
slice_index : int
Index of the slice to visualize along the chosen orientation.
sphere: DIPY Sphere
Sphere used for visualization.
sh_order : int
Maximum spherical harmonics order.
sh_basis : str
Type of basis for the spherical harmonics.
full_basis : bool
Boolean indicating if the basis is full or not.
scale : float
Scaling factor for FODF.
sh_variance : np.ndarray, optional
Spherical harmonics of the variance fODF data.
mask : np.ndarray, optional
Only the data inside the mask will be displayed. Defaults to None.
nb_subdivide : int, optional
Number of subdivisions for given sphere. If None, uses the given sphere
as is.
radial_scale : bool
If True, enables radial scale for ODF slicer.
norm : bool
If True, enables normalization of ODF slicer.
colormap : str, optional
Colormap for the ODF slicer. If None, a RGB colormap is used.
variance_k : float
Factor that multiplies sqrt(variance).
variance_color : tuple, optional
Color of the variance fODF data, in RGB.
is_legacy: bool
Whether the SH basis is used in legacy formats [True].
Returns
-------
odf_actor : actor.odf_slicer
Fury object containing the odf information.
var_actor : actor.odf_slicer
Fury object containing the odf variance information.
"""
# Subdivide the spheres if nb_subdivide is provided
if nb_subdivide is not None:
sphere = sphere.subdivide(n=nb_subdivide)
fodf = sh_to_sf(sh_fodf, sphere, sh_order, sh_basis,
full_basis=full_basis, legacy=is_legacy)
fodf_var = None
B_mat = None
if sh_variance is not None:
fodf_var = sh_to_sf(sh_variance, sphere, sh_order, sh_basis,
full_basis=full_basis, legacy=is_legacy)
else:
fodf = sh_fodf
B_mat = sh_to_sf_matrix(sphere, sh_order, sh_basis,
full_basis, return_inv=False)
odf_actor, var_actor = create_odf_actors(fodf, sphere, scale, fodf_var,
mask, radial_scale,
norm, colormap,
variance_k, variance_color,
B_mat=B_mat)
set_display_extent(odf_actor, orientation, sh_fodf.shape[:3], slice_index)
if sh_variance is not None:
set_display_extent(var_actor, orientation,
sh_fodf.shape[:3], slice_index)
return odf_actor, var_actor
[docs]
def create_bingham_slicer(data, orientation, slice_index,
sphere, color_per_lobe=False):
"""
Create a bingham fit slicer using a combination of odf_slicer actors
Parameters
----------
data: Array
Volume of shape (X, Y, Z, N_LOBES, NB_PARAMS) containing
the Bingham distributions parameters. Note, NB_PARAMS is usually 7.
One of X, Y, Z should be of value 1 (one slice).
orientation: str
Name of the axis to visualize. Choices are axial, coronal and sagittal.
slice_index: int
Index of the slice of interest along the chosen orientation.
sphere: DIPY Sphere
Sphere used for visualization.
color_per_lobe: bool
If true, each Bingham distribution is colored using a disting color.
Else, Bingham distributions are colored by their orientation.
Return
------
actors: list of fury odf_slicer actors
ODF slicer actors representing the Bingham distributions.
"""
shape = data.shape
if len(shape) != 5:
raise ValueError('Expecting bingham data to be 5D '
'(x, y, z, N_LOBES, NB_PARAMS), but got {}'
.format(shape))
nb_lobes = shape[-2]
colors = [c * 255 for c in generate_n_colors(nb_lobes)]
# lmax norm for normalization: first bingham param, averaged on lobes
lmaxnorm = np.max(np.abs(data[..., 0]), axis=-1)
bingham_sf = bingham_to_sf(data, sphere.vertices)
actors = []
for nn in range(nb_lobes):
sf = bingham_sf[..., nn, :]
sf[lmaxnorm > 0] /= lmaxnorm[lmaxnorm > 0][:, None]
color = colors[nn] if color_per_lobe else None
odf_actor, _ = create_odf_actors(sf, sphere, 0.5, colormap=color,
radial_scale=True)
set_display_extent(odf_actor, orientation, shape[:3], slice_index)
actors.append(odf_actor)
return actors