# -*- coding: utf-8 -*-
"""
Module to provide an interlaced stereo window, designed for
driving things like the Storz 3D laparoscope monitor.
"""
# pylint: disable=c-extension-no-member, no-name-in-module, too-many-instance-attributes
# pylint:disable=super-with-arguments
import cv2
import numpy as np
from PySide6 import QtWidgets
from PySide6.QtWidgets import QSizePolicy
import sksurgeryimage.processing.interlace as i
import sksurgerycore.utilities.validate_matrix as vm
import sksurgeryvtk.widgets.vtk_overlay_window as ow
import sksurgeryvtk.camera.vtk_camera_model as cm
[docs]class VTKStereoInterlacedWindow(QtWidgets.QWidget):
"""
Class to contain a pair of VTKOverlayWindows, stacked with a QLabel widget
containing the resulting interlaced picture.
:param init_widget: If True we will call self.Initialize and self.Start
as part of the init function. Set to false if you're on Linux.
"""
def __init__(self,
offscreen=False,
left_camera_matrix=None,
right_camera_matrix=None,
clipping_range=(1, 10000),
init_widget=True
):
super().__init__()
self.left_widget = ow.VTKOverlayWindow(
offscreen=offscreen,
camera_matrix=left_camera_matrix,
clipping_range=clipping_range,
init_widget=init_widget
)
self.left_widget.setContentsMargins(0, 0, 0, 0)
self.right_widget = ow.VTKOverlayWindow(
offscreen=offscreen,
camera_matrix=right_camera_matrix,
clipping_range=clipping_range,
init_widget=init_widget
)
self.right_widget.setContentsMargins(0, 0, 0, 0)
self.left_widget.show()
self.left_widget.Initialize()
self.left_widget.Start()
self.right_widget.show()
self.right_widget.Initialize()
self.right_widget.Start()
self.left_rescaled = None
self.right_rescaled = None
self.interlaced_widget = ow.VTKOverlayWindow(
offscreen=offscreen,
init_widget=init_widget
)
self.interlaced_widget.setContentsMargins(0, 0, 0, 0)
self.interlaced_widget.show()
self.interlaced_widget.Initialize()
self.interlaced_widget.Start()
self.stacked_stereo_widget = ow.VTKOverlayWindow(
offscreen=offscreen,
init_widget=init_widget
)
self.stacked_stereo_widget.setContentsMargins(0, 0, 0, 0)
self.stacked_stereo_widget.show()
self.stacked_stereo_widget.Initialize()
self.stacked_stereo_widget.Start()
self.stacked = QtWidgets.QStackedWidget()
self.stacked.addWidget(self.left_widget)
self.stacked.addWidget(self.right_widget)
self.stacked.addWidget(self.interlaced_widget)
self.stacked.addWidget(self.stacked_stereo_widget)
self.stacked.setContentsMargins(0, 0, 0, 0)
# Set Qt Size Policy
self.size_policy = \
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.stacked.setSizePolicy(self.size_policy)
self.setSizePolicy(self.size_policy)
self.layout = QtWidgets.QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.layout.addWidget(self.stacked)
self.setLayout(self.layout)
self.setContentsMargins(0, 0, 0, 0)
self.interlaced = np.eye(1)
self.interlaced_swapped = np.eye(1)
self.left_camera_to_world = np.eye(4)
self.left_to_right = np.eye(4)
self.default_viewer_index = 3
self.stacked.setCurrentIndex(self.default_viewer_index)
# pylint: disable=invalid-name
[docs] def paintEvent(self, ev):
"""
Ensure that the interlaced image is recomputed.
"""
super(VTKStereoInterlacedWindow, self).paintEvent(ev)
self.render()
# pylint: disable=invalid-name
[docs] def resizeEvent(self, ev):
"""
Ensure that the interlaced image is recomputed.
"""
super(VTKStereoInterlacedWindow, self).resizeEvent(ev)
self.render()
[docs] def set_current_viewer_index(self, viewer_index):
"""
Sets the current viewer selection.
Defaults to self.default_viewer_ndex.
0 = left
1 = right
2 = interlaced
3 = stacked
:param viewer_index: index of viewer, as above.
"""
self.stacked.setCurrentIndex(viewer_index)
[docs] def set_view_to_interlaced(self):
""" Sets the current view to interlaced. """
self.set_current_viewer_index(2)
[docs] def set_view_to_stacked(self):
""" Sets the current view to stacked. """
self.set_current_viewer_index(3)
[docs] def set_video_images(self, left_image, right_image):
"""
Sets both left and right video images. Images
must be the same shape, and have an even number of rows.
:param left_image: left numpy image
:param right_image: right numpy image
:raises: ValueError, TypeError
"""
if not isinstance(left_image, np.ndarray):
raise TypeError('left image is not an np.ndarray')
if not isinstance(right_image, np.ndarray):
raise TypeError('right image is not an np.ndarray')
if left_image.shape != right_image.shape:
raise ValueError('left and right images differ in shape')
if left_image.shape[0] % 2 != 0:
raise ValueError('left image does not have an even number of rows')
if right_image.shape[0] % 2 != 0:
raise ValueError('right image does not have an even number of rows')
self.left_widget.set_video_image(left_image)
self.right_widget.set_video_image(right_image)
self.__update_left_right()
self.__update_interlaced()
self.__update_stacked()
[docs] def closeEvent(self, QCloseEvent):
super().closeEvent(QCloseEvent)
self.left_widget.Finalize()
self.right_widget.Finalize()
def __update_left_right(self):
"""
Update and grab current scene from left and right widgets.
"""
left = self.left_widget.convert_scene_to_numpy_array()
right = self.right_widget.convert_scene_to_numpy_array()
self.left_rescaled = cv2.resize(left, (0, 0), fx=1, fy=0.5)
self.right_rescaled = cv2.resize(right, (0, 0), fx=1, fy=0.5)
def __update_interlaced(self):
"""
Updates the interlaced image by forcing a repaint on left and right,
grabbing the current scene from those widgets, interlacing it and
placing it as the background on the interlaced widget.
"""
self.interlaced = i.interlace_to_new(self.left_rescaled,
self.right_rescaled)
self.interlaced_widget.set_video_image(self.interlaced)
def __update_stacked(self):
"""
Updates the stacked image by forcing a repaint on left and right,
grabbing the current scene from those widgets, stacking it and
placing it as the background on the stacked_stereo widget.
"""
stacked_image = i.stack_to_new(self.left_rescaled, self.right_rescaled)
self.stacked_stereo_widget.set_video_image(stacked_image)
[docs] def set_camera_matrices(self, left_camera_matrix, right_camera_matrix):
"""
Sets both the left and right camera matrices.
:param left_camera_matrix: numpy 3x3 ndarray containing fx, fy, cx, cy
:param right_camera_matrix: numpy 3x3 ndarray containing fx, fy, cx, cy
"""
self.left_widget.set_camera_matrix(left_camera_matrix)
self.right_widget.set_camera_matrix(right_camera_matrix)
[docs] def set_left_to_right(self, left_to_right):
"""
Sets the left_to_right transform (stereo extrinsics).
:param left_to_right: 4x4 numpy ndarray, rigid transform
"""
vm.validate_rigid_matrix(left_to_right)
self.left_to_right = left_to_right
[docs] def set_camera_poses(self, left_camera_to_world):
"""
Sets the pose of both the left and right camera.
If you haven't set the left_to_right transform, it will be identity.
:param left_camera_to_world: 4x4 numpy ndarray, rigid transform
"""
self.left_camera_to_world = left_camera_to_world
right_camera_to_world = cm.compute_right_camera_pose(
self.left_camera_to_world, self.left_to_right)
self.left_widget.set_camera_pose(left_camera_to_world)
self.right_widget.set_camera_pose(right_camera_to_world)
[docs] def add_vtk_models(self, models):
"""
Add models to both left and right widgets.
Here a model is anything with an attribute called actor that
is a vtkActor.
:param models: vtk_base_model
"""
self.left_widget.add_vtk_models(models)
self.right_widget.add_vtk_models(models)
[docs] def add_vtk_actor(self, actor):
"""
Adds a vtkActor to both left and right widgets.
:param actor: vtkActor
"""
self.left_widget.add_vtk_actor(actor)
self.right_widget.add_vtk_actor(actor)
[docs] def render(self):
"""
Calls Render on all 3 contained vtk_overlay_windows.
"""
self.left_widget.Render()
self.right_widget.Render()
self.__update_interlaced()
self.interlaced_widget.Render()
self.__update_stacked()
self.stacked_stereo_widget.Render()
self.stacked.repaint()
[docs] def save_scene_to_file(self, file_name):
"""
Writes the currently displayed widget contents to file.
:param file_name: file name compatible with cv2.imwrite()
"""
self.render()
self.stacked.currentWidget().save_scene_to_file(file_name)