Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewer toolbar #337

Closed
wants to merge 14 commits into from
111 changes: 111 additions & 0 deletions Wrappers/Python/ccpi/viewer/QCILViewer3DToolBar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from PySide2 import QtWidgets

from ccpi.viewer.ui.SettingsDialog import SettingsDialog
from ccpi.viewer.ui.VolumeRenderSettingsDialog import VolumeRenderSettingsDialog


class QCILViewer3DToolBar(QtWidgets.QToolBar):
def __init__(self, parent=None, viewer=None, **kwargs):
"""
Parameters
-----------
viewer: an instance of viewer2D or viewer3D
the viewer which the toolbar is for. The viewer instance
is passed to allow interactions to be controlled using the
toolbar.

"""
self.parent = parent
self.viewer = viewer
super(QCILViewer3DToolBar, self).__init__(parent=parent, **kwargs)
self.dialog = {"settings": None, "volume_render_settings": None}

# Settings button
settings_2d = QtWidgets.QToolButton()
settings_2d.setText("Settings ⚙️")
self.addWidget(settings_2d)
settings_2d.clicked.connect(lambda: self.open_dialog("settings"))

# Volume render settings button
settings_3d = QtWidgets.QToolButton()
settings_3d.setText("Volume Render Settings ⚙️")
self.addWidget(settings_3d)
settings_3d.clicked.connect(lambda: self.open_dialog("volume_render_settings"))

# Reset camera button
settings_reset = QtWidgets.QToolButton()
settings_reset.setText("Reset ⚙️")
self.addWidget(settings_reset)

# Reset settings button
reset_camera_btn = QtWidgets.QToolButton()
reset_camera_btn.setText("Reset 📷")
self.addWidget(reset_camera_btn)
reset_camera_btn.clicked.connect(self.reset_camera)

# Save image button
save_image = QtWidgets.QToolButton()
save_image.setText("Save 💾")
self.addWidget(save_image)
save_image.clicked.connect(self.save_render)

def reset_camera(self):
"""Reset camera to default position."""
self.viewer.resetCameraToDefault()
self.viewer.updatePipeline()

def change_orientation(self, dialog):
"""Change orientation of the viewer."""
orientation = 1
self.viewer.style.SetSliceOrientation(orientation)
self.viewer.updatePipeline()

def open_dialog(self, mode):
"""Open a dialog box for the settings of the viewer."""
# pylint(access-member-before-definition)
if mode == "settings":
if self.dialog["settings"] is None:
dialog = SettingsDialog(parent=self.parent, title="Settings")
dialog.Ok.clicked.connect(lambda: self.accepted(mode))
dialog.Cancel.clicked.connect(lambda: self.rejected(mode))
dialog.set_viewer(self.viewer)
self.dialog[mode] = dialog
# self.default_settings = self.dialog[mode].get_settings()

self.settings = self.dialog[mode].get_settings()
self.dialog[mode].open()
return

if mode == "volume_render_settings":
if self.dialog["volume_render_settings"] is None:
dialog = VolumeRenderSettingsDialog(parent=self.parent, title="Volume Render Settings")
dialog.Ok.clicked.connect(lambda: self.accepted(mode))
dialog.Cancel.clicked.connect(lambda: self.rejected(mode))
dialog.set_viewer(self.viewer)
self.dialog[mode] = dialog

self.settings = self.dialog[mode].get_settings()
self.dialog[mode].open()
return

def accepted(self, mode):
"""Extract settings and apply them."""
self.dialog[mode].close()

def rejected(self, mode):
"""Reapply previous settings."""
self.dialog[mode].apply_settings(self.settings)
self.dialog[mode].close()

def save_render(self):
"""Save the render to a file."""
if self.dialog.get("settings") is None:
self.viewer.saveRender("render")
else:
self.viewer.saveRender(self.dialog.get("settings").file_location)

def save_dialog_settings(self):
pass

def apply_dialog_settings(self, mode, settings):
pass
73 changes: 43 additions & 30 deletions Wrappers/Python/ccpi/viewer/QCILViewerWidget.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,88 @@
from PySide2 import QtWidgets
import vtk
import sys
import vtk
from PySide2 import QtCore, QtWidgets
from ccpi.viewer.QCILRenderWindowInteractor import QCILRenderWindowInteractor
from ccpi.viewer import viewer2D
from ccpi.viewer import viewer2D, viewer3D
from ccpi.viewer.QCILViewer3DToolBar import QCILViewer3DToolBar


