Hi everyone,
I’m a beginner at VTK (using the Python wrapping) and am currently having a bug where the program silently crashes when vtk.vtkRenderWindow.Render() is called.
Currently I’m running the following code on my system (excuse my messy code):
import vtk
import numpy as np
from mmw.utils import mmw_numba
opacity_map_default = np.array([[0, 0],
[0.2, 1]
],
dtype=np.float32)
color_map_default = np.array([[0, 0, 0, 0],
[0.1, 0.8, 0.8, 0.8],
[1, 1, 1, 1]
],
dtype=np.float32)
background_default = [0.2, 0.2, 0.2]
class vtkTimerCallback():
def __init__(self, volume_list, renderer, render_window):
self.volume_list = volume_list
self.renderer = renderer
self.render_window = render_window
self.current_volume = 0
self.num_volumes = len(self.volume_list)
def execute(self, obj, event):
volumeCollection = self.renderer.GetVolumes()
num_active_volumes = volumeCollection.GetNumberOfItems()
if num_active_volumes > 0:
for i in range(num_active_volumes):
self.renderer.RemoveVolume(volumeCollection.GetItemAsObject(i))
self.renderer.AddVolume(self.volume_list[self.current_volume])
self.render_window.Render()
if self.current_volume >= self.num_volumes - 1:
self.current_volume = 0
else:
self.current_volume += 1
def exitCheck(obj, event):
'''A simple function to be called when the user decides to quit the application.'''
if obj.GetEventPending() != 0:
obj.SetAbortRender(1)
def multi_volume_render(data, voxel_size, dynamic_range, fps=1,
color_map=color_map_default,
opacity_map=opacity_map_default,
background=background_default, attachment_window=None, interactor=None):
"""Render a numpy volume using VTK. The data can be a 3D static volume or a 4D animated volume
Parameters
----------
data : [numpy 3d/4d float32 or complex64 array]
The volume(s) to render with VTK. A 4D volume will be animated.
SHould be shape (nx, ny, nz) or (num frames, nx, ny, nz)
voxel_size : [float / list[3]]
This is the voxel size of the volume. If all dimensions are equal pass a
single float otherwise pass a list of length 3
dynamic_range : [float]
The desired dynamic range to render the volume with
fps : [float] (default 1)
The desired frames per second to render with vtk. If data is a 3d volume
this parameter is not used
color_map : [numpy 2D float array]
A numpy array mapping the intensity scaled from 0 to 1 to a color map.
The shape should by (number points, 4). each row is (intensity, R, G, B)
with everything normalized from 0 to 1
opacity_map : [numpy 2D float array]
A numpy array mapping the intensity scaled from 0 to 1 to an opacity map.
The shape should by (number points, 2). each row is
(intensity, opacity) with everything normalized from 0 to 1
background : [list[3]]
This is the RGB values for the background values range from 0-1
"""
if data.ndim == 3:
animated = False
elif data.ndim == 4:
animated = True
else:
raise ValueError(f'data must be either 3 or 4 dimensions, is : {data.ndim}')
if data.dtype != np.float32 and data.dtype != np.complex64:
raise ValueError(f'data must be float32 or complex64, is : {data.dtype}')
if isinstance(voxel_size, float):
voxel_size = [voxel_size, voxel_size, voxel_size]
if isinstance(voxel_size, list) and len(voxel_size) == 3:
pass
else:
raise ValueError('voxel_size must be a float or a list of length 3')
if not isinstance(background, list) and len(voxel_size) != 3:
raise ValueError('background must a list of length 3')
if color_map.ndim != 2 and color_map.shape[1] != 4:
raise ValueError(f'color_map my be of dimension 2 and the second dimension must'
f'be length 4\n color_map.shape = {color_map.hshape}')
if opacity_map.ndim != 2 and opacity_map.shape[1] != 2:
raise ValueError(f'opacity_map my be of dimension 2 and the second dimension must'
f'be length 2\n opacity_map.shape = {opacity_map.hshape}')
print(f'Converting to {dynamic_range} dB dynamic range...')
scale_max = 255
# convert to u8 image
data_u8 = mmw_numba.fast_dbscale(data, dynamic_range)
data_u8 -= data_u8.min()
data_u8 *= scale_max / data_u8.max()
data_u8 = data_u8.astype(np.uint8)
if data.ndim == 3:
data_u8 = np.expand_dims(data_u8, axis=0)
num_frames, nx, ny, nz = data_u8.shape
opacityFunc = vtk.vtkPiecewiseFunction()
for i in range(opacity_map.shape[0]):
opacityFunc.AddPoint(scale_max * opacity_map[i, 0], opacity_map[i, 1])
colorFunc = vtk.vtkColorTransferFunction()
for i in range(color_map.shape[0]):
colorFunc.AddRGBPoint(scale_max * color_map[i, 0], # intensity
color_map[i, 1], # R
color_map[i, 2], # G
color_map[i, 3]) # B
volume_list = []
for fn in range(num_frames):
print(f'Creating VTK volume {fn + 1} / {num_frames}')
dataImporter = vtk.vtkImageImport()
dataImporter.SetDataSpacing(voxel_size[0], voxel_size[1], voxel_size[2])
dataImporter.SetDataOrigin(0, 0, 0)
data_bytes = data_u8[fn].tobytes()
dataImporter.CopyImportVoidPointer(data_bytes, len(data_bytes))
dataImporter.SetDataScalarTypeToUnsignedChar()
dataImporter.SetNumberOfScalarComponents(1)
dataImporter.SetDataExtent(0, nx - 1, 0, ny - 1, 0, nz - 1)
dataImporter.SetWholeExtent(0, nx - 1, 0, ny - 1, 0, nz - 1)
# volume properties
volumeProperty = vtk.vtkVolumeProperty()
volumeProperty.SetColor(colorFunc)
volumeProperty.SetScalarOpacity(opacityFunc)
volumeProperty.ShadeOn()
volumeProperty.SetAmbient(0.0)
volumeProperty.SetDiffuse(1.0)
volumeProperty.SetSpecular(0.0)
volumeProperty.SetSpecularPower(0.0)
volumeProperty.SetInterpolationTypeToLinear()
# create volume mapper
volumeMapper = vtk.vtkGPUVolumeRayCastMapper()
# volumeMapper.SetRequestedRenderModeToGPU()
volumeMapper.SetInputConnection(0, dataImporter.GetOutputPort())
volumeMapper.SetBlendModeToComposite()
# The class vtkVolume is used to pair the previously declared volume as
# well as the properties to be used when rendering that volume.
volume = vtk.vtkVolume()
multivolume = vtk.vtkMultiVolume()
volume.SetMapper(volumeMapper)
volume.SetProperty(volumeProperty)
multivolume.SetVolume(volume, 0)
multiVolumeMapper = vtk.vtkGPUVolumeRayCastMapper()
multiVolumeMapper.UseJitteringOn()
# multiVolumeMapper.SetRequestedRenderModeToGPU()
multivolume.SetMapper(multiVolumeMapper)
volume_list.append(multivolume)
# With almost everything else ready, its time to initialize the renderer
# and window, as well as creating a method for exiting the application
renderer = vtk.vtkRenderer()
if attachment_window == None:
print("WINDOWMAKER POWER")
renderWin = vtk.vtkRenderWindow()
else:
renderWin = attachment_window
renderWin.SetSize(600, 1200)
renderWin.AddRenderer(renderer)
renderer.SetBackground(background[0], background[1], background[2])
if interactor == None:
renderInteractor = vtk.vtkRenderWindowInteractor()
else:
renderInteractor = interactor
# We add the volume to the renderer ...
renderer.AddVolume(volume_list[0])
renderInteractor.SetRenderWindow(renderWin)
# Tell the application to use the function as an exit check.
renderWin.AddObserver("AbortCheckEvent", exitCheck)
renderInteractor.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
try:
renderInteractor.Initialize()
except:
print("What happened here?")
if animated:
volume_timer = vtkTimerCallback(volume_list, renderer, renderWin)
renderInteractor.AddObserver('TimerEvent', volume_timer.execute)
timerId = renderInteractor.CreateRepeatingTimer(int(1 / fps * 1000))
# Because nothing will be rendered without any input, we order the first
# render manually before control is handed over to the main-loop.
print('Starting Renderer..')
pos = renderer.GetActiveCamera().GetPosition()
renderer.GetActiveCamera().SetPosition(5 * nx * voxel_size[0], ny // 2 * voxel_size[1], nz // 2 * voxel_size[2])
renderer.GetActiveCamera().SetFocalPoint(nx // 2 * voxel_size[0], ny // 2 * voxel_size[1], nz // 2 * voxel_size[2])
# renderer.GetActiveCamera().Azimuth(90)
# renderer.GetActiveCamera().Elevation(0)
# renderer.ResetCameraClippingRange() # crashes program
# renderer.ResetCamera() # crashes program
renderWin.Render() # crashes program
renderInteractor.Start()
sample_volume = np.array(
[
[
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6]
],
[
[3, 2, 3, 4, 5],
[1, 3, 3, 4, 5],
[2, 3, 8, 5, 6]
],
[
[1, 9, 3, 4, 5],
[1, 2, 3, 4, 5],
[2, 3, 4, 9, 6]
],
],
dtype=np.float32)
multi_volume_render(sample_volume, [1, 1, 1], 25, 10,
color_map_default,
opacity_map_default,
background_default)
I’ve determined using VSCode debug tools that renderWin.Render() is the exact last line that runs before the program crashes with no error message. Is there a reason why that is, and how would it be solved? I’m using vtk version 8.2.0.