Esempio n. 1
0
class MainWindow(qtw.QMainWindow):
    def __init__(self, input_file, gaussian, radius, isosurface, window_size,
                 *args, **kwargs):
        """MainWindow constructor"""
        super().__init__(*args, **kwargs)

        # Window setup
        self.resize(window_size[0], window_size[1])
        self.title = "Basic Qt Viewer for MDSC 689.03"

        self.statusBar().showMessage("Welcome.", 8000)

        # Capture defaults
        self.gaussian = gaussian
        self.radius = radius
        self.isosurface = isosurface

        # Initialize the window
        self.initUI()

        # Set up some VTK pipeline classes
        self.reader = None
        self.gauss = vtk.vtkImageGaussianSmooth()
        self.marchingCubes = vtk.vtkImageMarchingCubes()
        self.mapper = vtk.vtkPolyDataMapper()
        self.actor = vtk.vtkActor()

        # Take inputs from command line. Only use these if there is an input file specified
        if (input_file != None):
            if (not os.path.exists(input_file)):
                qtw.QMessageBox.warning(self, "Error", "Invalid input file.")
                return

            #_,ext = os.path.splitext(input_file)
            #if not (self.validExtension(ext.lower())):
            #  qtw.QMessageBox.warning(self, "Error", "Invalid file type.")
            #  return

            self.createPipeline(input_file)
            self.statusBar().showMessage("Loading file " + input_file, 4000)
            self.changeSigma(gaussian)
            self.changeRadius(radius)
            self.changeIsosurface(isosurface)

    def initUI(self):
        ########################################
        # Create Widgets
        ########################################

        self.loadPushButton = qtw.QPushButton(
            "Load Image",
            self,
            objectName="loadPushButton",
            shortcut=qtg.QKeySequence("Ctrl+f"))
        self.sigmaSpinBox = qtw.QDoubleSpinBox(self,
                                               objectName="sigmaSpinBox",
                                               value=self.gaussian,
                                               decimals=1,
                                               maximum=20.0,
                                               minimum=0.1,
                                               singleStep=0.1,
                                               keyboardTracking=False)
        self.radiusSpinBox = qtw.QSpinBox(self,
                                          objectName="radiusSpinBox",
                                          value=self.radius,
                                          maximum=20,
                                          minimum=1,
                                          singleStep=1,
                                          keyboardTracking=False)
        self.isosurfaceSpinBox = qtw.QSpinBox(self,
                                              objectName="isosurfaceSpinBox",
                                              value=self.isosurface,
                                              maximum=32768,
                                              minimum=0,
                                              singleStep=1,
                                              keyboardTracking=False)

        # Create the menu options --------------------------------------------------------------------
        menubar = qtw.QMenuBar()
        self.setMenuBar(menubar)
        menubar.setNativeMenuBar(False)

        file_menu = menubar.addMenu("File")
        open_action = file_menu.addAction("Open Image")
        file_menu.addSeparator()
        about_action = file_menu.addAction("About")
        quit_action = file_menu.addAction("Quit")

        # Lay out the GUI ----------------------------------------------------------------------------
        self.mainGroupBox = qtw.QGroupBox("Image Controls")
        self.mainGroupBox.setLayout(qtw.QHBoxLayout())

        self.isosurfaceGroupBox = qtw.QGroupBox("Isosurface controls")
        self.isosurfaceGroupBox.setLayout(qtw.QVBoxLayout())
        self.isosurfaceFormLayout = qtw.QFormLayout()
        self.isosurfaceFormLayout.addRow("Sigma", self.sigmaSpinBox)
        self.isosurfaceFormLayout.addRow("Radius", self.radiusSpinBox)
        self.isosurfaceFormLayout.addRow("Isosurface", self.isosurfaceSpinBox)
        self.isosurfaceGroupBox.layout().addLayout(self.isosurfaceFormLayout)

        self.mainGroupBox.layout().addWidget(self.loadPushButton)
        self.mainGroupBox.layout().addWidget(self.isosurfaceGroupBox)

        # Assemble the side control panel and put it in a QPanel widget ------------------------------
        self.panel = qtw.QVBoxLayout()
        self.panel.addWidget(self.mainGroupBox)
        self.panelWidget = qtw.QFrame()
        self.panelWidget.setLayout(self.panel)

        # Create the VTK rendering window ------------------------------------------------------------
        self.vtkWidget = QVTKRenderWindowInteractor()
        self.vtkWidget.AddObserver("ExitEvent", lambda o, e, a=self: a.quit())
        self.vtkWidget.AddObserver("KeyReleaseEvent", self.keyEventDetected)
        self.vtkWidget.AddObserver("LeftButtonPressEvent",
                                   self.mouseEventDetected)

        # Create main layout and add VTK window and control panel
        self.mainLayout = qtw.QHBoxLayout()
        self.mainLayout.addWidget(self.vtkWidget, 4)
        self.mainLayout.addWidget(self.panelWidget, 1)

        self.frame = qtw.QFrame()
        self.frame.setLayout(self.mainLayout)
        self.setCentralWidget(self.frame)

        self.setWindowTitle(self.title)
        self.centreWindow()

        # Set size policies --------------------------------------------------------------------------
        self.sigmaSpinBox.setMinimumSize(70, 20)
        self.radiusSpinBox.setMinimumSize(70, 20)
        self.isosurfaceSpinBox.setMinimumSize(70, 20)

        self.mainGroupBox.setMaximumSize(1000, 200)

        self.vtkWidget.setSizePolicy(qtw.QSizePolicy.MinimumExpanding,
                                     qtw.QSizePolicy.MinimumExpanding)

        self.mainGroupBox.setSizePolicy(qtw.QSizePolicy.Maximum,
                                        qtw.QSizePolicy.Maximum)

        # Connect signals and slots ------------------------------------------------------------------
        self.loadPushButton.clicked.connect(self.openFile)
        self.sigmaSpinBox.valueChanged.connect(lambda s: self.changeSigma(s))
        self.radiusSpinBox.valueChanged.connect(lambda s: self.changeRadius(s))
        self.isosurfaceSpinBox.valueChanged.connect(
            lambda s: self.changeIsosurface(s))

        self.initRenderWindow()

        # Menu actions
        open_action.triggered.connect(self.openFile)
        about_action.triggered.connect(self.about)
        quit_action.triggered.connect(self.quit)

        self.pipe = None

        # End main UI code
        self.show()

        ########################################
        # Define methods for controlling GUI
        ########################################

    def centreWindow(self):
        qr = self.frameGeometry()
        cp = qtw.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def initRenderWindow(self):
        # Create renderer
        self.renderer = vtk.vtkRenderer()
        self.renderer.SetBackground(
            (0.000, 0.000, 204.0 / 255.0))  # Scanco blue

        # Create interactor
        self.renWin = self.vtkWidget.GetRenderWindow()
        self.renWin.AddRenderer(self.renderer)
        self.iren = self.renWin.GetInteractor()
        self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())

        # Initialize
        self.iren.Initialize()
        self.iren.Start()

    def createPipeline(self, _filename):
        # Read in the file
        if _filename.lower().endswith('.nii'):
            self.reader = vtk.vtkNIFTIImageReader()
            self.reader.SetFileName(_filename)
        elif _filename.lower().endswith('.nii.gz'):
            self.reader = vtk.vtkNIFTIImageReader()
            self.reader.SetFileName(_filename)
        elif _filename.lower().endswith('.dcm'):
            self.reader = vtk.vtkDICOMImageReader()
            self.reader.SetDirectoryName(os.path.dirname(_filename))
        elif os.path.isdir(_filename):
            self.reader = vtk.vtkDICOMImageReader()
            self.reader.SetDirectoryName(_filename)

        if self.reader is None:
            os.sys.exit("[ERROR] Cannot find reader for file \"{}\"".format(
                self.filename))
        self.reader.Update()

        # Gaussian smoothing
        self.gauss.SetStandardDeviation(self.gaussian, self.gaussian,
                                        self.gaussian)
        self.gauss.SetRadiusFactors(self.radius, self.radius, self.radius)
        self.gauss.SetInputConnection(self.reader.GetOutputPort())

        # Marching Cubes
        self.marchingCubes.SetInputConnection(self.gauss.GetOutputPort())
        self.marchingCubes.ComputeGradientsOn()
        self.marchingCubes.ComputeNormalsOn()
        self.marchingCubes.ComputeScalarsOff()
        self.marchingCubes.SetNumberOfContours(1)
        self.marchingCubes.SetValue(0, self.isosurface)

        # Set mapper for image data
        self.mapper.SetInputConnection(self.marchingCubes.GetOutputPort())

        # Actor
        self.actor.SetMapper(self.mapper)
        self.actor.GetProperty().SetColor((0.890, 0.855, 0.788))
        self.renderer.AddActor(self.actor)

        self.refreshRenderWindow()
        return

    def changeSigma(self, _value):
        self.gauss.SetStandardDeviation(_value, _value, _value)
        self.statusBar().showMessage(
            f"Changing standard deviation to {_value}", 4000)
        self.refreshRenderWindow()
        return

    def changeRadius(self, _value):
        self.gauss.SetRadiusFactors(_value, _value, _value)
        self.statusBar().showMessage(f"Changing radius to {_value}", 4000)
        self.refreshRenderWindow()
        return

    def changeIsosurface(self, _value):
        self.marchingCubes.SetValue(0, _value)
        self.statusBar().showMessage(f"Changing isosurface to {_value}", 4000)
        self.refreshRenderWindow()
        return

    def keyEventDetected(self, obj, event):
        key = self.vtkWidget.GetKeySym()
        print("key press – clicked " + key + "!")
        return

    def mouseEventDetected(self, obj, event):
        print("mouse press – click!")
        return

    def validExtension(self, extension):
        if (extension == ".nii" or \
            extension == ".dcm"):
            return True
        else:
            return False

    def openFile(self):
        self.statusBar().showMessage("Load image types (.nii, .dcm)", 4000)
        filename, _ = qtw.QFileDialog.getOpenFileName(
            self, "Select a 3D image file to open…", qtc.QDir.homePath(),
            "Nifti Files (*.nii) ;;DICOM Files (*.dcm) ;;All Files (*)",
            "All Files (*)", qtw.QFileDialog.DontUseNativeDialog
            | qtw.QFileDialog.DontResolveSymlinks)

        if filename:
            _, ext = os.path.splitext(filename)
            if not (self.validExtension(ext.lower())):
                qtw.QMessageBox.warning(self, "Error", "Invalid file type.")
                return

            self.createPipeline(filename)
            self.statusBar().showMessage("Loading file " + filename, 4000)
        return

    def quit(self):
        reply = qtw.QMessageBox.question(
            self, "Message", "Are you sure you want to quit?",
            qtw.QMessageBox.Yes | qtw.QMessageBox.No, qtw.QMessageBox.Yes)
        if reply == qtw.QMessageBox.Yes:
            exit(0)

    def about(self):
        about = qtw.QMessageBox(self)
        about.setText("blQtBasic 1.0")
        about.setInformativeText(
            "Copyright (C) 2021\nBone Imaging Laboratory\nAll rights reserved.\[email protected]"
        )
        about.setStandardButtons(qtw.QMessageBox.Ok | qtw.QMessageBox.Cancel)
        about.exec_()