class QCILViewerWidget(QtWidgets.QFrame):
'''A QFrame to embed in Qt application containing a VTK Render Window
"""A QFrame to embed in Qt application containing a VTK Render Window

All the interaction is passed from Qt to VTK.

:param viewer: The viewer you want to embed in Qt: CILViewer2D or CILViewer
:param interactorStyle: The interactor style for the Viewer.
'''
:param interactorStyle: The interactor style for the Viewer.
"""

def __init__(self, parent=None, **kwargs):
'''Creator. Creates an instance of a QFrame and of a CILViewer
The viewer is placed in the QFrame inside a QVBoxLayout.
"""Creator. Creates an instance of a QFrame and of a CILViewer

The viewer is placed in the QFrame inside a QVBoxLayout.
The viewer is accessible as member 'viewer'
'''
"""

super(QCILViewerWidget, self).__init__(parent=parent)
# currently the size of the frame is set by stretching to the whole
# area in the main window. A resize of the MainWindow triggers a resize of
# the QFrame to occupy the whole area available.

dimx, dimy = kwargs.get('shape', (600, 600))
dimx, dimy = kwargs.get("shape", (600, 600))
# self.resize(dimx, dimy)

self.vl = QtWidgets.QVBoxLayout()
#self.vtkWidget = QVTKRenderWindowInteractor(self)
self.vtkWidget = QCILRenderWindowInteractor(self)
self.vl.addWidget(self.vtkWidget)

if 'renderer' in kwargs.keys():
self.ren = kwargs['renderer']
if "renderer" in kwargs.keys():
self.ren = kwargs["renderer"]
else:
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
# https://discourse.vtk.org/t/qvtkwidget-render-window-is-outside-main-qt-app-window/1539/8?u=edoardo_pasca
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()

try:

# print ("provided viewer class ", kwargs['viewer'])
self.viewer = kwargs['viewer'](renWin=self.vtkWidget.GetRenderWindow(),
iren=self.iren,
ren=self.ren,
dimx=dimx,
dimy=dimy,
debug=kwargs.get('debug', False))
self.viewer = kwargs["viewer"](
renWin=self.vtkWidget.GetRenderWindow(),
iren=self.iren,
ren=self.ren,
dimx=dimx,
dimy=dimy,
debug=kwargs.get("debug", False),
)
except KeyError:
raise KeyError("Viewer class not provided. Submit an uninstantiated viewer class object"
"using 'viewer' keyword")
raise KeyError(
"Viewer class not provided. Submit an uninstantiated viewer class object" "using 'viewer' keyword"
)

if 'interactorStyle' in kwargs.keys():
self.viewer.style = kwargs['interactorStyle'](self.viewer)
if "interactorStyle" in kwargs.keys():
self.viewer.style = kwargs["interactorStyle"](self.viewer)
self.viewer.iren.SetInteractorStyle(self.viewer.style)

self.vl = QtWidgets.QVBoxLayout()

toolBar = self.getToolbar(parent)
if toolBar is not None:
self.vl.addWidget(toolBar)
self.vl.addWidget(self.vtkWidget)

self.setLayout(self.vl)
self.adjustSize()

def getToolbar(self, parent=None):
# Adds a toolbar to the QFrame if we have a 3D viewer
if isinstance(self.viewer, viewer3D):
toolBar = QCILViewer3DToolBar(viewer=self.viewer, parent=parent)
return toolBar

class QCILDockableWidget(QtWidgets.QDockWidget):

class QCILDockableWidget(QtWidgets.QDockWidget):
def __init__(self, parent=None, **kwargs):
viewer = kwargs.get('viewer', viewer2D)
shape = kwargs.get('shape', (600, 600))
title = kwargs.get('title', "3D View")
viewer = kwargs.get("viewer", viewer2D)
shape = kwargs.get("shape", (600, 600))
title = kwargs.get("title", "3D View")

super(QCILDockableWidget, self).__init__(parent)

Expand Down
46 changes: 22 additions & 24 deletions Wrappers/Python/ccpi/viewer/iviewer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sys
import vtk
from PySide2 import QtCore, QtWidgets
from PySide2 import QtWidgets
from ccpi.viewer import viewer2D, viewer3D
from ccpi.viewer.QCILViewerWidget import QCILViewerWidget
import ccpi.viewer.viewerLinker as vlink
Expand All @@ -10,14 +10,13 @@


class SingleViewerCenterWidget(QtWidgets.QMainWindow):

def __init__(self, parent=None, viewer=viewer2D):
QtWidgets.QMainWindow.__init__(self, parent)

self.frame = QCILViewerWidget(viewer=viewer, shape=(600, 600))

if viewer == viewer3D:
self.frame.viewer.setVolumeRenderOpacityMethod('scalar')
self.frame.viewer.setVolumeRenderOpacityMethod("scalar")

self.setCentralWidget(self.frame)

Expand All @@ -28,19 +27,18 @@ def set_input(self, data):


class TwoLinkedViewersCenterWidget(QtWidgets.QMainWindow):

def __init__(self, parent=None, viewer1='2D', viewer2='2D'):
def __init__(self, parent=None, viewer1="2D", viewer2="2D"):
QtWidgets.QMainWindow.__init__(self, parent)
#self.resize(800,600)
# self.resize(800,600)
styles = []
viewers = []

for viewer in [viewer1, viewer2]:
if viewer == '2D':
if viewer == "2D":
styles.append(vlink.Linked2DInteractorStyle)
elif viewer == '3D':
elif viewer == "3D":
styles.append(vlink.Linked3DInteractorStyle)
viewers.append(eval('viewer' + viewer))
viewers.append(eval("viewer" + viewer))
self.frame1 = QCILViewerWidget(viewer=viewers[0], shape=(600, 600), interactorStyle=styles[0])
self.frame2 = QCILViewerWidget(viewer=viewers[1], shape=(600, 600), interactorStyle=styles[1])

Expand Down Expand Up @@ -74,7 +72,7 @@ def set_input(self, data1, data2):


class iviewer(object):
'''
"""
a Qt interactive viewer that can be used as plotter2D with one single dataset
Parameters
----------
Expand All @@ -86,11 +84,11 @@ class iviewer(object):
the type of viewer to display the first image on
viewer2: string - '2D' or '3D', optional
the type of viewer to display the second image on (if present)
'''

"""

