Skip to content
Open
Changes from all commits
Commits
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
131 changes: 131 additions & 0 deletions trx/viz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
"""Optional 3D visualization using FURY/VTK."""

import itertools
import logging

import numpy as np

try:
from dipy.viz import actor, colormap, window
import fury.utils as ut_vtk
from fury.utils import get_bounds
import vtk

fury_available = True
except ImportError:
fury_available = False


def display(
volume, volume_affine=None, streamlines=None, title="FURY", display_bounds=True
):
"""Display a volume with optional streamlines using fury.

Parameters
----------
volume : np.ndarray
3D volume to display.
volume_affine : np.ndarray or None, optional
Affine matrix for the volume; None assumes identity.
streamlines : sequence or None, optional
Streamlines to render as lines.
title : str, optional
Window title.
display_bounds : bool, optional
If True, draw bounding box and coordinate annotations.

Returns
-------
None
Opens an interactive visualization window when fury is available.
"""
if not fury_available:
logging.error(
"Fury library is missing, visualization functions are not available."
)
return None
volume = volume.astype(float)
scene = window.Scene()
scene.background((1.0, 0.5, 0.0))

# Show the X/Y/Z plane intersecting, mid-slices
slicer_actor_1 = actor.slicer(
volume,
affine=volume_affine,
value_range=(volume.min(), volume.max()),
interpolation="nearest",
opacity=0.8,
)
slicer_actor_2 = actor.slicer(
volume,
affine=volume_affine,
value_range=(volume.min(), volume.max()),
interpolation="nearest",
opacity=0.8,
)
slicer_actor_3 = actor.slicer(
volume,
affine=volume_affine,
value_range=(volume.min(), volume.max()),
interpolation="nearest",
opacity=0.8,
)
slicer_actor_1.display(y=volume.shape[1] // 2)
slicer_actor_2.display(x=volume.shape[0] // 2)
slicer_actor_3.display(z=volume.shape[2] // 2)

scene.add(slicer_actor_1)
scene.add(slicer_actor_2)
scene.add(slicer_actor_3)

# Bounding box to facilitate error detections
if display_bounds:
src = vtk.vtkCubeSource()
bounds = np.round(get_bounds(slicer_actor_1), 6)
src.SetBounds(bounds)
src.Update()
cube_actor = ut_vtk.get_actor_from_polydata(src.GetOutput())
cube_actor.GetProperty().SetRepresentationToWireframe()
scene.add(cube_actor)

# Show each corner's coordinates
corners = itertools.product(bounds[0:2], bounds[2:4], bounds[4:6])
for corner in corners:
text_actor = actor.text_3d(
"{}, {}, {}".format(*corner),
corner,
font_size=6,
justification="center",
)
scene.add(text_actor)

# Show the X/Y/Z dimensions
text_actor_x = actor.text_3d(
"{}".format(np.abs(bounds[0] - bounds[1])),
((bounds[0] + bounds[1]) / 2, bounds[2], bounds[4]),
font_size=10,
justification="center",
)
text_actor_y = actor.text_3d(
"{}".format(np.abs(bounds[2] - bounds[3])),
(bounds[0], (bounds[2] + bounds[3]) / 2, bounds[4]),
font_size=10,
justification="center",
)
text_actor_z = actor.text_3d(
"{}".format(np.abs(bounds[4] - bounds[5])),
(bounds[0], bounds[2], (bounds[4] + bounds[5]) / 2),
font_size=10,
justification="center",
)
scene.add(text_actor_x)
scene.add(text_actor_y)
scene.add(text_actor_z)

if streamlines is not None:
streamlines_actor = actor.line(
streamlines, colormap.line_colors(streamlines), opacity=0.25
)
scene.add(streamlines_actor)
window.show(scene, title=title, size=(800, 800))
Loading