Loading code/episode_7/src/nova_tutorial/models/vtk.py 0 → 100644 +114 −0 Original line number Diff line number Diff line """Configuration for the VTK example.""" import numpy as np from pyvista import examples from vtk import vtkSLCReader from vtkmodules.vtkCommonDataModel import vtkPiecewiseFunction from vtkmodules.vtkRenderingCore import vtkColorTransferFunction, vtkVolume, vtkVolumeProperty from vtkmodules.vtkRenderingVolume import vtkFixedPointVolumeRayCastMapper KNEE_DATA = examples.download_knee_full() KNEE_DATAFILE = examples.download_knee_full(load=False) class VTKConfig: """Configuration class for the VTK example.""" max: float = KNEE_DATA.get_data_range()[1] min: float = KNEE_DATA.get_data_range()[0] def __init__(self) -> None: reader = vtkSLCReader() reader.SetFileName(KNEE_DATAFILE) mapper = vtkFixedPointVolumeRayCastMapper() mapper.SetInputConnection(reader.GetOutputPort()) lut = self.init_lut() pwf = self.init_pwf() volume_props = vtkVolumeProperty() volume_props.SetColor(lut) volume_props.SetScalarOpacity(pwf) volume_props.SetShade(0) volume_props.SetInterpolationTypeToLinear() self.volume = vtkVolume() self.volume.SetMapper(mapper) self.volume.SetProperty(volume_props) self.volume.SetVisibility(1) def get_volume(self) -> vtkVolume: return self.volume def init_lut(self) -> vtkColorTransferFunction: # This method defines the "Fast" colormap. # See https://www.kitware.com/new-default-colormap-and-background-in-next-paraview-release/ lut = vtkColorTransferFunction() lut.SetColorSpaceToRGB() lut.SetNanColor([0.0, 1.0, 0.0]) srgb = np.array( [ 0, 0.05639999999999999, 0.05639999999999999, 0.47, 0.17159223942480895, 0.24300000000000013, 0.4603500000000004, 0.81, 0.2984914818394138, 0.3568143826543521, 0.7450246485363142, 0.954367702893722, 0.4321287371255907, 0.6882, 0.93, 0.9179099999999999, 0.5, 0.8994959551205902, 0.944646394975174, 0.7686567142818399, 0.5882260353170073, 0.957107977357604, 0.8338185108985666, 0.5089156299842102, 0.7061412605695164, 0.9275207599610714, 0.6214389091739178, 0.31535705838676426, 0.8476395308725272, 0.8, 0.3520000000000001, 0.15999999999999998, 1, 0.59, 0.07670000000000013, 0.11947499999999994, ] ) for arr in np.split(srgb, len(srgb) / 4): lut.AddRGBPoint(arr[0], arr[1], arr[2], arr[3]) prev_min, prev_max = lut.GetRange() prev_delta = prev_max - prev_min node = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] next_delta = self.max - self.min for i in range(lut.GetSize()): lut.GetNodeValue(i, node) node[0] = next_delta * (node[0] - prev_min) / prev_delta + self.min lut.SetNodeValue(i, node) return lut def init_pwf(self) -> vtkPiecewiseFunction: pwf = vtkPiecewiseFunction() pwf.RemoveAllPoints() pwf.AddPoint(self.min, 0) pwf.AddPoint(self.max, 0.7) return pwf code/episode_7/src/nova_tutorial/view_models/main.py +7 −0 Original line number Diff line number Diff line Loading @@ -4,10 +4,12 @@ from typing import Any, Dict from nova.mvvm.interface import BindingInterface from pyvista import Plotter # just for typing from vtkmodules.vtkRenderingCore import vtkVolume # just for typing from ..models.main_model import MainModel from ..models.plotly import PlotlyConfig from ..models.pyvista import PyVistaConfig from ..models.vtk import VTKConfig class MainViewModel: Loading @@ -17,12 +19,14 @@ class MainViewModel: self.model = model self.plotly_config = PlotlyConfig() self.pyvista_config = PyVistaConfig() self.vtk_config = VTKConfig() self.plotly_config_bind = binding.new_bind( linked_object=self.plotly_config, callback_after_update=self.update_plotly_figure ) self.plotly_figure_bind = binding.new_bind() self.pyvista_config_bind = binding.new_bind(linked_object=self.pyvista_config) # We didn't add any controls for the VTK rendering, so there's no need to create a data binding here. # here we create a bind that connects ViewModel with View. It returns a communicator object, # that allows to update View from ViewModel (by calling update_view). Loading @@ -46,3 +50,6 @@ class MainViewModel: def update_pyvista_volume(self, plotter: Plotter) -> None: self.pyvista_config.render(plotter) def get_vtk_volume(self) -> vtkVolume: return self.vtk_config.get_volume() code/episode_7/src/nova_tutorial/views/tab_content_panel.py +3 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ from trame_server import Server from ..view_models.main import MainViewModel from ..views.plotly import PlotlyView from ..views.pyvista import PyVistaView from ..views.vtk import VTKView class TabContentPanel: Loading @@ -26,3 +27,5 @@ class TabContentPanel: PlotlyView(self.view_model) with vuetify.VWindowItem(value=2): PyVistaView(self.view_model) with vuetify.VWindowItem(value=3): VTKView(self.view_model) code/episode_7/src/nova_tutorial/views/tabs_panel.py +1 −0 Original line number Diff line number Diff line Loading @@ -17,3 +17,4 @@ class TabsPanel: with vuetify.VTabs(v_model=("active_tab", 0), classes="pl-5"): vuetify.VTab("Plotly", value=1) vuetify.VTab("PyVista", value=2) vuetify.VTab("VTK", value=3) code/episode_7/src/nova_tutorial/views/vtk.py 0 → 100644 +47 −0 Original line number Diff line number Diff line """View for the 3d plot using PyVista.""" import vtkmodules.vtkRenderingVolumeOpenGL2 # noqa from nova.trame.view.layouts import HBoxLayout from trame.widgets import vtk as vtkw from trame.widgets import vuetify3 as vuetify from vtkmodules.vtkRenderingCore import vtkRenderer, vtkRenderWindow, vtkRenderWindowInteractor, vtkVolume from ..view_models.main import MainViewModel class VTKView: """View class for the 3d plot using PyVista.""" def __init__(self, view_model: MainViewModel) -> None: self.view_model = view_model self.create_vtk() self.create_ui() self.render() def create_vtk(self) -> None: self.renderer = vtkRenderer() self.renderer.SetBackground(0.7, 0.7, 0.7) self.render_window = vtkRenderWindow() self.render_window.AddRenderer(self.renderer) self.render_window.OffScreenRenderingOn() self.render_window_interactor = vtkRenderWindowInteractor() self.render_window_interactor.SetRenderWindow(self.render_window) self.render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() self.render_window_interactor.Initialize() # Ensure interactor is initialized def create_ui(self) -> None: vuetify.VCardTitle("VTK") with HBoxLayout(halign="center", height="50vh"): self.view = vtkw.VtkRemoteView(self.render_window, interactive_ratio=1) def render(self) -> None: volume = self.view_model.get_vtk_volume() self.renderer.Clear() self.renderer.AddVolume(volume) self.render_window.Render() Loading
code/episode_7/src/nova_tutorial/models/vtk.py 0 → 100644 +114 −0 Original line number Diff line number Diff line """Configuration for the VTK example.""" import numpy as np from pyvista import examples from vtk import vtkSLCReader from vtkmodules.vtkCommonDataModel import vtkPiecewiseFunction from vtkmodules.vtkRenderingCore import vtkColorTransferFunction, vtkVolume, vtkVolumeProperty from vtkmodules.vtkRenderingVolume import vtkFixedPointVolumeRayCastMapper KNEE_DATA = examples.download_knee_full() KNEE_DATAFILE = examples.download_knee_full(load=False) class VTKConfig: """Configuration class for the VTK example.""" max: float = KNEE_DATA.get_data_range()[1] min: float = KNEE_DATA.get_data_range()[0] def __init__(self) -> None: reader = vtkSLCReader() reader.SetFileName(KNEE_DATAFILE) mapper = vtkFixedPointVolumeRayCastMapper() mapper.SetInputConnection(reader.GetOutputPort()) lut = self.init_lut() pwf = self.init_pwf() volume_props = vtkVolumeProperty() volume_props.SetColor(lut) volume_props.SetScalarOpacity(pwf) volume_props.SetShade(0) volume_props.SetInterpolationTypeToLinear() self.volume = vtkVolume() self.volume.SetMapper(mapper) self.volume.SetProperty(volume_props) self.volume.SetVisibility(1) def get_volume(self) -> vtkVolume: return self.volume def init_lut(self) -> vtkColorTransferFunction: # This method defines the "Fast" colormap. # See https://www.kitware.com/new-default-colormap-and-background-in-next-paraview-release/ lut = vtkColorTransferFunction() lut.SetColorSpaceToRGB() lut.SetNanColor([0.0, 1.0, 0.0]) srgb = np.array( [ 0, 0.05639999999999999, 0.05639999999999999, 0.47, 0.17159223942480895, 0.24300000000000013, 0.4603500000000004, 0.81, 0.2984914818394138, 0.3568143826543521, 0.7450246485363142, 0.954367702893722, 0.4321287371255907, 0.6882, 0.93, 0.9179099999999999, 0.5, 0.8994959551205902, 0.944646394975174, 0.7686567142818399, 0.5882260353170073, 0.957107977357604, 0.8338185108985666, 0.5089156299842102, 0.7061412605695164, 0.9275207599610714, 0.6214389091739178, 0.31535705838676426, 0.8476395308725272, 0.8, 0.3520000000000001, 0.15999999999999998, 1, 0.59, 0.07670000000000013, 0.11947499999999994, ] ) for arr in np.split(srgb, len(srgb) / 4): lut.AddRGBPoint(arr[0], arr[1], arr[2], arr[3]) prev_min, prev_max = lut.GetRange() prev_delta = prev_max - prev_min node = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] next_delta = self.max - self.min for i in range(lut.GetSize()): lut.GetNodeValue(i, node) node[0] = next_delta * (node[0] - prev_min) / prev_delta + self.min lut.SetNodeValue(i, node) return lut def init_pwf(self) -> vtkPiecewiseFunction: pwf = vtkPiecewiseFunction() pwf.RemoveAllPoints() pwf.AddPoint(self.min, 0) pwf.AddPoint(self.max, 0.7) return pwf
code/episode_7/src/nova_tutorial/view_models/main.py +7 −0 Original line number Diff line number Diff line Loading @@ -4,10 +4,12 @@ from typing import Any, Dict from nova.mvvm.interface import BindingInterface from pyvista import Plotter # just for typing from vtkmodules.vtkRenderingCore import vtkVolume # just for typing from ..models.main_model import MainModel from ..models.plotly import PlotlyConfig from ..models.pyvista import PyVistaConfig from ..models.vtk import VTKConfig class MainViewModel: Loading @@ -17,12 +19,14 @@ class MainViewModel: self.model = model self.plotly_config = PlotlyConfig() self.pyvista_config = PyVistaConfig() self.vtk_config = VTKConfig() self.plotly_config_bind = binding.new_bind( linked_object=self.plotly_config, callback_after_update=self.update_plotly_figure ) self.plotly_figure_bind = binding.new_bind() self.pyvista_config_bind = binding.new_bind(linked_object=self.pyvista_config) # We didn't add any controls for the VTK rendering, so there's no need to create a data binding here. # here we create a bind that connects ViewModel with View. It returns a communicator object, # that allows to update View from ViewModel (by calling update_view). Loading @@ -46,3 +50,6 @@ class MainViewModel: def update_pyvista_volume(self, plotter: Plotter) -> None: self.pyvista_config.render(plotter) def get_vtk_volume(self) -> vtkVolume: return self.vtk_config.get_volume()
code/episode_7/src/nova_tutorial/views/tab_content_panel.py +3 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ from trame_server import Server from ..view_models.main import MainViewModel from ..views.plotly import PlotlyView from ..views.pyvista import PyVistaView from ..views.vtk import VTKView class TabContentPanel: Loading @@ -26,3 +27,5 @@ class TabContentPanel: PlotlyView(self.view_model) with vuetify.VWindowItem(value=2): PyVistaView(self.view_model) with vuetify.VWindowItem(value=3): VTKView(self.view_model)
code/episode_7/src/nova_tutorial/views/tabs_panel.py +1 −0 Original line number Diff line number Diff line Loading @@ -17,3 +17,4 @@ class TabsPanel: with vuetify.VTabs(v_model=("active_tab", 0), classes="pl-5"): vuetify.VTab("Plotly", value=1) vuetify.VTab("PyVista", value=2) vuetify.VTab("VTK", value=3)
code/episode_7/src/nova_tutorial/views/vtk.py 0 → 100644 +47 −0 Original line number Diff line number Diff line """View for the 3d plot using PyVista.""" import vtkmodules.vtkRenderingVolumeOpenGL2 # noqa from nova.trame.view.layouts import HBoxLayout from trame.widgets import vtk as vtkw from trame.widgets import vuetify3 as vuetify from vtkmodules.vtkRenderingCore import vtkRenderer, vtkRenderWindow, vtkRenderWindowInteractor, vtkVolume from ..view_models.main import MainViewModel class VTKView: """View class for the 3d plot using PyVista.""" def __init__(self, view_model: MainViewModel) -> None: self.view_model = view_model self.create_vtk() self.create_ui() self.render() def create_vtk(self) -> None: self.renderer = vtkRenderer() self.renderer.SetBackground(0.7, 0.7, 0.7) self.render_window = vtkRenderWindow() self.render_window.AddRenderer(self.renderer) self.render_window.OffScreenRenderingOn() self.render_window_interactor = vtkRenderWindowInteractor() self.render_window_interactor.SetRenderWindow(self.render_window) self.render_window_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() self.render_window_interactor.Initialize() # Ensure interactor is initialized def create_ui(self) -> None: vuetify.VCardTitle("VTK") with HBoxLayout(halign="center", height="50vh"): self.view = vtkw.VtkRemoteView(self.render_window, interactive_ratio=1) def render(self) -> None: volume = self.view_model.get_vtk_volume() self.renderer.Clear() self.renderer.AddVolume(volume) self.render_window.Render()