Source code for dipy.viz.horizon.visualizer.slice

import warnings

import numpy as np
from scipy import stats

from dipy.testing.decorators import warning_for_keywords
from dipy.utils.optpkg import optional_package

fury, has_fury, setup_module = optional_package("fury", min_version="0.10.0")

if has_fury:
    from fury import actor
    from fury.utils import apply_affine


[docs] class SlicesVisualizer: @warning_for_keywords() def __init__( self, interactor, scene, data, *, affine=None, world_coords=False, percentiles=(0, 100), rgb=False, ): self._interactor = interactor self._scene = scene self._data = data self._affine = affine if not world_coords: self._affine = np.eye(4) self._slice_actors = [None] * 3 self._data_ndim = data.ndim self._data_shape = data.shape self._rgb = False self._percentiles = percentiles vol_data = self._data if ( self._data_ndim == 4 and rgb and self._data_shape[-1] == 3 ) or self._data_ndim == 3: self._rgb = True and not self._data_ndim == 3 self._int_range = np.percentile(vol_data, self._percentiles) _evaluate_intensities_range(self._int_range) else: if self._data_ndim == 4 and rgb and self._data_shape[-1] != 3: warnings.warn( "The rgb flag is enabled but the color " + "channel information is not provided", stacklevel=2, ) vol_data = self._volume_calculations(self._percentiles) self._vol_max = np.max(vol_data) self._vol_min = np.min(vol_data) self._resliced_vol = None print(f"Original shape: {self._data_shape}") self._create_and_resize_actors(vol_data, self._int_range) print(f"Resized to RAS shape: {self._data_shape}") self._sel_slices = np.rint(np.asarray(self._data_shape[:3]) / 2).astype(int) self._add_slice_actors_to_scene(self._sel_slices) self._picker_callback = None self._picked_voxel_actor = None def _volume_calculations(self, percentiles): for i in range(self._data.shape[-1]): vol_data = self._data[..., i] self._int_range = np.percentile(vol_data, percentiles) if np.sum(np.diff(self._int_range)) != 0: break else: if i < self._data_shape[-1] - 1: warnings.warn( f"Volume N°{i} does not have any contrast. " "Please, check the value ranges of your data. " "Moving to the next volume.", stacklevel=2, ) else: _evaluate_intensities_range(self._int_range) return vol_data def _add_slice_actors_to_scene(self, visible_slices): self._slice_actors[0].display_extent( visible_slices[0], visible_slices[0], 0, self._data_shape[1] - 1, 0, self._data_shape[2] - 1, ) self._slice_actors[1].display_extent( 0, self._data_shape[0] - 1, visible_slices[1], visible_slices[1], 0, self._data_shape[2] - 1, ) self._slice_actors[2].display_extent( 0, self._data_shape[0] - 1, 0, self._data_shape[1] - 1, visible_slices[2], visible_slices[2], ) for act in self._slice_actors: self._scene.add(act) def _create_and_resize_actors(self, vol_data, value_range): self._slice_actors[0] = actor.slicer( vol_data, affine=self._affine, value_range=value_range, interpolation="nearest", ) self._resliced_vol = self._slice_actors[0].resliced_array() self._slice_actors[1] = self._slice_actors[0].copy() self._slice_actors[2] = self._slice_actors[0].copy() for slice_actor in self._slice_actors: slice_actor.AddObserver( "LeftButtonPressEvent", self._left_click_picker_callback, 1.0 ) if self._data_ndim == 4 and not self._rgb: self._data_shape = self._resliced_vol.shape + (self._data.shape[-1],) else: self._data_shape = self._resliced_vol.shape def _left_click_picker_callback(self, obj, event): # TODO: Find out why this is not triggered when opacity < 1 event_pos = self._interactor.GetEventPosition() obj.picker.Pick(event_pos[0], event_pos[1], 0, self._scene) i, j, k = obj.picker.GetPointIJK() res = self._resliced_vol[i, j, k] try: message = f"{res:.2f}" except TypeError: message = f"{res[0]:.2f} {res[1]:.2f} {res[2]:.2f}" message = f"({i}, {j}, {k}) = {message}" self._picker_callback(message) # TODO: Fix this # self._replace_picked_voxel_actor(i, j, k) def _replace_picked_voxel_actor(self, x, y, z): if self._picked_voxel_actor: self._scene.rm(self._picked_voxel_actor) pnt = np.asarray([[x, y, z]]) pnt = apply_affine(self._affine, pnt) self._picked_voxel_actor = actor.dot(pnt, colors=(0.9, 0.4, 0.0), dot_size=10) self._scene.add(self._picked_voxel_actor)
[docs] def change_volume(self, prev_idx, next_idx, intensities, visible_slices): vol_data = self._data[..., prev_idx] # NOTE: Supported only in latests versions of scipy # percs = stats.percentileofscore(np.ravel(vol_data), intensities) perc_0 = stats.percentileofscore(np.ravel(vol_data), intensities[0]) perc_1 = stats.percentileofscore(np.ravel(vol_data), intensities[1]) vol_data = self._data[..., next_idx] value_range = np.percentile(vol_data, [perc_0, perc_1]) if np.sum(np.diff(self._int_range)) == 0: return False self._int_range = value_range self._vol_max = np.max(vol_data) self._vol_min = np.min(vol_data) for slice_actor in self._slice_actors: self._scene.rm(slice_actor) self._create_and_resize_actors(vol_data, self._int_range) self._add_slice_actors_to_scene(visible_slices) return True
[docs] def register_picker_callback(self, callback): self._picker_callback = callback
@property def data_shape(self): return self._data_shape @property def intensities_range(self): return self._int_range @property def selected_slices(self): return self._sel_slices @property def slice_actors(self): return self._slice_actors @property def volume_max(self): return self._vol_max @property def volume_min(self): return self._vol_min @property def rgb(self): return self._rgb
def _evaluate_intensities_range(intensities_range): if np.sum(np.diff(intensities_range)) == 0: raise ValueError( "Your data does not have any contrast. Please, check the " "value range of your data." )