Esempio n. 2
0
class MainWindow(qtw.QMainWindow):
    def __init__(self, input_file, gaussian, radius, isosurface, window_size,
                 *args, **kwargs):
        """MainWindow constructor"""
        super().__init__(*args, **kwargs)

        # Window setup
        self.resize(window_size[0], window_size[1])
        self.title = "Bone Imaging Laboratory – Viewer"
        self.iconPath = os.path.join(os.getcwd(), "qtviewer")
        self.iconPath = os.path.abspath(os.path.join(self.iconPath,
                                                     "icon.png"))

        self.statusBar().showMessage("Welcome.", 8000)

        # Initialize the window
        self.initUI()

        # Take inputs from command line. Only use these if there is an input file specified
        if (input_file != None):
            if (not os.path.exists(input_file)):
                qtw.QMessageBox.warning(self, "Error", "Invalid input file.")
                return

            _, ext = os.path.splitext(input_file)
            if not (self.validExtension(ext.lower())):
                qtw.QMessageBox.warning(self, "Error", "Invalid file type.")
                return

            self.createPipeline(input_file, "in1")
            self.statusBar().showMessage("Loading file " + input_file, 4000)
            self.changeSigma(gaussian, "in1")
            self.changeRadius(radius, "in1")
            self.changeIsosurface(isosurface, "in1")
            self.updateGUI()

    def initUI(self):
        ########################################
        # Create Widgets
        ########################################

        # Fixed image (in1)
        self.in1_loadPushButton = qtw.QPushButton(
            "Load Image 1",
            self,
            objectName="in1_loadPushButton",
            shortcut=qtg.QKeySequence("Ctrl+f"))
        self.in1_pickableCheckBox = qtw.QCheckBox(
            "Pickable",
            self,
            objectName="in1_pickableCheckBox",
            checkable=True,
            checked=True)
        self.in1_visibilityCheckBox = qtw.QCheckBox(
            "Visibility",
            self,
            objectName="in1_visibilityCheckBox",
            checkable=True,
            checked=True)
        self.in1_filenameLabel = qtw.QLabel("", self)

        self.in1_sigmaSpinBox = qtw.QDoubleSpinBox(
            self,
            objectName="in1_sigmaSpinBox",
            value=1.2,
            decimals=1,
            maximum=20.0,
            minimum=0.1,
            singleStep=0.1,
            keyboardTracking=False)
        self.in1_radiusSpinBox = qtw.QSpinBox(self,
                                              objectName="in1_radiusSpinBox",
                                              value=2,
                                              maximum=20,
                                              minimum=1,
                                              singleStep=1,
                                              keyboardTracking=False)
        self.in1_isosurfaceSpinBox = qtw.QSpinBox(
            self,
            objectName="in1_isosurfaceSpinBox",
            value=0,
            maximum=32768,
            minimum=0,
            singleStep=1,
            keyboardTracking=False)

        # Moving image (in2)
        self.in2_loadPushButton = qtw.QPushButton(
            "Load Image 2",
            self,
            objectName="in2_loadPushButton",
            shortcut=qtg.QKeySequence("Ctrl+m"))
        self.in2_pickableCheckBox = qtw.QCheckBox(
            "Pickable",
            self,
            objectName="in2_pickableCheckBox",
            checkable=True,
            checked=False)
        self.in2_visibilityCheckBox = qtw.QCheckBox(
            "Visibility",
            self,
            objectName="in2_visibilityCheckBox",
            checkable=True,
            checked=True)
        self.in2_filenameLabel = qtw.QLabel("", self)

        self.in2_sigmaSpinBox = qtw.QDoubleSpinBox(
            self,
            objectName="in2_sigmaSpinBox",
            value=1.2,
            decimals=1,
            maximum=20.0,
            minimum=0.1,
            singleStep=0.1,
            keyboardTracking=False)
        self.in2_radiusSpinBox = qtw.QSpinBox(self,
                                              objectName="in2_radiusSpinBox",
                                              value=2,
                                              maximum=20,
                                              minimum=1,
                                              singleStep=1,
                                              keyboardTracking=False)
        self.in2_isosurfaceSpinBox = qtw.QSpinBox(
            self,
            objectName="in2_isosurfaceSpinBox",
            value=0,
            maximum=32768,
            minimum=0,
            singleStep=1,
            keyboardTracking=False)

        # Camera controls
        self.rollCameraPushButton = qtw.QPushButton(
            "Roll", self, objectName="rollCameraPushButton")
        self.elevationCameraPushButton = qtw.QPushButton(
            "Elevation", self, objectName="elevationCameraPushButton")
        self.azimuthCameraPushButton = qtw.QPushButton(
            "Azimuth", self, objectName="azimuthCameraPushButton")
        self.incrementCameraSpinBox = qtw.QSpinBox(
            self,
            objectName="incrementCameraSpinBox",
            value=90,
            maximum=90,
            minimum=-90,
            singleStep=10)

        # Landmark transform
        self.landmarkTransformPushButton = qtw.QPushButton(
            "Landmark",
            self,
            objectName="Landmark Transform",
            enabled=False,
            shortcut=qtg.QKeySequence("Ctrl+l"))

        # ICP transform
        self.icpTransformPushButton = qtw.QPushButton(
            "ICP",
            self,
            objectName="ICP Transform",
            enabled=True,
            shortcut=qtg.QKeySequence("Ctrl+i"))

        # Reset transform
        self.resetTransformPushButton = qtw.QPushButton(
            "Reset",
            self,
            objectName="Reset Transform",
            enabled=True,
            shortcut=qtg.QKeySequence("Ctrl+r"))

        self.viewTransformCheckBox = qtw.QCheckBox("Toggle View",
                                                   self,
                                                   objectName="View Transform",
                                                   checkable=True,
                                                   checked=True)

        self.in1_points_count_label = qtw.QLabel("in1_points_label",
                                                 self,
                                                 text="Points")
        self.in2_points_count_label = qtw.QLabel("in2_points_label",
                                                 self,
                                                 text="Points")
        self.in1_points_count = qtw.QLCDNumber(
            self, intValue=0, segmentStyle=qtw.QLCDNumber.Flat)
        self.in2_points_count = qtw.QLCDNumber(
            self, intValue=0, segmentStyle=qtw.QLCDNumber.Flat)

        # Vectors
        self.rot1 = qtw.QLabel("rot1", self, text="  0.000")
        self.rot2 = qtw.QLabel("rot2", self, text="  0.000")
        self.rot3 = qtw.QLabel("rot3", self, text="  0.000")
        self.trans1 = qtw.QLabel("trans1", self, text="  0.000")
        self.trans2 = qtw.QLabel("trans2", self, text="  0.000")
        self.trans3 = qtw.QLabel("trans3", self, text="  0.000")
        self.rotLabel = qtw.QLabel("R:", self)
        self.transLabel = qtw.QLabel("T:", self)

        # 4x4 matrix
        self.mat11 = qtw.QLabel("mat11", self, text="  0.000")
        self.mat12 = qtw.QLabel("mat12", self, text="  0.000")
        self.mat13 = qtw.QLabel("mat13", self, text="  0.000")
        self.mat14 = qtw.QLabel("mat14", self, text="  0.000")
        self.mat21 = qtw.QLabel("mat21", self, text="  0.000")
        self.mat22 = qtw.QLabel("mat22", self, text="  0.000")
        self.mat23 = qtw.QLabel("mat23", self, text="  0.000")
        self.mat24 = qtw.QLabel("mat24", self, text="  0.000")
        self.mat31 = qtw.QLabel("mat31", self, text="  0.000")
        self.mat32 = qtw.QLabel("mat32", self, text="  0.000")
        self.mat33 = qtw.QLabel("mat33", self, text="  0.000")
        self.mat34 = qtw.QLabel("mat34", self, text="  0.000")
        self.mat41 = qtw.QLabel("mat41", self, text="  0.000")
        self.mat42 = qtw.QLabel("mat42", self, text="  0.000")
        self.mat43 = qtw.QLabel("mat43", self, text="  0.000")
        self.mat44 = qtw.QLabel("mat44", self, text="  0.000")

        self.updateMatrixGUI(vtk.vtkMatrix4x4())

        # Log window
        self.log_window = qtw.QTextEdit(self,
                                        objectName="log_window",
                                        acceptRichText=False,
                                        lineWrapMode=qtw.QTextEdit.NoWrap,
                                        lineWrapColumnOrWidth=80,
                                        placeholderText="Ready...")

        # Create the menu options --------------------------------------------------------------------
        menubar = qtw.QMenuBar()
        self.setMenuBar(menubar)
        menubar.setNativeMenuBar(False)

        file_menu = menubar.addMenu("File")
        open_in1_action = file_menu.addAction("Open Image1")
        open_in2_action = file_menu.addAction("Open Image2")
        file_menu.addSeparator()
        about_action = file_menu.addAction("About")
        quit_action = file_menu.addAction("Quit")

        save_menu = menubar.addMenu("Save")
        save_log_action = save_menu.addAction("Log Window")
        save_menu.addSeparator()
        save_points_action = save_menu.addAction("Points")
        save_transform_matrix_action = save_menu.addAction("Transform Matrix")
        save_transform_vector_action = save_menu.addAction("Transform Vector")
        save_menu.addSeparator()
        save_extrusion_action = save_menu.addAction("Extruded Shape")

        ########################################
        # Layouts
        ########################################

        # Fixed Image (in1) --------------------------------------------------------------------------
        self.in1_mainGroupBox = qtw.QGroupBox("Fixed Image (in1)")
        self.in1_mainGroupBox.setLayout(qtw.QHBoxLayout())

        self.in1_pointWidget = qtw.QFrame()
        self.in1_pointWidget.setLayout(qtw.QHBoxLayout())
        self.in1_pointWidget.layout().addWidget(self.in1_points_count_label)
        self.in1_pointWidget.layout().addWidget(self.in1_points_count)

        self.in1_loadGridLayout = qtw.QGridLayout()
        self.in1_loadGridLayout.addWidget(self.in1_loadPushButton, 0, 0, 1, 1)
        self.in1_loadGridLayout.addWidget(self.in1_pickableCheckBox, 0, 1, 1,
                                          1)
        self.in1_loadGridLayout.addWidget(self.in1_pointWidget, 1, 0, 1, 1)
        self.in1_loadGridLayout.addWidget(self.in1_visibilityCheckBox, 1, 1, 1,
                                          1)
        self.in1_loadGridLayout.addWidget(self.in1_filenameLabel, 2, 0, 1, 4)

        self.in1_isosurfaceGroupBox = qtw.QGroupBox("Isosurface controls")
        self.in1_isosurfaceGroupBox.setLayout(qtw.QVBoxLayout())
        self.in1_isosurfaceFormLayout = qtw.QFormLayout()
        self.in1_isosurfaceFormLayout.addRow("Sigma", self.in1_sigmaSpinBox)
        self.in1_isosurfaceFormLayout.addRow("Radius", self.in1_radiusSpinBox)
        self.in1_isosurfaceFormLayout.addRow("Isosurface",
                                             self.in1_isosurfaceSpinBox)
        self.in1_isosurfaceGroupBox.layout().addLayout(
            self.in1_isosurfaceFormLayout)

        self.in1_mainGroupBox.layout().addLayout(self.in1_loadGridLayout)
        self.in1_mainGroupBox.layout().addWidget(self.in1_isosurfaceGroupBox)

        # Moving Image (in2) -------------------------------------------------------------------------
        self.in2_mainGroupBox = qtw.QGroupBox("Moving Image (in2)")
        self.in2_mainGroupBox.setLayout(qtw.QHBoxLayout())

        self.in2_pointWidget = qtw.QFrame()
        self.in2_pointWidget.setLayout(qtw.QHBoxLayout())
        self.in2_pointWidget.layout().addWidget(self.in2_points_count_label)
        self.in2_pointWidget.layout().addWidget(self.in2_points_count)

        self.in2_loadGridLayout = qtw.QGridLayout()
        self.in2_loadGridLayout.addWidget(self.in2_loadPushButton, 0, 0, 1, 1)
        self.in2_loadGridLayout.addWidget(self.in2_pickableCheckBox, 0, 1, 1,
                                          1)
        self.in2_loadGridLayout.addWidget(self.in2_pointWidget, 1, 0, 1, 1)
        self.in2_loadGridLayout.addWidget(self.in2_visibilityCheckBox, 1, 1, 1,
                                          1)
        self.in2_loadGridLayout.addWidget(self.in2_filenameLabel, 2, 0, 1, 4)

        self.in2_isosurfaceGroupBox = qtw.QGroupBox("Isosurface controls")
        self.in2_isosurfaceGroupBox.setLayout(qtw.QVBoxLayout())
        self.in2_isosurfaceFormLayout = qtw.QFormLayout()
        self.in2_isosurfaceFormLayout.addRow("Sigma", self.in2_sigmaSpinBox)
        self.in2_isosurfaceFormLayout.addRow("Radius", self.in2_radiusSpinBox)
        self.in2_isosurfaceFormLayout.addRow("Isosurface",
                                             self.in2_isosurfaceSpinBox)
        self.in2_isosurfaceGroupBox.layout().addLayout(
            self.in2_isosurfaceFormLayout)

        self.in2_mainGroupBox.layout().addLayout(self.in2_loadGridLayout)
        self.in2_mainGroupBox.layout().addWidget(self.in2_isosurfaceGroupBox)

        # Camera controls panel ----------------------------------------------------------------------
        self.cameraControlsGroupBox = qtw.QGroupBox("Camera")
        self.cameraControlsGroupBox.setLayout(qtw.QHBoxLayout())
        self.cameraControlsGroupBox.layout().addWidget(
            self.rollCameraPushButton)
        self.cameraControlsGroupBox.layout().addWidget(
            self.elevationCameraPushButton)
        self.cameraControlsGroupBox.layout().addWidget(
            self.azimuthCameraPushButton)
        self.incrementCameraFormLayout = qtw.QFormLayout()
        self.incrementCameraFormLayout.addRow("Increment",
                                              self.incrementCameraSpinBox)
        self.cameraControlsGroupBox.layout().addLayout(
            self.incrementCameraFormLayout)

        # Transform panel ----------------------------------------------------------------------------
        self.transformPanelGroupBox = qtw.QGroupBox("Transform")
        self.transformPanelGroupBox.setLayout(qtw.QHBoxLayout())

        # Transform actions
        self.transformLayout = qtw.QGridLayout()
        self.transformLayout.addWidget(self.landmarkTransformPushButton, 0, 0,
                                       1, 1)
        self.transformLayout.addWidget(self.icpTransformPushButton, 0, 1, 1, 1)
        self.transformLayout.addWidget(self.resetTransformPushButton, 1, 0, 1,
                                       1)
        self.transformLayout.addWidget(self.viewTransformCheckBox, 1, 1, 1, 1)

        # Vectors display
        self.vectorsLayout = qtw.QGridLayout()
        self.vectorsLayout.addWidget(self.rotLabel, 0, 0, 1, 1)
        self.vectorsLayout.addWidget(self.rot1, 0, 1, 1, 1)
        self.vectorsLayout.addWidget(self.rot2, 0, 2, 1, 1)
        self.vectorsLayout.addWidget(self.rot3, 0, 3, 1, 1)
        self.vectorsLayout.addWidget(self.transLabel, 1, 0, 1, 1)
        self.vectorsLayout.addWidget(self.trans1, 1, 1, 1, 1)
        self.vectorsLayout.addWidget(self.trans2, 1, 2, 1, 1)
        self.vectorsLayout.addWidget(self.trans3, 1, 3, 1, 1)

        self.vectorsGroupBox = qtw.QGroupBox()
        self.vectorsGroupBox.setLayout(self.vectorsLayout)
        self.transformLayout.addWidget(self.vectorsGroupBox, 2, 0, 2, 2)

        # Matrix display
        self.matrixLayout = qtw.QGridLayout()
        self.matrixLayout.addWidget(self.mat11, 0, 0, 1, 1)
        self.matrixLayout.addWidget(self.mat12, 0, 1, 1, 1)
        self.matrixLayout.addWidget(self.mat13, 0, 2, 1, 1)
        self.matrixLayout.addWidget(self.mat14, 0, 3, 1, 1)
        self.matrixLayout.addWidget(self.mat21, 1, 0, 1, 1)
        self.matrixLayout.addWidget(self.mat22, 1, 1, 1, 1)
        self.matrixLayout.addWidget(self.mat23, 1, 2, 1, 1)
        self.matrixLayout.addWidget(self.mat24, 1, 3, 1, 1)
        self.matrixLayout.addWidget(self.mat31, 2, 0, 1, 1)
        self.matrixLayout.addWidget(self.mat32, 2, 1, 1, 1)
        self.matrixLayout.addWidget(self.mat33, 2, 2, 1, 1)
        self.matrixLayout.addWidget(self.mat34, 2, 3, 1, 1)
        self.matrixLayout.addWidget(self.mat41, 3, 0, 1, 1)
        self.matrixLayout.addWidget(self.mat42, 3, 1, 1, 1)
        self.matrixLayout.addWidget(self.mat43, 3, 2, 1, 1)
        self.matrixLayout.addWidget(self.mat44, 3, 3, 1, 1)

        self.matrixGroupBox = qtw.QGroupBox("Matrix4x4 (in2)")
        self.matrixGroupBox.setLayout(self.matrixLayout)

        self.transformPanelGroupBox.layout().addLayout(self.transformLayout)
        self.transformPanelGroupBox.layout().addWidget(self.matrixGroupBox)

        # Set up the log window ----------------------------------------------------------------------
        font = qtg.QFont("Courier")
        font.setStyleHint(qtg.QFont.TypeWriter)
        font.setWeight(25)
        self.log_window.setTextColor(qtg.QColor("blue"))
        self.log_window.setCurrentFont(font)

        # Add logo -----------------------------------------------------------------------------------
        logo = qtg.QPixmap(':/bonelab/icon.png')
        self.logoLabel = qtw.QLabel("", self)
        self.logoLabel.setPixmap(logo)

        # Assemble the side control panel and put it in a QPanel widget ------------------------------
        self.panel = qtw.QVBoxLayout()
        self.panel.addWidget(self.in1_mainGroupBox)
        self.panel.addWidget(self.in2_mainGroupBox)
        self.panel.addWidget(self.cameraControlsGroupBox)
        self.panel.addWidget(self.transformPanelGroupBox)
        self.panel.addWidget(self.log_window)
        self.panel.addWidget(self.logoLabel, alignment=qtc.Qt.AlignRight)
        self.panelWidget = qtw.QFrame()
        self.panelWidget.setLayout(self.panel)

        # Create the VTK rendering window ------------------------------------------------------------
        self.vtkWidget = QVTKRenderWindowInteractor()
        self.vtkWidget.AddObserver("ExitEvent", lambda o, e, a=self: a.quit())
        #self.vtkWidget.AddObserver("KeyReleaseEvent", self.keyEventDetected)
        self.vtkWidget.AddObserver("LeftButtonReleaseEvent",
                                   self.mouseEventDetected)

        # Create main layout and add VTK window and control panel
        self.mainLayout = qtw.QHBoxLayout()
        self.mainLayout.addWidget(self.vtkWidget, 4)
        self.mainLayout.addWidget(self.panelWidget, 2)

        self.frame = qtw.QFrame()
        self.frame.setLayout(self.mainLayout)
        self.setCentralWidget(self.frame)

        self.setWindowTitle(self.title)
        self.centreWindow()
        #print(self.iconPath)
        self.setWindowIcon(qtg.QIcon(self.iconPath))

        self.cp = ColourPalette()

        # Set size policies --------------------------------------------------------------------------
        self.in1_sigmaSpinBox.setMinimumSize(70, 20)
        self.in1_radiusSpinBox.setMinimumSize(70, 20)
        self.in1_isosurfaceSpinBox.setMinimumSize(70, 20)

        self.in2_sigmaSpinBox.setMinimumSize(70, 20)
        self.in2_radiusSpinBox.setMinimumSize(70, 20)
        self.in2_isosurfaceSpinBox.setMinimumSize(70, 20)

        self.in1_mainGroupBox.setMaximumSize(1000, 1000)
        self.in2_mainGroupBox.setMaximumSize(1000, 1000)
        self.transformPanelGroupBox.setMaximumSize(1000, 1000)

        self.rotLabel.setMaximumSize(15, 20)
        self.transLabel.setMaximumSize(15, 20)

        self.vtkWidget.setSizePolicy(qtw.QSizePolicy.MinimumExpanding,
                                     qtw.QSizePolicy.MinimumExpanding)

        self.in1_points_count.setMaximumSize(50, 30)
        self.in2_points_count.setMaximumSize(50, 30)

        self.in1_mainGroupBox.setSizePolicy(qtw.QSizePolicy.Maximum,
                                            qtw.QSizePolicy.Maximum)

        self.in2_mainGroupBox.setSizePolicy(qtw.QSizePolicy.Maximum,
                                            qtw.QSizePolicy.Maximum)

        self.transformPanelGroupBox.setSizePolicy(qtw.QSizePolicy.Maximum,
                                                  qtw.QSizePolicy.Maximum)

        self.log_window.setSizePolicy(qtw.QSizePolicy.MinimumExpanding,
                                      qtw.QSizePolicy.MinimumExpanding)

        # Connect signals and slots ------------------------------------------------------------------
        # The use of the lambda function is so that the same slot can be used for either pipeline
        self.in1_loadPushButton.clicked.connect(lambda: self.openFile("in1"))
        self.in1_pickableCheckBox.stateChanged.connect(
            lambda s: self.togglePickable(s, "in1"))
        self.in1_visibilityCheckBox.stateChanged.connect(
            lambda s: self.toggleVisibility(s, "in1"))
        self.in1_sigmaSpinBox.valueChanged.connect(
            lambda s: self.changeSigma(s, "in1"))
        self.in1_radiusSpinBox.valueChanged.connect(
            lambda s: self.changeRadius(s, "in1"))
        self.in1_isosurfaceSpinBox.valueChanged.connect(
            lambda s: self.changeIsosurface(s, "in1"))

        self.in2_loadPushButton.clicked.connect(lambda: self.openFile("in2"))
        self.in2_pickableCheckBox.stateChanged.connect(
            lambda s: self.togglePickable(s, "in2"))
        self.in2_visibilityCheckBox.stateChanged.connect(
            lambda s: self.toggleVisibility(s, "in2"))
        self.in2_sigmaSpinBox.valueChanged.connect(
            lambda s: self.changeSigma(s, "in2"))
        self.in2_radiusSpinBox.valueChanged.connect(
            lambda s: self.changeRadius(s, "in2"))
        self.in2_isosurfaceSpinBox.valueChanged.connect(
            lambda s: self.changeIsosurface(s, "in2"))

        self.initRenderWindow()
        self.in2_mainGroupBox.setEnabled(
            False)  # Force user to load in1 before in2

        # Camera
        self.rollCameraPushButton.clicked.connect(
            lambda: self.updateCamera("roll"))
        self.elevationCameraPushButton.clicked.connect(
            lambda: self.updateCamera("elevation"))
        self.azimuthCameraPushButton.clicked.connect(
            lambda: self.updateCamera("azimuth"))

        # Transform
        self.landmarkTransformPushButton.clicked.connect(
            self.applyLandmarkTransform)
        self.icpTransformPushButton.clicked.connect(self.applyICPTransform)
        self.resetTransformPushButton.clicked.connect(self.resetTransform)
        self.viewTransformCheckBox.stateChanged.connect(
            self.toggleTransformApplied)

        # Menu actions
        open_in1_action.triggered.connect(lambda: self.openFile("in1"))
        open_in2_action.triggered.connect(lambda: self.openFile("in2"))
        about_action.triggered.connect(self.about)
        quit_action.triggered.connect(self.quit)

        save_log_action.triggered.connect(self.saveLogFile)
        save_points_action.triggered.connect(self.savePointsFile)
        save_transform_matrix_action.triggered.connect(
            lambda: self.saveTransformFile("matrix"))
        save_transform_vector_action.triggered.connect(
            lambda: self.saveTransformFile("vector"))
        save_extrusion_action.triggered.connect(self.extrudeFromPoints)

        # Variables for managing the VTK pipelines
        self.in1_pipe = None
        self.in2_pipe = None

        # End main UI code
        self.show()

    def centreWindow(self):
        qr = self.frameGeometry()
        cp = qtw.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def initRenderWindow(self):
        # Create renderer
        self.renderer = vtk.vtkRenderer()
        self.renderer.SetBackground(self.cp.getColour("background2"))

        # Create interactor
        self.renWin = self.vtkWidget.GetRenderWindow()
        self.renWin.AddRenderer(self.renderer)
        self.iren = self.renWin.GetInteractor()

        self.pickerstyle = MyInteractorStyle()
        self.pickerstyle.AddObserver("UpdateEvent", self.keyEventDetected)
        #self.pickerstyle.SetCurrentStyleToTrackballCamera()
        self.iren.SetInteractorStyle(self.pickerstyle)
        #print(self.iren.GetInteractorStyle().GetClassName())

        # Initialize
        self.iren.Initialize()
        self.iren.Start()

    def refreshRenderWindow(self):
        self.renWin.Render()
        self.renderer.ResetCamera()
        self.iren.Render()

    def setPipelineAttributesFromGUI(self, pipeline):
        if (pipeline == "in1"):
            if self.in1_pickableCheckBox.isChecked():
                self.in1_pipe.setActorPickable(1)
            else:
                self.in1_pipe.setActorPickable(0)
            if self.in1_visibilityCheckBox.isChecked():
                self.in1_pipe.setActorVisibility(1)
            else:
                self.in1_pipe.setActorVisibility(0)
            self.in1_pipe.setGaussStandardDeviation(
                self.in1_sigmaSpinBox.value())
            self.in1_pipe.setGaussRadius(self.in1_radiusSpinBox.value())
            self.in1_pipe.setIsosurface(self.in1_isosurfaceSpinBox.value())
        elif (pipeline == "in2"):
            if self.in2_pickableCheckBox.isChecked():
                self.in2_pipe.setActorPickable(1)
            else:
                self.in2_pipe.setActorPickable(0)
            if self.in2_visibilityCheckBox.isChecked():
                self.in2_pipe.setActorVisibility(1)
            else:
                self.in2_pipe.setActorVisibility(0)
            self.in2_pipe.setGaussStandardDeviation(
                self.in2_sigmaSpinBox.value())
            self.in2_pipe.setGaussRadius(self.in2_radiusSpinBox.value())
            self.in2_pipe.setIsosurface(self.in2_isosurfaceSpinBox.value())
        else:
            return

    def createPipeline(self, _filename, pipeline):
        # Remove any existing pipelines before creating a new one
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                self.renderer.RemoveActor(self.in1_pipe.getActor())
                self.pickerstyle.removePoints("in1_pipeline")
                del self.in1_pipe
                self.in1_pipe = None
            self.in1_pipe = Pipeline()
            self.in1_pipe.setActorColor(self.cp.getColour("bone1"))
            self.in1_filenameLabel.setText(os.path.basename(_filename))
            self.in1_filenameLabel.setToolTip(_filename)
            ptr = self.in1_pipe
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                self.renderer.RemoveActor(self.in2_pipe.getActor())
                self.pickerstyle.removePoints("in2_pipeline")
                del self.in2_pipe
                self.in2_pipe = None
            self.in2_pipe = Pipeline()
            self.in2_pipe.setActorColor(self.cp.getColour("bone2"))
            self.in2_filenameLabel.setText(os.path.basename(_filename))
            self.in2_filenameLabel.setToolTip(_filename)
            ptr = self.in2_pipe
        else:
            return

        self.setPipelineAttributesFromGUI(pipeline)
        ptr.constructPipeline(_filename)
        ptr.addActor(self.renderer)
        self.pickerstyle.setMainActor(ptr.getActor(), (pipeline + "_pipeline"))
        self.log_window.append(ptr.getProcessingLog())
        self.log_window.append(ptr.getImageInfoLog())
        self.refreshRenderWindow()
        self.updateGUI()
        return

    def togglePickable(self, _state, pipeline):
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                ptr = self.in1_pipe
            else:
                return
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                ptr = self.in2_pipe
            else:
                return

        # Only two states are possible
        if (qtc.Qt.Checked == _state):
            ptr.setActorPickable(1)
            self.statusBar().showMessage("Toggling actor pickability ON", 4000)
            self.refreshRenderWindow()
        else:
            ptr.setActorPickable(0)
            self.statusBar().showMessage("Toggling actor pickability OFF",
                                         4000)
            self.refreshRenderWindow()
        return

    def toggleVisibility(self, _state, pipeline):
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                name = "in1_pipeline"
                ptr = self.in1_pipe
            else:
                return
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                name = "in2_pipeline"
                ptr = self.in2_pipe
            else:
                return

        # Only two states are possible
        if (qtc.Qt.Checked == _state):
            ptr.setActorVisibility(1)
            self.pickerstyle.setVisibilityOfPoints(name, 1)
            self.statusBar().showMessage("Toggling actor visibility ON", 4000)
            self.refreshRenderWindow()
        else:
            ptr.setActorVisibility(0)
            self.pickerstyle.setVisibilityOfPoints(name, 0)
            self.statusBar().showMessage("Toggling actor visibility OFF", 4000)
            self.refreshRenderWindow()
        return

    def changeSigma(self, _value, pipeline):
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                self.in1_pipe.setGaussStandardDeviation(_value)
                self.statusBar().showMessage(
                    f"Changing standard deviation to {_value}", 4000)
                self.refreshRenderWindow()
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                self.in2_pipe.setGaussStandardDeviation(_value)
                self.statusBar().showMessage(
                    f"Changing standard deviation to {_value}", 4000)
                self.refreshRenderWindow()
        else:
            return
        return

    def changeRadius(self, _value, pipeline):
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                self.in1_pipe.setGaussRadius(_value)
                self.statusBar().showMessage(f"Changing radius to {_value}",
                                             4000)
                self.refreshRenderWindow()
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                self.in2_pipe.setGaussRadius(_value)
                self.statusBar().showMessage(f"Changing radius to {_value}",
                                             4000)
                self.refreshRenderWindow()
        else:
            return
        return

    def changeIsosurface(self, _value, pipeline):
        if (pipeline == "in1"):
            if (self.in1_pipe != None):
                self.in1_pipe.setIsosurface(_value)
                self.statusBar().showMessage(
                    f"Changing isosurface to {_value}", 4000)
                self.refreshRenderWindow()
        elif (pipeline == "in2"):
            if (self.in2_pipe != None):
                self.in2_pipe.setIsosurface(_value)
                self.statusBar().showMessage(
                    f"Changing isosurface to {_value}", 4000)
                self.refreshRenderWindow()
        else:
            return
        return

    def updateCamera(self, type):
        camera = self.renderer.GetActiveCamera()
        inc = self.incrementCameraSpinBox.value()

        if (type == "roll"):
            camera.Roll(inc)
        if (type == "elevation"):
            camera.Elevation(inc)
        if (type == "azimuth"):
            camera.Azimuth(inc)

        camera.OrthogonalizeViewUp()
        self.refreshRenderWindow()
        return

    def applyLandmarkTransform(self):
        if (self.in2_pipe != None):
            in1_pts = self.pickerstyle.getNumberOfPoints("in1_pipeline")
            in2_pts = self.pickerstyle.getNumberOfPoints("in2_pipeline")
            if (in1_pts == in2_pts and in1_pts >= 3):
                lm = vtk.vtkLandmarkTransform()
                lm.SetTargetLandmarks(
                    self.pickerstyle.getPoints("in1_pipeline"))
                lm.SetSourceLandmarks(
                    self.pickerstyle.getPoints("in2_pipeline"))
                lm.SetModeToRigidBody()
                lm.Update()
                mat = lm.GetMatrix()
                self.in2_pipe.setRigidBodyTransformConcatenateMatrix(mat)
                self.pickerstyle.removePoints("in1_pipeline")
                self.pickerstyle.removePoints("in2_pipeline")
                self.refreshRenderWindow()
                self.updateGUI()
                self.statusBar().showMessage(
                    f"Landmark transform complete based on {in1_pts}", 4000)
            else:
                self.statusBar().showMessage(
                    "ERROR: Landmark transform could not be executed", 4000)

    def applyICPTransform(self):
        if (self.in2_pipe != None):
            icp = vtk.vtkIterativeClosestPointTransform()
            icp.SetTarget(self.in1_pipe.getPolyData())
            icp.SetSource(self.in2_pipe.getPolyData())
            icp.SetMaximumNumberOfIterations(10)
            icp.StartByMatchingCentroidsOn()
            icp.GetInverse()
            icp.Update()
            # Concatenate the transform
            mat = icp.GetMatrix()
            self.in2_pipe.setRigidBodyTransformConcatenateMatrix(mat)
            # Clean up
            self.pickerstyle.removePoints("in1_pipeline")
            self.pickerstyle.removePoints("in2_pipeline")
            self.refreshRenderWindow()
            self.updateGUI()
            self.statusBar().clearMessage()
            self.statusBar().showMessage("ICP transform complete", 4000)

    def resetTransform(self):
        if (self.in2_pipe != None):
            reply = qtw.QMessageBox.question(
                self, "Message",
                "Are you sure you want to reset the transform?",
                qtw.QMessageBox.Yes | qtw.QMessageBox.No, qtw.QMessageBox.Yes)
            if reply == qtw.QMessageBox.Yes:
                self.in2_pipe.setRigidBodyTransformToIdentity()
                self.pickerstyle.removePoints("in1_pipeline")
                self.pickerstyle.removePoints("in2_pipeline")
                self.refreshRenderWindow()
                self.updateGUI()
                self.statusBar().showMessage("Reset transform complete", 4000)

    def toggleTransformApplied(self, _state):
        if (self.in2_pipe != None):
            if (qtc.Qt.Checked == _state):
                self.in2_pipe.useTransform(True)
                self.statusBar().showMessage(
                    "Toggling transform visibility ON", 4000)
            else:
                self.in2_pipe.useTransform(False)
                self.statusBar().showMessage(
                    "Toggling transform visibility OFF", 4000)
            self.refreshRenderWindow()

    def updateMatrixGUI(self, _mat):
        precision = 3
        formatter = "{{:6.{}f}}".format(precision)
        self.mat11.setText(formatter.format(float(_mat.GetElement(0, 0))))
        self.mat12.setText(formatter.format(float(_mat.GetElement(0, 1))))
        self.mat13.setText(formatter.format(float(_mat.GetElement(0, 2))))
        self.mat14.setText(formatter.format(float(_mat.GetElement(0, 3))))
        self.mat21.setText(formatter.format(float(_mat.GetElement(1, 0))))
        self.mat22.setText(formatter.format(float(_mat.GetElement(1, 1))))
        self.mat23.setText(formatter.format(float(_mat.GetElement(1, 2))))
        self.mat24.setText(formatter.format(float(_mat.GetElement(1, 3))))
        self.mat31.setText(formatter.format(float(_mat.GetElement(2, 0))))
        self.mat32.setText(formatter.format(float(_mat.GetElement(2, 1))))
        self.mat33.setText(formatter.format(float(_mat.GetElement(2, 2))))
        self.mat34.setText(formatter.format(float(_mat.GetElement(2, 3))))
        self.mat41.setText(formatter.format(float(_mat.GetElement(3, 0))))
        self.mat42.setText(formatter.format(float(_mat.GetElement(3, 1))))
        self.mat43.setText(formatter.format(float(_mat.GetElement(3, 2))))
        self.mat44.setText(formatter.format(float(_mat.GetElement(3, 3))))
        return

    def updateVectorsGUI(self):
        if (self.in2_pipe == None):
            return
        converter = ScancoMatrixConverter()
        mat = self.in2_pipe.getMatrix()
        converter.setDimImage1(self.in1_pipe.getDimensions())
        converter.setDimImage2(self.in2_pipe.getDimensions())
        converter.setPosImage1(self.in1_pipe.getPosition())
        converter.setPosImage2(self.in2_pipe.getPosition())
        converter.setElSizeMMImage1(self.in1_pipe.getElementSize())
        converter.setElSizeMMImage2(self.in2_pipe.getElementSize())
        converter.setTransform(mat)
        converter.calculateVectors()

        rot = converter.getRotationVector()
        trans = converter.getTranslationVector()

        precision = 3
        formatter = "{{:6.{}f}}".format(precision)
        self.rot1.setText(formatter.format(float(rot[0])))
        self.rot2.setText(formatter.format(float(rot[1])))
        self.rot3.setText(formatter.format(float(rot[2])))
        self.trans1.setText(formatter.format(float(trans[0])))
        self.trans2.setText(formatter.format(float(trans[1])))
        self.trans3.setText(formatter.format(float(trans[2])))
        return

    def updateGUI(self):
        in1_pts = self.pickerstyle.getNumberOfPoints("in1_pipeline")
        in2_pts = self.pickerstyle.getNumberOfPoints("in2_pipeline")
        #print("There are " + str(in1_pts) + " in1 points.")
        #print("There are " + str(in2_pts) + " in2 points.")
        self.in1_points_count.display(in1_pts)
        self.in2_points_count.display(in2_pts)
        if (in1_pts == in2_pts and in1_pts >= 3):
            self.landmarkTransformPushButton.setEnabled(True)
        else:
            self.landmarkTransformPushButton.setEnabled(False)
        if (self.in1_pipe != None):
            self.in2_mainGroupBox.setEnabled(
                True)  # activate GUI for in2_pipeline
            self.in1_sigmaSpinBox.setValue(
                self.in1_pipe.getGaussStandardDeviation())
            self.in1_radiusSpinBox.setValue(self.in1_pipe.getGaussRadius())
            self.in1_isosurfaceSpinBox.setValue(self.in1_pipe.getIsosurface())
        if (self.in2_pipe != None):
            self.updateMatrixGUI(self.in2_pipe.getMatrix())
            self.updateVectorsGUI()
            self.in2_sigmaSpinBox.setValue(
                self.in2_pipe.getGaussStandardDeviation())
            self.in2_radiusSpinBox.setValue(self.in2_pipe.getGaussRadius())
            self.in2_isosurfaceSpinBox.setValue(self.in2_pipe.getIsosurface())
        return

    def keyEventDetected(self, obj, event):
        self.updateGUI()
        key = self.vtkWidget.GetKeySym()
        if (key in 'p') or (key in 'd'):  # pick or delete points
            self.log_window.append(self.pickerstyle.getPointActionString())
        # print("keypress – clicked "+key)
        return

    def mouseEventDetected(self, obj, event):
        print("mouserelease – click!")
        return

    def validExtension(self, extension):
        if (extension == ".aim" or \
            extension == ".nii" or \
            extension == ".dcm" or \
            extension == ".stl"):
            return True
        else:
            return False

    def openFile(self, pipeline_name):
        if (pipeline_name == "in2" and self.in1_pipe == None):
            qtw.QMessageBox.warning(
                self, "Warning", "Image 2 cannot be loaded before image 1.")
            return
        self.statusBar().showMessage("Load image types (.aim, .nii, .dcm)",
                                     4000)
        filename, _ = qtw.QFileDialog.getOpenFileName(
            self, "Select a 3D image file to open…", qtc.QDir.homePath(),
            "Aim Files (*.aim) ;;Nifti Files (*.nii) ;;DICOM Files (*.dcm) ;;STL Files (*.stl) ;;All Files (*)",
            "All Files (*)", qtw.QFileDialog.DontUseNativeDialog
            | qtw.QFileDialog.DontResolveSymlinks)

        if filename:
            _, ext = os.path.splitext(filename)
            if not (self.validExtension(ext.lower())):
                qtw.QMessageBox.warning(self, "Error", "Invalid file type.")
                return

            self.createPipeline(filename, pipeline_name)
            self.statusBar().showMessage("Loading file " + filename, 4000)
        return

    def saveLogFile(self):
        filename, _ = qtw.QFileDialog.getSaveFileName(
            self, "Select the file to save to…", qtc.QDir.homePath(),
            "Text Files (*.txt) ")
        if filename:
            try:
                with open(filename, 'w') as fh:
                    fh.write(self.log_window.toPlainText())
            except Exception as e:
                qtw.QMessageBox.critical(self, f"Could not save file: {e}")

    def savePointsFile(self):
        filename, _ = qtw.QFileDialog.getSaveFileName(
            self, "Select the file to save to…", qtc.QDir.homePath(),
            "Text Files (*.txt) ;;Python Files (*.py) ;;All Files (*)")
        if filename:
            try:
                with open(filename, 'w') as fh:
                    fh.write(self.pickerstyle.getAllPointsAsString())
            except Exception as e:
                qtw.QMessageBox.critical(self, f"Could not save file: {e}")

    def saveTransformFile(self, type):

        converter = ScancoMatrixConverter()

        if (self.in2_pipe == None):
            mat = vtk.vtkMatrix4x4()
            converter.setDimImage1(self.in1_pipe.getDimensions())
            converter.setPosImage1(self.in1_pipe.getPosition())
            converter.setElSizeMMImage1(self.in1_pipe.getElementSize())
            converter.setTransform(mat)
            converter.calculateVectors()
        else:
            mat = self.in2_pipe.getMatrix()
            converter.setDimImage1(self.in1_pipe.getDimensions())
            converter.setDimImage2(self.in2_pipe.getDimensions())
            converter.setPosImage1(self.in1_pipe.getPosition())
            converter.setPosImage2(self.in2_pipe.getPosition())
            converter.setElSizeMMImage1(self.in1_pipe.getElementSize())
            converter.setElSizeMMImage2(self.in2_pipe.getElementSize())
            converter.setTransform(mat)
            converter.calculateVectors()

        filename, _ = qtw.QFileDialog.getSaveFileName(
            self, "Select the file to save to…", qtc.QDir.homePath(),
            "Text Files (*.txt) ;;Python Files (*.py) ;;All Files (*)")
        if filename:
            try:
                with open(filename, 'w') as fh:
                    s = ""
                    if (type == "matrix"):
                        s += converter.getTransformAsString()
                    if (type == "vector"):
                        s += converter.getVectorsAsString()
                    fh.write(s)
            except Exception as e:
                qtw.QMessageBox.critical(self, f"Could not save file: {e}")

    def quit(self):
        reply = qtw.QMessageBox.question(
            self, "Message", "Are you sure you want to quit?",
            qtw.QMessageBox.Yes | qtw.QMessageBox.No, qtw.QMessageBox.Yes)
        if reply == qtw.QMessageBox.Yes:
            exit(0)

    def about(self):
        about = qtw.QMessageBox(self)
        about.setWindowIcon(qtg.QIcon('/bonelab/gui/src/icon.png'))
        about.setIcon(qtw.QMessageBox.Information)
        about.setText("blQtViewer 1.0")
        about.setInformativeText(
            "Copyright (C) 2020\nBone Imaging Laboratory\nAll rights reserved.\[email protected]"
        )
        about.setStandardButtons(qtw.QMessageBox.Ok | qtw.QMessageBox.Cancel)
        about.exec_()

    def extrudeFromPoints(self):

        pts = self.pickerstyle.getPoints("in1_pipeline")

        if (pts.GetNumberOfPoints() < 3):
            qtw.QMessageBox.warning(
                self, "Warning",
                "At least 3 points must be defined on image 1 to create extrusion."
            )
            return

        if (not self.in1_pipe.getIsValidForExtrusion()):
            qtw.QMessageBox.warning(
                self, "Warning",
                "Extrusion may not work properly when input file is not of type AIM."
            )

        # Spline
        spline = vtk.vtkParametricSpline()
        spline.SetPoints(pts)
        spline.ClosedOn()

        parametricFunction = vtk.vtkParametricFunctionSource()
        parametricFunction.SetParametricFunction(spline)
        parametricFunction.Update()

        # Extrude
        extrusionFactor = 100.0  # mm above and below surface
        # A large number will cause the extrusion to fill the extent of the input image

        positiveExtruder = vtk.vtkLinearExtrusionFilter()
        positiveExtruder.SetInputConnection(parametricFunction.GetOutputPort())
        positiveExtruder.SetExtrusionTypeToNormalExtrusion()
        positiveExtruder.SetVector(0, 0, 1)
        positiveExtruder.CappingOn()
        positiveExtruder.SetScaleFactor(extrusionFactor)

        posTriFilter = vtk.vtkTriangleFilter()
        posTriFilter.SetInputConnection(positiveExtruder.GetOutputPort())

        negativeExtruder = vtk.vtkLinearExtrusionFilter()
        negativeExtruder.SetInputConnection(parametricFunction.GetOutputPort())
        negativeExtruder.SetExtrusionTypeToNormalExtrusion()
        negativeExtruder.SetVector(0, 0, -1)
        negativeExtruder.CappingOn()
        negativeExtruder.SetScaleFactor(extrusionFactor)

        negTriFilter = vtk.vtkTriangleFilter()
        negTriFilter.SetInputConnection(negativeExtruder.GetOutputPort())

        # Combine data
        combiner = vtk.vtkAppendPolyData()
        combiner.AddInputConnection(posTriFilter.GetOutputPort())
        combiner.AddInputConnection(negTriFilter.GetOutputPort())

        cleaner = vtk.vtkCleanPolyData()
        cleaner.SetInputConnection(combiner.GetOutputPort())
        cleaner.Update()

        el_size_mm = self.in1_pipe.getElementSize()
        dim = self.in1_pipe.getDimensions()
        extent = self.in1_pipe.getExtent()
        origin = self.in1_pipe.getOrigin()
        foregroundValue = 127
        backgroundValue = 0

        # Stencil
        whiteImage = vtk.vtkImageData()
        whiteImage.SetSpacing(el_size_mm)
        whiteImage.SetDimensions(dim)
        whiteImage.SetExtent(extent)
        whiteImage.SetOrigin(origin)
        whiteImage.AllocateScalars(vtk.VTK_CHAR, 1)
        whiteImage.GetPointData().GetScalars().Fill(foregroundValue)

        # Use our extruded polydata to stencil the solid image
        poly2sten = vtk.vtkPolyDataToImageStencil()
        poly2sten.SetTolerance(0)
        #poly2sten.SetInputConnection(clipper.GetOutputPort())
        poly2sten.SetInputConnection(cleaner.GetOutputPort())
        poly2sten.SetOutputOrigin(origin)
        poly2sten.SetOutputSpacing(el_size_mm)
        poly2sten.SetOutputWholeExtent(whiteImage.GetExtent())

        stencil = vtk.vtkImageStencil()
        stencil.SetInputData(whiteImage)
        stencil.SetStencilConnection(poly2sten.GetOutputPort())
        #stencil.ReverseStencilOff()
        stencil.SetBackgroundValue(backgroundValue)
        stencil.Update()

        # Write image
        filename, _ = qtw.QFileDialog.getSaveFileName(
            self, "Select the file to save to…", qtc.QDir.homePath(),
            "AIM File (*.aim)")

        if (filename):
            writer = vtkbone.vtkboneAIMWriter()
            writer.SetInputConnection(stencil.GetOutputPort())
            writer.SetFileName(filename)
            writer.SetProcessingLog(
                '!-------------------------------------------------------------------------------\n'
                + 'Written by blQtViewer.')
            writer.Update()
            self.statusBar().showMessage("File " + filename + " written.",
                                         4000)