Skip to content

Commit

Permalink
Merge branch '222-n-rendering-layers-in-overlay'
Browse files Browse the repository at this point in the history
  • Loading branch information
MattClarkson committed Feb 16, 2024
2 parents 6ff80b7 + 0e9706d commit 6e4898a
Show file tree
Hide file tree
Showing 10 changed files with 822 additions and 263 deletions.
3 changes: 2 additions & 1 deletion sksurgeryvtk/models/voxelise.py
Expand Up @@ -606,7 +606,8 @@ def apply_displacement_to_mesh(mesh: Union[vtk.vtkDataObject, str],
0,
vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS,
"preoperativeSurface")
threshold.ThresholdByLower(0)
threshold.SetLowerThreshold(0)
threshold.SetThresholdFunction(threshold.THRESHOLD_LOWER)
threshold.SetInputData(field)
threshold.Update()
fieldInternal = threshold.GetOutput()
Expand Down
4 changes: 3 additions & 1 deletion sksurgeryvtk/models/vtk_grid_model.py
Expand Up @@ -125,5 +125,7 @@ def threshold_between(self, lower: float, upper: float):
:param upper: Upper limit
:type upper: float
"""
self.threshold.ThresholdBetween(lower, upper)
self.threshold.SetLowerThreshold(lower)
self.threshold.SetUpperThreshold(upper)
self.threshold.SetThresholdFunction(self.threshold.THRESHOLD_BETWEEN)
self.threshold.Update()
654 changes: 449 additions & 205 deletions sksurgeryvtk/widgets/vtk_overlay_window.py

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions tests/camera/test_liver_overlay.py
Expand Up @@ -13,12 +13,15 @@

skip_pytest_in_runner_macos = pytest.mark.skipif(
platform.system() == "Darwin",
reason=f'for [{platform.system()} OSs with CI=[{os.environ.get("CI")}] with RUNNER_OS=[{os.environ.get("RUNNER_OS")}] '
f'{os.environ.get("SESSION_MANAGER")[0:20] if (platform.system() == "Darwin" and os.environ.get("GITHUB_ACTIONS") == None) else ""} '
f'with {os.environ.get("XDG_CURRENT_DESKTOP") if (platform.system() == "Darwin" and os.environ.get("GITHUB_ACTIONS") == None) else ""} '
reason=f'for [{platform.system()} OSs with '
f'CI=[{os.environ.get("CI")}] with '
f'RUNNER_OS=[{os.environ.get("RUNNER_OS")}] '
f'SESSION_MANAGER=[{os.environ.get("SESSION_MANAGER")[0:20] if (platform.system() == "Darwin" and os.environ.get("GITHUB_ACTIONS") is not None and os.environ.get("SESSION_MANAGER") is not None) else ""}] '
f'XDG_CURRENT_DESKTOP=[{os.environ.get("XDG_CURRENT_DESKTOP") if (platform.system() == "Darwin" and os.environ.get("GITHUB_ACTIONS") is not None) else ""}] '
f'due to issues with Fatal Python error: Segmentation fault'
)


def _reproject_and_save_image(image,
model_to_camera,
point_cloud,
Expand Down Expand Up @@ -59,6 +62,7 @@ def _reproject_and_save_image(image,

cv2.imwrite(output_file, output_image)


@skip_pytest_in_runner_macos
def test_overlay_liver_points(setup_vtk_overlay_window):
"""
Expand Down
93 changes: 78 additions & 15 deletions tests/conftest.py
Expand Up @@ -21,7 +21,6 @@ def setup_qt():
@pytest.fixture(scope="session")
def setup_vtk_err(setup_qt):
""" Used to send VTK errors to file instead of screen. """

err_out = vtk.vtkFileOutputWindow()
err_out.SetFileName('tests/output/vtk.err.txt')
vtk_std_err = vtk.vtkOutputWindow()
Expand All @@ -32,11 +31,9 @@ def setup_vtk_err(setup_qt):
@pytest.fixture(scope="function")
def setup_vtk_overlay_window(setup_vtk_err):
"""
This function so you can select offscreen or not, while debugging.
`init_widget_flag` is set to false `init_widget_flag` when testing in Linux OS machines.
Otherwise, `init_widget_flag = True`, calling `self.Initialize` and `self.Start` in the init function of
VTKOverlayWindow class
Sets `init_widget_flag` to False on Linux, and True on Windows and Mac.
When init_widget_flag==True, the VTKOverlayWindow constructor calls
`self.Initialize` and `self.Start` when creating the widget.
"""