def __init__(self, data, *moredata, **kwargs):
'''Creator'''
"""Creator"""
app = QtWidgets.QApplication(sys.argv)
self.app = app

Expand All @@ -101,16 +99,16 @@ def setUp(self, data, *moredata, **kwargs):
if len(moredata) == 0:
# can change the behaviour by setting which viewer you want
# between viewer2D and viewer3D
viewer_type = kwargs.get('viewer1', '2D')
if viewer_type == '2D':
viewer_type = kwargs.get("viewer1", "2D")
if viewer_type == "2D":
viewer = viewer2D
elif viewer_type == '3D':
elif viewer_type == "3D":
viewer = viewer3D
window = SingleViewerCenterWidget(viewer=viewer)
window.set_input(self.convert_to_vtkImage(data))
else:
viewer1 = kwargs.get('viewer1', '2D')
viewer2 = kwargs.get('viewer2', '2D')
viewer1 = kwargs.get("viewer1", "2D")
viewer2 = kwargs.get("viewer2", "2D")
window = TwoLinkedViewersCenterWidget(viewer1=viewer1, viewer2=viewer2)
window.set_input(self.convert_to_vtkImage(data), self.convert_to_vtkImage(moredata[0]))
viewer_type = None
Expand All @@ -125,21 +123,21 @@ def show(self):
if self.has_run is None:
self.has_run = self.app.exec_()
else:
print('No instance can be run interactively again. Delete and re-instantiate.')
print("No instance can be run interactively again. Delete and re-instantiate.")

def __del__(self):
'''destructor'''
"""destructor"""
self.app.exit()

def convert_to_vtkImage(self, data):
'''convert the data to vtkImageData for the viewer'''
"""convert the data to vtkImageData for the viewer"""
if isinstance(data, vtk.vtkImageData):
vtkImage = data

elif isinstance(data, np.ndarray):
vtkImage = Converter.numpy2vtkImage(data)

elif hasattr(data, 'as_array'):
elif hasattr(data, "as_array"):
# this makes it likely it is a CIL/SIRF DataContainer
# currently this will only deal with the actual data
# but it will parse the metadata in future
Expand All @@ -155,10 +153,10 @@ def convert_to_vtkImage(self, data):
vtk.vtkOutputWindow.SetInstance(err)

data = example_data.HEAD.get()
iviewer(data, data, viewer1='2D', viewer2='3D')
iviewer(data, data, viewer1="2D", viewer2="3D")

# To use your own metaimage file, uncomment:
# reader = vtk.vtkMetaImageReader()
# reader.SetFileName('head.mha')
# reader.Update()
#iviewer(reader.GetOutput(), reader.GetOutput(), viewer1='2D', viewer2='3D')
# iviewer(reader.GetOutput(), reader.GetOutput(), viewer1='2D', viewer2='3D')
Loading