# -*- coding: utf-8 -*-
""" Harness to run CT & overlay application. """
import sys
import os
import vtk
from PySide6 import QtWidgets
import sksurgeryvtk.models.vtk_surface_model as sm
import sksurgeryutils.common_overlay_apps as coa
#pylint:disable=no-member, too-many-instance-attributes
[docs]
class OverlayMainWindow(coa.OverlayOnVideoFeed):
""" OverlayMainWindow"""
def __init__(self, video_source, input_volume, input_surface):
super().__init__(video_source)
# Start by loading some data.
if os.path.isdir(input_volume):
reader = vtk.vtkDICOMImageReader()
reader.SetDirectoryName(input_volume)
elif input_volume.endswith(('.nii', '.nii.gz')):
reader = vtk.vtkNIFTIImageReader()
reader.SetFileName(input_volume)
else:
raise TypeError("Invalid input volume specified")
reader.Update()
self.model = sm.VTKSurfaceModel(input_surface,
[0.5, 0.5, 0.5], opacity=0.5)
self.vtk_overlay_window.add_vtk_models([self.model])
# Calculate the center of the volume
self.x_min, self.x_max, self.y_min, self.y_max, self.z_min, self.z_max \
= reader.GetExecutive().GetWholeExtent(
reader.GetOutputInformation(0))
self.num_x = self.x_max - self.x_min
self.num_y = self.y_max - self.y_min
self.num_z = self.z_max - self.z_min
self.x_spacing, self.y_spacing, self.z_spacing = \
reader.GetOutput().GetSpacing()
self.x_0, self.y_0, self.z_0 = reader.GetOutput().GetOrigin()
self.center =\
[self.x_spacing * (self.x_min + 0.5 * (self.x_min + self.x_max)),
self.x_spacing * (self.y_min + 0.5 * (self.y_min + self.y_max)),
self.z_spacing * (self.z_min + 0.5 * (self.z_min + self.z_max))]
self.reslice_center = \
[-0.5 * self.x_spacing * (self.num_x - 1),
-0.5 * self.y_spacing * (self.num_y - 1),
-0.5 * self.z_spacing * (self.num_z - 1)]
# Setup reslice driver
self.reslice = vtk.vtkImageReslice()
self.reslice.SetInputConnection(reader.GetOutputPort())
# Specific values to make the skull example look nicer.
self.reslice.SetOutputExtent(100, 400, 150, 400, 25, 175)
self.reslice.SetOutputOrigin(self.reslice_center[0],
self.reslice_center[1], 0)
self.reslice.SetInterpolationModeToLinear()
self.reslice_x_angle = 0
self.reslice_y_angle = 0
self.reslice_z_angle = 0
# Create a greyscale lookup table
table = vtk.vtkLookupTable()
table.SetRange(-1000, 1000) # image intensity range
table.SetValueRange(0.1, 0.9) # from black to white
table.SetSaturationRange(0.0, 0.0) # no color saturation
table.SetRampToLinear()
table.Build()
# Map the image through the lookup table
color = vtk.vtkImageMapToColors()
color.SetLookupTable(table)
color.SetInputConnection(self.reslice.GetOutputPort())
# Display the image
self.reslice_actor = vtk.vtkImageActor()
self.reslice_actor.GetMapper().SetInputConnection(color.GetOutputPort())
self.vtk_overlay_window.add_vtk_actor(self.reslice_actor)
self.update_reslice()
[docs]
def update_reslice(self):
""" Reslice the volume based on the new angles.
Thanks to https://markmail.org/message/ycfr246az23acrl7
for tips on setting the translation correctly. """
slice_transform = vtk.vtkTransform()
slice_transform.Identity()
slice_transform.Translate(-1 * self.reslice_center[0],
-1 * self.reslice_center[1],
-1 * self.reslice_center[2])
slice_transform.RotateX(self.reslice_x_angle)
slice_transform.RotateY(self.reslice_y_angle)
slice_transform.RotateZ(self.reslice_z_angle)
self.reslice.SetResliceTransform(slice_transform)
actor_transform = vtk.vtkTransform()
actor_transform.Identity()
actor_transform.Translate(self.center)
actor_transform.RotateZ(self.reslice_z_angle)
actor_transform.RotateX(self.reslice_x_angle)
actor_transform.RotateY(self.reslice_y_angle)
self.reslice_actor.SetUserTransform(actor_transform)
self.vtk_overlay_window.Render()
[docs]
class MainWindow(QtWidgets.QMainWindow):
""" MainWindow """
def __init__(self, video_source, input_volume, input_surface):
super().__init__()
self.overlay_window = OverlayMainWindow(video_source,
input_volume, input_surface)
self.layout = QtWidgets.QHBoxLayout()
self.layout.addWidget(self.overlay_window.vtk_overlay_window)
self.setup_controls()
self.frame = QtWidgets.QFrame()
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)
[docs]
def setup_controls(self):
""" Setup widgets for buttons etc."""
self.controls = QtWidgets.QVBoxLayout()
# Not really controlling x/y rotation, but good enough for a demo
self.slider_x = QtWidgets.QSlider()
self.slider_y = QtWidgets.QSlider()
for slider in [self.slider_x, self.slider_y]:
slider.setMinimum(-180)
slider.setMaximum(180)
slider.setTickPosition(QtWidgets.QSlider.TicksBothSides)
slider.setTickInterval(45)
slider.setSliderPosition(0)
self.slider_x.valueChanged.connect(self.x_changed)
self.slider_y.valueChanged.connect(self.y_changed)
self.toggle_model_btn = QtWidgets.QPushButton("Toggle Model")
self.toggle_slice_btn = QtWidgets.QPushButton("Toggle Slice")
self.toggle_model_btn.clicked.connect(self.toggle_model)
self.toggle_slice_btn.clicked.connect(self.toggle_slice)
self.slider_layout = QtWidgets.QHBoxLayout()
self.slider_layout.addWidget(self.slider_x)
self.slider_layout.addWidget(self.slider_y)
self.controls.addLayout(self.slider_layout)
self.controls.addWidget(self.toggle_model_btn)
self.controls.addWidget(self.toggle_slice_btn)
self.layout.addLayout(self.controls)
[docs]
def toggle_model(self):
""" Toggle model view"""
self.overlay_window.model.toggle_visibility()
[docs]
def toggle_slice(self):
""" Toggle slice view """
actor = self.overlay_window.reslice_actor
if actor.GetVisibility():
actor.VisibilityOff()
else:
actor.VisibilityOn()
[docs]
def x_changed(self, x_value):
""" Callbakc for x slider. """
self.overlay_window.reslice_x_angle = x_value
self.overlay_window.update_reslice()
[docs]
def y_changed(self, y_value):
""" Callback for y slider"""
self.overlay_window.reslice_y_angle = y_value
self.overlay_window.update_reslice()
[docs]
def start(self):
""" Start overlay window. """
self.overlay_window.start()
[docs]
def run_overlay():
"""
Run the app
"""
# Need this for all the Qt magic.
app = QtWidgets.QApplication([])
# App is just one window, containing one widget, defined above.
# Not gauranteed to work well with data other than the skull test data.
window = MainWindow(0, 'tests/data/skull/skull.nii',
'tests/data/skull/skull.vtk')
window.show()
window.start()
# Start event loop.
return sys.exit(app.exec_())