vtk_std_err, setup_qt = setup_vtk_err
Expand All @@ -52,22 +49,21 @@ def setup_vtk_overlay_window(setup_vtk_err):

@pytest.fixture(scope="function")
def setup_vtk_overlay_window_no_init(setup_vtk_err):
""" This function so you can select offscreen or not, while debugging. """
"""
Similar to the above function, except init_widget=False, so
`self.Initialize` and `self.Start` are not called in the VTKOverlayWindow constructor.
"""

vtk_std_err, setup_qt = setup_vtk_err

vtk_overlay = VTKOverlayWindow(offscreen=False, init_widget=False)
return vtk_overlay, vtk_std_err, setup_qt


# Note: These windows will persist while all unit tests run.
# Don't waste time trying to debug why you see >1 windows.
@pytest.fixture(scope="function")
def vtk_overlay_with_gradient_image(setup_vtk_overlay_window):
""" Creates a VTKOverlayWindow with gradient image. """

vtk_overlay, vtk_std_err, setup_qt = setup_vtk_overlay_window

def _create_gradient_image():
"""
Creates a dummy gradient image for testing only.
"""
width = 512
height = 256
image = np.ones((height, width, 3), dtype=np.uint8)
Expand All @@ -76,6 +72,17 @@ def vtk_overlay_with_gradient_image(setup_vtk_overlay_window):
image[y][x][0] = y
image[y][x][1] = y
image[y][x][2] = y
return image


# Note: These windows will persist while all unit tests run.
# Don't waste time trying to debug why you see >1 windows.
@pytest.fixture(scope="function")
def vtk_overlay_with_gradient_image(setup_vtk_overlay_window):
""" Creates a VTKOverlayWindow with gradient image. """

vtk_overlay, vtk_std_err, setup_qt = setup_vtk_overlay_window
image = _create_gradient_image()
vtk_overlay.set_video_image(image)
return image, vtk_overlay, vtk_std_err, setup_qt

Expand All @@ -93,3 +100,59 @@ def vtk_interlaced_stereo_window(setup_vtk_err):

vtk_interlaced = VTKStereoInterlacedWindow(offscreen=False, init_widget=init_widget_flag)
return vtk_interlaced, vtk_std_err, setup_qt


@pytest.fixture(scope="function")
def setup_vtk_overlay_window_video_only_layer_2(setup_vtk_err):
"""
Sets `init_widget_flag` to False on Linux, and True on Windows and Mac.
When init_widget_flag==True, the VTKOverlayWindow constructor calls
`self.Initialize` and `self.Start` when creating the widget.
As of Issue #222: And also sets video_in_layer_0=False and video_in_layer_2=True
in VTKOverlayWindow constructor. See VTKOverlayWindow docstring for explanation.
"""

vtk_std_err, setup_qt = setup_vtk_err

if platform.system() == 'Linux':
init_widget_flag = False
else:
init_widget_flag = True

vtk_overlay = VTKOverlayWindow(offscreen=False,
init_widget=init_widget_flag,
video_in_layer_0=False,
video_in_layer_2=True
)
image = _create_gradient_image()
vtk_overlay.set_video_image(image)
return vtk_overlay, vtk_std_err, setup_qt


@pytest.fixture(scope="function")
def setup_vtk_overlay_window_video_both_layer_0_and_2(setup_vtk_err):
"""
Sets `init_widget_flag` to False on Linux, and True on Windows and Mac.
When init_widget_flag==True, the VTKOverlayWindow constructor calls
`self.Initialize` and `self.Start` when creating the widget.
As of Issue #222: And also sets video_in_layer_0=True and video_in_layer_2=True
in VTKOverlayWindow constructor. See VTKOverlayWindow docstring for explanation.
"""

vtk_std_err, setup_qt = setup_vtk_err

if platform.system() == 'Linux':
init_widget_flag = False
else:
init_widget_flag = True

