import itertools
import warnings
import numpy as np
from dipy.testing.decorators import warning_for_keywords
from dipy.utils.optpkg import optional_package
from dipy.viz.gmem import GlobalHorizon
fury, have_fury, setup_module = optional_package("fury", min_version="0.10.0")
if have_fury:
from dipy.viz import actor, colormap, ui
[docs]
@warning_for_keywords()
def build_label(text, *, font_size=18, bold=False):
"""Simple utility function to build labels
Parameters
----------
text : str
font_size : int
bold : bool
Returns
-------
label : TextBlock2D
"""
label = ui.TextBlock2D()
label.message = text
label.font_size = font_size
label.font_family = "Arial"
label.justification = "left"
label.bold = bold
label.italic = False
label.shadow = False
label.actor.GetTextProperty().SetBackgroundColor(0, 0, 0)
label.actor.GetTextProperty().SetBackgroundOpacity(0.0)
label.color = (0.7, 0.7, 0.7)
return label
def _color_slider(slider):
slider.default_color = (1, 0.5, 0)
slider.track.color = (0.8, 0.3, 0)
slider.active_color = (0.9, 0.4, 0)
slider.handle.color = (1, 0.5, 0)
def _color_dslider(slider):
slider.default_color = (1, 0.5, 0)
slider.track.color = (0.8, 0.3, 0)
slider.active_color = (0.9, 0.4, 0)
slider.handles[0].color = (1, 0.5, 0)
slider.handles[1].color = (1, 0.5, 0)
[docs]
@warning_for_keywords()
def slicer_panel(
scene,
iren,
*,
data=None,
affine=None,
world_coords=False,
pam=None,
mask=None,
mem=None,
):
"""Slicer panel with slicer included
Parameters
----------
scene : Scene
Scene.
iren : Interactor
Interactor.
data : 3d ndarray
Data to be sliced.
affine : 4x4 ndarray
Affine matrix.
world_coords : bool
If True then the affine is applied.
peaks : PeaksAndMetrics
Default None
mem :
Returns
-------
panel : Panel
"""
if mem is None:
mem = GlobalHorizon()
orig_shape = data.shape
print("Original shape", orig_shape)
ndim = data.ndim
tmp = data
if ndim == 4:
if orig_shape[-1] > 3:
orig_shape = orig_shape[:3]
# Sometimes, first volume is null, so we try the next one.
for i in range(orig_shape[-1]):
tmp = data[..., i]
value_range = np.percentile(data[..., i], q=[2, 98])
if np.sum(np.diff(value_range)) != 0:
break
if orig_shape[-1] == 3:
value_range = (0, 1.0)
mem.slicer_rgb = True
if ndim == 3:
value_range = np.percentile(tmp, q=[2, 98])
if np.sum(np.diff(value_range)) == 0:
msg = "Your data does not have any contrast. "
msg += "Please, check the value range of your data."
warnings.warn(msg, stacklevel=2)
if not world_coords:
affine = np.eye(4)
image_actor_z = actor.slicer(
tmp,
affine=affine,
value_range=value_range,
interpolation="nearest",
picking_tol=0.025,
)
tmp_new = image_actor_z.resliced_array()
if len(data.shape) == 4:
if data.shape[-1] == 3:
print("Resized to RAS shape ", tmp_new.shape)
else:
print("Resized to RAS shape ", tmp_new.shape + (data.shape[-1],))
else:
print("Resized to RAS shape ", tmp_new.shape)
shape = tmp_new.shape
if pam is not None:
peaks_actor_z = actor.peak_slicer(
pam.peak_dirs, peaks_values=None, mask=mask, affine=affine, colors=None
)
slicer_opacity = 1.0
image_actor_z.opacity(slicer_opacity)
image_actor_x = image_actor_z.copy()
x_midpoint = int(np.round(shape[0] / 2))
image_actor_x.display_extent(
x_midpoint, x_midpoint, 0, shape[1] - 1, 0, shape[2] - 1
)
image_actor_y = image_actor_z.copy()
y_midpoint = int(np.round(shape[1] / 2))
image_actor_y.display_extent(
0, shape[0] - 1, y_midpoint, y_midpoint, 0, shape[2] - 1
)
scene.add(image_actor_z)
scene.add(image_actor_x)
scene.add(image_actor_y)
if pam is not None:
scene.add(peaks_actor_z)
line_slider_z = ui.LineSlider2D(
min_value=0,
max_value=shape[2] - 1,
initial_value=shape[2] / 2,
text_template="{value:.0f}",
length=140,
)
_color_slider(line_slider_z)
def change_slice_z(slider):
z = int(np.round(slider.value))
mem.slicer_curr_actor_z.display_extent(0, shape[0] - 1, 0, shape[1] - 1, z, z)
if pam is not None:
mem.slicer_peaks_actor_z.display_extent(
0, shape[0] - 1, 0, shape[1] - 1, z, z
)
mem.slicer_curr_z = z
scene.reset_clipping_range()
line_slider_x = ui.LineSlider2D(
min_value=0,
max_value=shape[0] - 1,
initial_value=shape[0] / 2,
text_template="{value:.0f}",
length=140,
)
_color_slider(line_slider_x)
def change_slice_x(slider):
x = int(np.round(slider.value))
mem.slicer_curr_actor_x.display_extent(x, x, 0, shape[1] - 1, 0, shape[2] - 1)
scene.reset_clipping_range()
mem.slicer_curr_x = x
mem.window_timer_cnt += 100
line_slider_y = ui.LineSlider2D(
min_value=0,
max_value=shape[1] - 1,
initial_value=shape[1] / 2,
text_template="{value:.0f}",
length=140,
)
_color_slider(line_slider_y)
def change_slice_y(slider):
y = int(np.round(slider.value))
mem.slicer_curr_actor_y.display_extent(0, shape[0] - 1, y, y, 0, shape[2] - 1)
scene.reset_clipping_range()
mem.slicer_curr_y = y
# TODO there is some small bug when starting the app the handles
# are sitting a bit low
double_slider = ui.LineDoubleSlider2D(
length=140,
initial_values=value_range,
min_value=tmp.min(),
max_value=tmp.max(),
shape="square",
)
_color_dslider(double_slider)
def apply_colormap(r1, r2):
if mem.slicer_rgb:
return
if mem.slicer_colormap == "disting":
# use distinguishable colors
rgb = colormap.distinguishable_colormap(nb_colors=256)
rgb = np.asarray(rgb)
else:
# use matplotlib colormaps
rgb = colormap.create_colormap(
np.linspace(r1, r2, 256), name=mem.slicer_colormap, auto=True
)
N = rgb.shape[0]
lut = colormap.LookupTable()
lut.SetNumberOfTableValues(N)
lut.SetRange(r1, r2)
for i in range(N):
r, g, b = rgb[i]
lut.SetTableValue(i, r, g, b)
lut.SetRampToLinear()
lut.Build()
mem.slicer_curr_actor_z.output.SetLookupTable(lut)
mem.slicer_curr_actor_z.output.Update()
def on_change_ds(slider):
values = slider._values
r1, r2 = values
apply_colormap(r1, r2)
# TODO trying to see why there is a small bug in double slider
# double_slider.left_disk_value = 0
# double_slider.right_disk_value = 98
# double_slider.update(0)
# double_slider.update(1)
double_slider.on_change = on_change_ds
opacity_slider = ui.LineSlider2D(
min_value=0.0,
max_value=1.0,
initial_value=slicer_opacity,
length=140,
text_template="{ratio:.0%}",
)
_color_slider(opacity_slider)
def change_opacity(slider):
slicer_opacity = slider.value
mem.slicer_curr_actor_x.opacity(slicer_opacity)
mem.slicer_curr_actor_y.opacity(slicer_opacity)
mem.slicer_curr_actor_z.opacity(slicer_opacity)
volume_slider = ui.LineSlider2D(
min_value=0,
max_value=data.shape[-1] - 1,
initial_value=0,
length=140,
text_template="{value:.0f}",
shape="square",
)
_color_slider(volume_slider)
def change_volume(istyle, obj, slider):
vol_idx = int(np.round(slider.value))
mem.slicer_vol_idx = vol_idx
scene.rm(mem.slicer_curr_actor_x)
scene.rm(mem.slicer_curr_actor_y)
scene.rm(mem.slicer_curr_actor_z)
tmp = data[..., vol_idx]
image_actor_z = actor.slicer(
tmp,
affine=affine,
value_range=value_range,
interpolation="nearest",
picking_tol=0.025,
)
tmp_new = image_actor_z.resliced_array()
mem.slicer_vol = tmp_new
z = mem.slicer_curr_z
image_actor_z.display_extent(0, shape[0] - 1, 0, shape[1] - 1, z, z)
mem.slicer_curr_actor_z = image_actor_z
mem.slicer_curr_actor_x = image_actor_z.copy()
if pam is not None:
mem.slicer_peaks_actor_z = peaks_actor_z
x = mem.slicer_curr_x
mem.slicer_curr_actor_x.display_extent(x, x, 0, shape[1] - 1, 0, shape[2] - 1)
mem.slicer_curr_actor_y = image_actor_z.copy()
y = mem.slicer_curr_y
mem.slicer_curr_actor_y.display_extent(0, shape[0] - 1, y, y, 0, shape[2] - 1)
mem.slicer_curr_actor_z.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
mem.slicer_curr_actor_x.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
mem.slicer_curr_actor_y.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
scene.add(mem.slicer_curr_actor_z)
scene.add(mem.slicer_curr_actor_x)
scene.add(mem.slicer_curr_actor_y)
if pam is not None:
scene.add(mem.slicer_peaks_actor_z)
r1, r2 = double_slider._values
apply_colormap(r1, r2)
istyle.force_render()
def left_click_picker_callback(obj, ev):
"""Get the value of the clicked voxel and show it in the panel."""
event_pos = iren.GetEventPosition()
obj.picker.Pick(event_pos[0], event_pos[1], 0, scene)
i, j, k = obj.picker.GetPointIJK()
res = mem.slicer_vol[i, j, k]
try:
message = f"{res:.3f}"
except TypeError:
message = f"{res[0]:.3f} {res[1]:.3f} {res[2]:.3f}"
picker_label.message = f"({str(i)}, {str(j)}, {str(k)}) {message}"
mem.slicer_vol_idx = 0
mem.slicer_vol = tmp_new
mem.slicer_curr_actor_x = image_actor_x
mem.slicer_curr_actor_y = image_actor_y
mem.slicer_curr_actor_z = image_actor_z
if pam is not None:
# change_volume.peaks_actor_z = peaks_actor_z
mem.slicer_peaks_actor_z = peaks_actor_z
mem.slicer_curr_actor_x.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
mem.slicer_curr_actor_y.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
mem.slicer_curr_actor_z.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
if pam is not None:
mem.slicer_peaks_actor_z.AddObserver(
"LeftButtonPressEvent", left_click_picker_callback, 1.0
)
mem.slicer_curr_x = int(np.round(shape[0] / 2))
mem.slicer_curr_y = int(np.round(shape[1] / 2))
mem.slicer_curr_z = int(np.round(shape[2] / 2))
line_slider_x.on_change = change_slice_x
line_slider_y.on_change = change_slice_y
line_slider_z.on_change = change_slice_z
double_slider.on_change = on_change_ds
opacity_slider.on_change = change_opacity
volume_slider.handle_events(volume_slider.handle.actor)
volume_slider.on_left_mouse_button_released = change_volume
line_slider_label_x = build_label(text="X Slice")
line_slider_label_x.visibility = True
x_counter = itertools.count()
def label_callback_x(obj, event):
line_slider_label_x.visibility = not line_slider_label_x.visibility
line_slider_x.set_visibility(line_slider_label_x.visibility)
cnt = next(x_counter)
if line_slider_label_x.visibility and cnt > 0:
scene.add(mem.slicer_curr_actor_x)
else:
scene.rm(mem.slicer_curr_actor_x)
iren.Render()
line_slider_label_x.actor.AddObserver("LeftButtonPressEvent", label_callback_x, 1.0)
line_slider_label_y = build_label(text="Y Slice")
line_slider_label_y.visibility = True
y_counter = itertools.count()
def label_callback_y(obj, event):
line_slider_label_y.visibility = not line_slider_label_y.visibility
line_slider_y.set_visibility(line_slider_label_y.visibility)
cnt = next(y_counter)
if line_slider_label_y.visibility and cnt > 0:
scene.add(mem.slicer_curr_actor_y)
else:
scene.rm(mem.slicer_curr_actor_y)
iren.Render()
line_slider_label_y.actor.AddObserver("LeftButtonPressEvent", label_callback_y, 1.0)
line_slider_label_z = build_label(text="Z Slice")
line_slider_label_z.visibility = True
z_counter = itertools.count()
def label_callback_z(obj, event):
line_slider_label_z.visibility = not line_slider_label_z.visibility
line_slider_z.set_visibility(line_slider_label_z.visibility)
cnt = next(z_counter)
if line_slider_label_z.visibility and cnt > 0:
scene.add(mem.slicer_curr_actor_z)
else:
scene.rm(mem.slicer_curr_actor_z)
iren.Render()
line_slider_label_z.actor.AddObserver("LeftButtonPressEvent", label_callback_z, 1.0)
opacity_slider_label = build_label(text="Opacity")
volume_slider_label = build_label(text="Volume")
picker_label = build_label(text="")
double_slider_label = build_label(text="Colormap")
slicer_panel_label = build_label(text="Slicer panel", bold=True)
def label_colormap_callback(obj, event):
if mem.slicer_colormap_cnt == len(mem.slicer_colormaps) - 1:
mem.slicer_colormap_cnt = 0
else:
mem.slicer_colormap_cnt += 1
cnt = mem.slicer_colormap_cnt
mem.slicer_colormap = mem.slicer_colormaps[cnt]
double_slider_label.message = mem.slicer_colormap
values = double_slider._values
r1, r2 = values
apply_colormap(r1, r2)
iren.Render()
double_slider_label.actor.AddObserver(
"LeftButtonPressEvent", label_colormap_callback, 1.0
)
# volume_slider.on_right_mouse_button_released = change_volume2
def label_opacity_callback(obj, event):
if opacity_slider.value == 0:
opacity_slider.value = 100
opacity_slider.update()
slicer_opacity = 1
else:
opacity_slider.value = 0
opacity_slider.update()
slicer_opacity = 0
mem.slicer_curr_actor_x.opacity(slicer_opacity)
mem.slicer_curr_actor_y.opacity(slicer_opacity)
mem.slicer_curr_actor_z.opacity(slicer_opacity)
iren.Render()
opacity_slider_label.actor.AddObserver(
"LeftButtonPressEvent", label_opacity_callback, 1.0
)
if data.ndim == 4:
panel_size = (320, 400 + 100)
if data.ndim == 3:
panel_size = (320, 300 + 100)
panel = ui.Panel2D(
size=panel_size, position=(870, 10), color=(1, 1, 1), opacity=0.1, align="right"
)
ys = np.linspace(0, 1, 10)
panel.add_element(line_slider_z, coords=(0.42, ys[1]))
panel.add_element(line_slider_y, coords=(0.42, ys[2]))
panel.add_element(line_slider_x, coords=(0.42, ys[3]))
panel.add_element(opacity_slider, coords=(0.42, ys[4]))
panel.add_element(double_slider, coords=(0.42, (ys[7] + ys[8]) / 2.0))
if data.ndim == 4:
if data.shape[-1] > 3:
panel.add_element(volume_slider, coords=(0.42, ys[6]))
panel.add_element(line_slider_label_z, coords=(0.1, ys[1]))
panel.add_element(line_slider_label_y, coords=(0.1, ys[2]))
panel.add_element(line_slider_label_x, coords=(0.1, ys[3]))
panel.add_element(opacity_slider_label, coords=(0.1, ys[4]))
panel.add_element(double_slider_label, coords=(0.1, (ys[7] + ys[8]) / 2.0))
if data.ndim == 4:
if data.shape[-1] > 3:
panel.add_element(volume_slider_label, coords=(0.1, ys[6]))
panel.add_element(picker_label, coords=(0.2, ys[5]))
panel.add_element(slicer_panel_label, coords=(0.05, 0.9))
scene.add(panel)
# initialize colormap
r1, r2 = value_range
apply_colormap(r1, r2)
return panel