vtk_overlay = VTKOverlayWindow(offscreen=False,
init_widget=init_widget_flag,
video_in_layer_0=True,
video_in_layer_2=True
)
image = _create_gradient_image()
vtk_overlay.set_video_image(image)
return vtk_overlay, vtk_std_err, setup_qt
2 changes: 1 addition & 1 deletion tests/models/test_vtk_cylinder_model.py
Expand Up @@ -50,6 +50,6 @@ def test_cylinder_model(setup_vtk_overlay_window):

# You don't really want this in a unit test, otherwise you can't exit.
# If you want to do interactive testing, please uncomment the following line
# _pyside_qt_app.exec()
#_pyside_qt_app.exec()
widget.close()

4 changes: 2 additions & 2 deletions tests/models/test_vtk_surface_model.py
Expand Up @@ -186,10 +186,10 @@ def test_flat_shaded_on_coloured_background(setup_vtk_overlay_window):
widget, _, app = setup_vtk_overlay_window
widget.add_vtk_actor(model.actor)
model.set_no_shading(True)
widget.background_renderer.SetBackground(0, 0, 1)
widget.get_background_renderer().SetBackground(0, 0, 1)
widget.show()
model.set_no_shading(False)
widget.background_renderer.SetBackground(0, 1, 0)
widget.get_background_renderer().SetBackground(0, 1, 0)
widget.show()
# app.exec()

Expand Down
64 changes: 33 additions & 31 deletions tests/widgets/test_vtk_overlay_window.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-

import numpy as np
import platform
import pytest
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkFiltersSources import vtkConeSource
Expand All @@ -12,8 +11,6 @@
)
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from sksurgeryvtk.widgets.vtk_overlay_window import VTKOverlayWindow

import sksurgeryvtk.models.vtk_point_model as pm
import sksurgeryvtk.models.vtk_surface_model as sm

Expand All @@ -23,7 +20,7 @@ def test_vtk_render_window_settings(setup_vtk_overlay_window):

assert not widget.GetRenderWindow().GetStereoRender()
assert not widget.GetRenderWindow().GetStereoCapableWindow()
# assert widget.GetRenderWindow().GetAlphaBitPlanes()
assert widget.GetRenderWindow().GetAlphaBitPlanes()
assert widget.GetRenderWindow().GetMultiSamples() == 0
widget.close()

Expand All @@ -33,36 +30,38 @@ def test_vtk_render_window_settings_no_init(setup_vtk_overlay_window_no_init):

assert not widget.GetRenderWindow().GetStereoRender()
assert not widget.GetRenderWindow().GetStereoCapableWindow()
# assert widget.GetRenderWindow().GetAlphaBitPlanes()
assert widget.GetRenderWindow().GetAlphaBitPlanes()
assert widget.GetRenderWindow().GetMultiSamples() == 0
widget.close()


def test_vtk_foreground_render_settings(setup_vtk_overlay_window):
widget, _vtk_std_err, _pyside_qt_app = setup_vtk_overlay_window

assert widget.foreground_renderer.GetLayer() == 1
assert widget.foreground_renderer.GetUseDepthPeeling()
layer = widget.get_foreground_renderer().GetLayer()
assert widget.get_foreground_renderer().GetLayer() == 1
assert widget.get_foreground_renderer().GetUseDepthPeeling()
widget.close()


def test_vtk_background_render_settings(setup_vtk_overlay_window):
widget, _vtk_std_err, _pyside_qt_app = setup_vtk_overlay_window

assert widget.background_renderer.GetLayer() == 0
assert not widget.background_renderer.GetInteractive()
assert widget.get_background_renderer().GetLayer() == 0
assert not widget.get_background_renderer().GetInteractive()
widget.close()


def test_image_importer(setup_vtk_overlay_window):
widget, _vtk_std_err, _pyside_qt_app = setup_vtk_overlay_window

width, height, _number_of_scalar_components = widget.input.shape
expected_extent = (0, height - 1, 0, width - 1, 0, 0)
width, height, _number_of_scalar_components = widget.rgb_input.shape
expected_extent = (0, height - 1, 0, width - 1, 0, 2)
actual_extent = widget.rgb_image_importer.GetDataExtent()

assert widget.image_importer.GetDataExtent() == expected_extent
assert widget.image_importer.GetDataScalarTypeAsString() == "unsigned char"
assert widget.image_importer.GetNumberOfScalarComponents() == 3
assert actual_extent == expected_extent
assert widget.rgb_image_importer.GetDataScalarTypeAsString() == "unsigned char"
assert widget.rgb_image_importer.GetNumberOfScalarComponents() == 3
widget.close()


Expand Down Expand Up @@ -116,22 +115,21 @@ def test_basic_pyside_vtk_pipeline():
ren = vtkRenderer()
ren.AddActor(coneActor)

qvtk_render_window_iterator = QVTKRenderWindowInteractor()
qvtk_render_window_iterator.GetRenderWindow().AddRenderer(ren)
qvtk_render_window_iterator.resize(100, 100)
qvtk_render_window_interactor = QVTKRenderWindowInteractor()
qvtk_render_window_interactor.GetRenderWindow().AddRenderer(ren)
qvtk_render_window_interactor.resize(100, 100)

layout.addWidget(qvtk_render_window_iterator)
layout.addWidget(qvtk_render_window_interactor)

# To exit window using 'q' or 'e' key
qvtk_render_window_iterator.AddObserver("ExitEvent", lambda o, e, a=_pyside_qt_app: a.quit())

qvtk_render_window_iterator.Initialize()
qvtk_render_window_iterator.Start()
qvtk_render_window_interactor.AddObserver("ExitEvent", lambda o, e, a=_pyside_qt_app: a.quit())
qvtk_render_window_interactor.Initialize()
qvtk_render_window_interactor.Start()

# You don't really want this in a unit test, otherwise you can't exit.
# If you want to do interactive testing, please uncomment the following line
# _pyside_qt_app.exec()
qvtk_render_window_iterator.close()
qvtk_render_window_interactor.close()


def test_basic_cone_overlay(vtk_overlay_with_gradient_image):
Expand Down Expand Up @@ -227,6 +225,10 @@ def test_add_model_to_background_renderer_raises_error(vtk_overlay_with_gradient

with pytest.raises(ValueError):
widget.add_vtk_models(surface, layer=0)

with pytest.raises(ValueError):
widget.add_vtk_models(surface, layer=2)

widget.close()


Expand All @@ -238,17 +240,17 @@ def test_add_models_to_foreground_renderer(vtk_overlay_with_gradient_image):
# If no layer is specified, default is 0
widget.add_vtk_models(liver)

foreground_actors = widget.foreground_renderer.GetActors()
foreground_actors = widget.get_foreground_renderer().GetActors()
assert foreground_actors.GetNumberOfItems() == 1

# Explicitly specify use of foreground renderer
widget.add_vtk_models(tumors, 1)

foreground_actors = widget.foreground_renderer.GetActors()
foreground_actors = widget.get_foreground_renderer().GetActors()
assert foreground_actors.GetNumberOfItems() == 2

# Check overlay renderer is empty
overlay_renderer_actors = widget.generic_overlay_renderer.GetActors()
overlay_renderer_actors = widget.get_overlay_renderer().GetActors()
assert overlay_renderer_actors.GetNumberOfItems() == 0
widget.close()

Expand All @@ -258,17 +260,17 @@ def test_add_models_to_overlay_renderer(vtk_overlay_with_gradient_image):
tumors = [sm.VTKSurfaceModel('tests/data/models/Liver/liver_tumours.vtk', (1.0, 1.0, 1.0))]
_image, widget, _vtk_std_err, _pyside_qt_app = vtk_overlay_with_gradient_image

widget.add_vtk_models(liver, 2)
widget.add_vtk_models(liver, 4)

overlay_actors = widget.generic_overlay_renderer.GetActors()
overlay_actors = widget.get_overlay_renderer().GetActors()
assert overlay_actors.GetNumberOfItems() == 1

widget.add_vtk_models(tumors, 2)
widget.add_vtk_models(tumors, 4)

overlay_actors = widget.generic_overlay_renderer.GetActors()
overlay_actors = widget.get_overlay_renderer().GetActors()
assert overlay_actors.GetNumberOfItems() == 2

# Check foreground is empty
foreground_actors = widget.foreground_renderer.GetActors()
foreground_actors = widget.get_foreground_renderer().GetActors()
assert foreground_actors.GetNumberOfItems() == 0
widget.close()

0 comments on commit 6e4898a

Please sign in to comment.