From ab5d9b18907972df1b7d7a77074a3443aed55b6e Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Sat, 10 Apr 2021 00:18:25 -0400 Subject: [PATCH] COMP: Fix build errors with Slicer-4.13 --- AutoPortPlacement/CMakeLists.txt | 13 +- AutoPortPlacement/Logic/CMakeLists.txt | 10 - .../Logic/collisions/CMakeLists.txt | 2 +- .../Logic/collisions/collisions.h | 2 +- .../Logic/davinci-kinematics/davinci.cxx | 2 +- .../Logic/davinci-kinematics/davinci.h.in | 4 +- AutoPortPlacement/Logic/optim/optim.h | 4 +- .../Logic/vtkSlicerAutoPortPlacementLogic.cxx | 3 +- AutoPortPlacement/Testing/Cxx/CMakeLists.txt | 1 + .../qSlicerAutoPortPlacementModule.cxx | 27 +- .../qSlicerAutoPortPlacementModule.h | 4 +- .../qSlicerAutoPortPlacementModuleWidget.cxx | 2 +- .../qSlicerAutoPortPlacementModuleWidget.h | 2 +- CMakeLists.txt | 19 +- PortPlacement/CMakeLists.txt | 5 +- PortPlacement/PortPlacement.py | 1497 ++++++++--------- ...leModuleTemplate.png => PortPlacement.png} | Bin SuperBuild.cmake | 36 +- SuperBuild/External_NLopt.cmake | 61 +- 19 files changed, 838 insertions(+), 856 deletions(-) rename PortPlacement/Resources/Icons/{ScriptedLoadableModuleTemplate.png => PortPlacement.png} (100%) diff --git a/AutoPortPlacement/CMakeLists.txt b/AutoPortPlacement/CMakeLists.txt index 4cd4739..7773493 100644 --- a/AutoPortPlacement/CMakeLists.txt +++ b/AutoPortPlacement/CMakeLists.txt @@ -1,11 +1,3 @@ -cmake_minimum_required(VERSION 3.5) - -if(NOT DEFINED CMAKE_MACOSX_RPATH) - set(CMAKE_MACOSX_RPATH OFF) -endif() - -#----------------------------------------------------------------------------- -project(AutoPortPlacementModuleProject) #----------------------------------------------------------------------------- set(MODULE_NAME AutoPortPlacement) @@ -14,9 +6,6 @@ set(MODULE_TITLE ${MODULE_NAME}) string(TOUPPER ${MODULE_NAME} MODULE_NAME_UPPER) #----------------------------------------------------------------------------- -find_package(Slicer REQUIRED) -include(${Slicer_USE_FILE}) - add_subdirectory(Logic) #----------------------------------------------------------------------------- @@ -53,7 +42,7 @@ set(MODULE_RESOURCES ) #----------------------------------------------------------------------------- -slicerMacroBuildQtModule( +slicerMacroBuildLoadableModule( NAME ${MODULE_NAME} TITLE ${MODULE_TITLE} EXPORT_DIRECTIVE ${MODULE_EXPORT_DIRECTIVE} diff --git a/AutoPortPlacement/Logic/CMakeLists.txt b/AutoPortPlacement/Logic/CMakeLists.txt index 0d9156d..0a68590 100644 --- a/AutoPortPlacement/Logic/CMakeLists.txt +++ b/AutoPortPlacement/Logic/CMakeLists.txt @@ -1,14 +1,5 @@ -cmake_minimum_required(VERSION 3.5) - -if(NOT DEFINED CMAKE_MACOSX_RPATH) - set(CMAKE_MACOSX_RPATH OFF) -endif() - -#----------------------------------------------------------------------------- project(vtkSlicer${MODULE_NAME}ModuleLogic) -find_package(Eigen3 REQUIRED CONFIG) - set(KIT ${PROJECT_NAME}) set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_LOGIC_EXPORT") @@ -25,7 +16,6 @@ set(${KIT}_TARGET_LIBRARIES collisions davinci ${ITK_LIBRARIES} - Eigen3::Eigen optim vtkSlicerAnnotationsModuleMRML vtkSlicerMarkupsModuleMRML diff --git a/AutoPortPlacement/Logic/collisions/CMakeLists.txt b/AutoPortPlacement/Logic/collisions/CMakeLists.txt index 6ba8a79..94d6269 100644 --- a/AutoPortPlacement/Logic/collisions/CMakeLists.txt +++ b/AutoPortPlacement/Logic/collisions/CMakeLists.txt @@ -2,7 +2,7 @@ add_library(collisions STATIC collisions.cxx collisions.h ) -target_link_libraries(collisions PUBLIC Eigen3::Eigen) +target_link_libraries(collisions) set_property(TARGET collisions PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/AutoPortPlacement/Logic/collisions/collisions.h b/AutoPortPlacement/Logic/collisions/collisions.h index ff2395f..dd9b10e 100644 --- a/AutoPortPlacement/Logic/collisions/collisions.h +++ b/AutoPortPlacement/Logic/collisions/collisions.h @@ -1,7 +1,7 @@ #ifndef COLLISIONS_H #define COLLISIONS_H -#include +#include #include namespace Collisions diff --git a/AutoPortPlacement/Logic/davinci-kinematics/davinci.cxx b/AutoPortPlacement/Logic/davinci-kinematics/davinci.cxx index c3cdb69..8e8d0c4 100644 --- a/AutoPortPlacement/Logic/davinci-kinematics/davinci.cxx +++ b/AutoPortPlacement/Logic/davinci-kinematics/davinci.cxx @@ -129,7 +129,7 @@ DavinciKinematics::DavinciKinematics(const std::string& inputFilename) } - itk::DOMNode::Pointer dom = reader->GetModifiableOutput(); + itk::DOMNode::Pointer dom = reader->GetOutput(); itk::DOMNode::Pointer davinciParametersIntracorporeal = dom->Find("intracorporeal"); diff --git a/AutoPortPlacement/Logic/davinci-kinematics/davinci.h.in b/AutoPortPlacement/Logic/davinci-kinematics/davinci.h.in index 374476e..0f35726 100644 --- a/AutoPortPlacement/Logic/davinci-kinematics/davinci.h.in +++ b/AutoPortPlacement/Logic/davinci-kinematics/davinci.h.in @@ -1,8 +1,8 @@ #ifndef DAVINCI_KINEMATICS_H #define DAVINCI_KINEMATICS_H -#include -#include +#include +#include #include diff --git a/AutoPortPlacement/Logic/optim/optim.h b/AutoPortPlacement/Logic/optim/optim.h index 874cc8c..77f6b66 100644 --- a/AutoPortPlacement/Logic/optim/optim.h +++ b/AutoPortPlacement/Logic/optim/optim.h @@ -2,8 +2,8 @@ #define PORT_PLACEMENT_AUTO_PORT_PLACEMENT_LOGIC_OPTIM_OPTIM_H #include -#include -#include +#include +#include namespace Optim { diff --git a/AutoPortPlacement/Logic/vtkSlicerAutoPortPlacementLogic.cxx b/AutoPortPlacement/Logic/vtkSlicerAutoPortPlacementLogic.cxx index 799c9e8..8aeb373 100644 --- a/AutoPortPlacement/Logic/vtkSlicerAutoPortPlacementLogic.cxx +++ b/AutoPortPlacement/Logic/vtkSlicerAutoPortPlacementLogic.cxx @@ -46,6 +46,7 @@ vtkStandardNewMacro(vtkSlicerAutoPortPlacementLogic); namespace { + // Given a cylisphere returned by the DavinciKinematics object, // returns a vtkMRMLLinearTransformNode that will transform the // vtkCylinderSource's output to correspond to the input cylisphere. @@ -340,7 +341,7 @@ FindFeasiblePlan(vtkMRMLNode* taskFramesNode, // Get orientation part double quat[4]; - taskFramesFiducial->GetNthMarkupOrientation(tIdx, quat); + taskFramesFiducial->GetNthControlPointOrientation(tIdx, quat); double rotMat[3][3]; vtkMath::QuaternionToMatrix3x3(quat, rotMat); for (unsigned i = 0; i < 3; ++i) diff --git a/AutoPortPlacement/Testing/Cxx/CMakeLists.txt b/AutoPortPlacement/Testing/Cxx/CMakeLists.txt index b61fa1e..b7341cd 100644 --- a/AutoPortPlacement/Testing/Cxx/CMakeLists.txt +++ b/AutoPortPlacement/Testing/Cxx/CMakeLists.txt @@ -10,6 +10,7 @@ slicerMacroConfigureModuleCxxTestDriver( NAME ${KIT} SOURCES ${KIT_TEST_SRCS} WITH_VTK_DEBUG_LEAKS_CHECK + WITH_VTK_ERROR_OUTPUT_CHECK ) #----------------------------------------------------------------------------- diff --git a/AutoPortPlacement/qSlicerAutoPortPlacementModule.cxx b/AutoPortPlacement/qSlicerAutoPortPlacementModule.cxx index 1adb2a3..a7610fe 100644 --- a/AutoPortPlacement/qSlicerAutoPortPlacementModule.cxx +++ b/AutoPortPlacement/qSlicerAutoPortPlacementModule.cxx @@ -15,9 +15,6 @@ ==============================================================================*/ -// Qt includes -#include - // AutoPortPlacement Logic includes #include @@ -25,12 +22,6 @@ #include "qSlicerAutoPortPlacementModule.h" #include "qSlicerAutoPortPlacementModuleWidget.h" -//----------------------------------------------------------------------------- -#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) -#include -Q_EXPORT_PLUGIN2(qSlicerAutoPortPlacementModule, qSlicerAutoPortPlacementModule); -#endif - //----------------------------------------------------------------------------- /// \ingroup Slicer_QtModules_ExtensionTemplate class qSlicerAutoPortPlacementModulePrivate @@ -43,8 +34,7 @@ class qSlicerAutoPortPlacementModulePrivate // qSlicerAutoPortPlacementModulePrivate methods //----------------------------------------------------------------------------- -qSlicerAutoPortPlacementModulePrivate -::qSlicerAutoPortPlacementModulePrivate() +qSlicerAutoPortPlacementModulePrivate::qSlicerAutoPortPlacementModulePrivate() { } @@ -52,8 +42,7 @@ ::qSlicerAutoPortPlacementModulePrivate() // qSlicerAutoPortPlacementModule methods //----------------------------------------------------------------------------- -qSlicerAutoPortPlacementModule -::qSlicerAutoPortPlacementModule(QObject* _parent) +qSlicerAutoPortPlacementModule::qSlicerAutoPortPlacementModule(QObject* _parent) : Superclass(_parent) , d_ptr(new qSlicerAutoPortPlacementModulePrivate) { @@ -65,19 +54,19 @@ qSlicerAutoPortPlacementModule::~qSlicerAutoPortPlacementModule() } //----------------------------------------------------------------------------- -QString qSlicerAutoPortPlacementModule::helpText()const +QString qSlicerAutoPortPlacementModule::helpText() const { - return "This is a loadable module bundled in an extension"; + return "This is a loadable module that can be bundled in an extension"; } //----------------------------------------------------------------------------- -QString qSlicerAutoPortPlacementModule::acknowledgementText()const +QString qSlicerAutoPortPlacementModule::acknowledgementText() const { return "This work was was partially funded by NIH grant 3P41RR013218-12S1"; } //----------------------------------------------------------------------------- -QStringList qSlicerAutoPortPlacementModule::contributors()const +QStringList qSlicerAutoPortPlacementModule::contributors() const { QStringList moduleContributors; moduleContributors @@ -88,7 +77,7 @@ QStringList qSlicerAutoPortPlacementModule::contributors()const } //----------------------------------------------------------------------------- -QIcon qSlicerAutoPortPlacementModule::icon()const +QIcon qSlicerAutoPortPlacementModule::icon() const { return QIcon(":/Icons/AutoPortPlacement.png"); } @@ -112,7 +101,7 @@ void qSlicerAutoPortPlacementModule::setup() } //----------------------------------------------------------------------------- -qSlicerAbstractModuleRepresentation * qSlicerAutoPortPlacementModule +qSlicerAbstractModuleRepresentation* qSlicerAutoPortPlacementModule ::createWidgetRepresentation() { return new qSlicerAutoPortPlacementModuleWidget; diff --git a/AutoPortPlacement/qSlicerAutoPortPlacementModule.h b/AutoPortPlacement/qSlicerAutoPortPlacementModule.h index 399daa1..9773af0 100644 --- a/AutoPortPlacement/qSlicerAutoPortPlacementModule.h +++ b/AutoPortPlacement/qSlicerAutoPortPlacementModule.h @@ -18,7 +18,7 @@ #ifndef __qSlicerAutoPortPlacementModule_h #define __qSlicerAutoPortPlacementModule_h -// SlicerQt includes +// Slicer includes #include "qSlicerLoadableModule.h" #include "qSlicerAutoPortPlacementModuleExport.h" @@ -31,9 +31,7 @@ qSlicerAutoPortPlacementModule : public qSlicerLoadableModule { Q_OBJECT -#ifdef Slicer_HAVE_QT5 Q_PLUGIN_METADATA(IID "org.slicer.modules.loadable.qSlicerLoadableModule/1.0"); -#endif Q_INTERFACES(qSlicerLoadableModule); public: diff --git a/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.cxx b/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.cxx index 05f8aa3..70243bb 100644 --- a/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.cxx +++ b/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.cxx @@ -18,7 +18,7 @@ // Qt includes #include -// SlicerQt includes +// Slicer includes #include "qSlicerAutoPortPlacementModuleWidget.h" #include "ui_qSlicerAutoPortPlacementModuleWidget.h" diff --git a/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.h b/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.h index da042c8..0076ccb 100644 --- a/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.h +++ b/AutoPortPlacement/qSlicerAutoPortPlacementModuleWidget.h @@ -18,7 +18,7 @@ #ifndef __qSlicerAutoPortPlacementModuleWidget_h #define __qSlicerAutoPortPlacementModuleWidget_h -// SlicerQt includes +// Slicer includes #include "qSlicerAbstractModuleWidget.h" #include "qSlicerAutoPortPlacementModuleExport.h" diff --git a/CMakeLists.txt b/CMakeLists.txt index bd952e9..6bb4061 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,16 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.13.4) project(PortPlacement) #----------------------------------------------------------------------------- +# Extension meta-information set(EXTENSION_HOMEPAGE "http://www.slicer.org/slicerWiki/index.php/Documentation/Nightly/Extensions/PortPlacement") set(EXTENSION_CATEGORY "IGT") set(EXTENSION_CONTRIBUTORS "Andinet Enquobahrie (Kitware), Luis G. Torres (UNC), Jean-Christophe Fillion-Robin (Kitware)") set(EXTENSION_DESCRIPTION "Assists in the planning of surgical port placement in a laparoscopic procedure.") set(EXTENSION_ICONURL "http://www.slicer.org/slicerWiki/images/a/a7/Portplacement_icon.png") set(EXTENSION_SCREENSHOTURLS "http://www.slicer.org/slicerWiki/images/3/32/Portplacement.png") -set(EXTENSION_DEPENDS "Eigen3") +set(EXTENSION_DEPENDS "NA") set(EXTENSION_BUILD_SUBDIRECTORY inner-build) set(EXTENSION_STATUS "This extension is intended for public use and we expect to fully maintain it.") @@ -29,9 +30,6 @@ mark_as_superbuild(Slicer_DIR) find_package(Git REQUIRED) mark_as_superbuild(GIT_EXECUTABLE) -find_package(Eigen3 REQUIRED CONFIG) -mark_as_superbuild(Eigen3_DIR) - #----------------------------------------------------------------------------- # SuperBuild setup option(${EXTENSION_NAME}_SUPERBUILD "Build ${EXTENSION_NAME} and the projects it depends on." ON) @@ -42,9 +40,18 @@ if(${EXTENSION_NAME}_SUPERBUILD) endif() #----------------------------------------------------------------------------- -add_subdirectory(PortPlacement) +# Extension modules add_subdirectory(AutoPortPlacement) +add_subdirectory(PortPlacement) +## NEXT_MODULE + +#----------------------------------------------------------------------------- +set(EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS) +list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${NLopt_DIR};NLopt;RuntimeLibraries;/") +set(${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS "${EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS}" CACHE STRING "List of external projects to install" FORCE) #----------------------------------------------------------------------------- +list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR};${EXTENSION_NAME};ALL;/") +list(APPEND CPACK_INSTALL_CMAKE_PROJECTS "${${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS}") include(${Slicer_EXTENSION_GENERATE_CONFIG}) include(${Slicer_EXTENSION_CPACK}) diff --git a/PortPlacement/CMakeLists.txt b/PortPlacement/CMakeLists.txt index 7302047..b52c401 100644 --- a/PortPlacement/CMakeLists.txt +++ b/PortPlacement/CMakeLists.txt @@ -7,6 +7,7 @@ set(MODULE_PYTHON_SCRIPTS ) set(MODULE_PYTHON_RESOURCES + Resources/Icons/${MODULE_NAME}.png ) #----------------------------------------------------------------------------- @@ -22,10 +23,8 @@ if(BUILD_TESTING) # Register the unittest subclass in the main script as a ctest. # Note that the test will also be available at runtime. - slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py - SLICER_ARGS "--additional-module-path" "${CMAKE_CURRENT_LIST_DIR}") + slicer_add_python_unittest(SCRIPT ${MODULE_NAME}.py) # Additional build-time testing add_subdirectory(Testing) endif() - diff --git a/PortPlacement/PortPlacement.py b/PortPlacement/PortPlacement.py index c6a5b82..0988b89 100644 --- a/PortPlacement/PortPlacement.py +++ b/PortPlacement/PortPlacement.py @@ -1,751 +1,746 @@ -import os -import unittest -from __main__ import vtk, qt, ctk, slicer -import string -# -# PortPlacement -# - -class PortPlacement: - def __init__(self, parent): - parent.title = "Port Placement" - parent.categories = ["IGT"] - parent.dependencies = [] - parent.contributors = ["Andinet Enquobahrie (Kitware), Luis G. Torres (UNC)"] - parent.helpText = string.Template(""" - The PortPlacement module assists in the planning of surgical port placement in a laparoscopic procedure. Users can specify ports using fiducial markers and the module will automatically visualize simulated surgical tools at the port locations. Users can freely pivot the simulated surgical tools about the ports, as well as automatically orient all surgical tools toward a specified surgical target. Simulated tools are represented by cylinders whose lengths and radii can be varied from tool to tool. - - See the module documentation for more details. - """).substitute({ 'a':parent.slicerWikiUrl}) - parent.acknowledgementText = """ - This work was supported by NSF GRFP Grant No. DGE-1144081 and partially supported by NIH/NIBIB Grant No. 1R43EB014074-01. Anatomical atlas volume in module and extension icons courtesy of the Surgical Planning Laboratory (Link) -""" - self.parent = parent - - # Add this test to the SelfTest module's list for discovery when the module - # is created. Since this module may be discovered before SelfTests itself, - # create the list if it doesn't already exist. - try: - slicer.selfTests - except AttributeError: - slicer.selfTests = {} - slicer.selfTests['PortPlacement'] = self.runTest - - def runTest(self): - tester = PortPlacement() - tester.runTest() - -# -# PortPlacementWidget -# - -class PortPlacementWidget: - def __init__(self, parent = None): - if not parent: - self.parent = slicer.qMRMLWidget() - self.parent.setLayout(qt.QVBoxLayout()) - self.parent.setMRMLScene(slicer.mrmlScene) - else: - self.parent = parent - self.layout = self.parent.layout() - if not parent: - self.setup() - self.parent.show() - - def setup(self): - # Instantiate and connect widgets ... - - # # - # # Reload and Test area - # # - # reloadCollapsibleButton = ctk.ctkCollapsibleButton() - # reloadCollapsibleButton.text = "Reload && Test" - # self.layout.addWidget(reloadCollapsibleButton) - # reloadFormLayout = qt.QFormLayout(reloadCollapsibleButton) - - # # reload button - # # (use this during development, but remove it when delivering - # # your module to users) - # self.reloadButton = qt.QPushButton("Reload") - # self.reloadButton.toolTip = "Reload this module." - # self.reloadButton.name = "PortPlacement Reload" - # reloadFormLayout.addWidget(self.reloadButton) - # self.reloadButton.connect('clicked()', self.onReload) - - # # reload and test button - # # (use this during development, but remove it when delivering - # # your module to users) - # self.reloadAndTestButton = qt.QPushButton("Reload and Test") - # self.reloadAndTestButton.toolTip = "Reload this module and then run the self tests." - # reloadFormLayout.addWidget(self.reloadAndTestButton) - # self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest) - - # - # Ports Area - # - portsCollapsibleButton = ctk.ctkCollapsibleButton() - portsCollapsibleButton.text = "Surgical Ports" - self.layout.addWidget(portsCollapsibleButton) - portsFormLayout = qt.QFormLayout(portsCollapsibleButton) - - # - # port fiducial list selector - # - self.portListSelector = slicer.qMRMLNodeComboBox() - self.portListSelector.nodeTypes = ["vtkMRMLMarkupsFiducialNode"] - self.portListSelector.addEnabled = False - self.portListSelector.removeEnabled = False - self.portListSelector.noneEnabled = True - self.portListSelector.setMRMLScene(slicer.mrmlScene) - self.portListSelector.setToolTip("Add surgical ports from a markups node.") - portsFormLayout.addRow("Markups node of surgical ports", self.portListSelector) - - # - # Add Port List button - # - self.addPortListButton = qt.QPushButton("Set Port Markups Node") - self.addPortListButton.enabled = False - portsFormLayout.addRow(self.addPortListButton) - - # - # Port table - # - self.portsTable = qt.QTableView() - self.portsTableModel = qt.QStandardItemModel() - self.portsTable.setModel(self.portsTableModel) - portsFormLayout.addRow(self.portsTable) - - # - # Remove Port button - # - self.removePortButton = qt.QPushButton("Remove Selected Port") - self.removePortButton.enabled = False - portsFormLayout.addRow(self.removePortButton) - - # - # Target area - # - targetCollapsibleButton = ctk.ctkCollapsibleButton() - targetCollapsibleButton.text = "Surgical Target" - self.layout.addWidget(targetCollapsibleButton) - targetFormLayout = qt.QFormLayout(targetCollapsibleButton) - - # - # target selector - # - self.targetSelector = slicer.qMRMLNodeComboBox() - self.targetSelector.nodeTypes = ["vtkMRMLMarkupsFiducialNode"] - self.targetSelector.addEnabled = False - self.targetSelector.removeEnabled = False - self.targetSelector.noneEnabled = True - self.targetSelector.setMRMLScene(slicer.mrmlScene) - self.targetSelector.setToolTip("Pick the surgical target that the tools should face.") - targetFormLayout.addRow("Surgical Target Fiducial", self.targetSelector) - - # - # Retarget button - # - self.retargetButton = qt.QPushButton("Aim Tools at Target") - self.retargetButton.toolTip = "Reset tool orientations to face target fiducial" - self.retargetButton.enabled = False - targetFormLayout.addRow(self.retargetButton) - - # - # Port Tool Display Options - # - toolsCollapsibleButton = ctk.ctkCollapsibleButton() - toolsCollapsibleButton.text = "Port Tool Display Options" - self.layout.addWidget(toolsCollapsibleButton) - toolsFormLayout = qt.QFormLayout(toolsCollapsibleButton) - - # - # Transform sliders - # - self.transformSliders = slicer.qMRMLTransformSliders() - self.transformSliders.TypeOfTransform = slicer.qMRMLTransformSliders.ROTATION - self.transformSliders.CoordinateReference = slicer.qMRMLTransformSliders.LOCAL - self.transformSliders.Title = 'Tool Orientation' - self.transformSliders.minMaxVisible = False - toolsFormLayout.addRow(self.transformSliders) - - # - # radius spin box - # - self.radiusSpinBox = qt.QDoubleSpinBox() - self.radiusSpinBox.setMinimum(0.0) - self.radiusSpinBox.setMaximum(10.0) - self.radiusSpinBox.setValue(2.0) - toolsFormLayout.addRow("Tool radius", self.radiusSpinBox) - - # - # length spin box - # - self.lengthSpinBox = qt.QDoubleSpinBox() - self.lengthSpinBox.setMinimum(0.0) - self.lengthSpinBox.setMaximum(250.0) - self.lengthSpinBox.setValue(150.0) - toolsFormLayout.addRow("Tool length", self.lengthSpinBox) - - # connections - self.portListSelector.connect('currentNodeChanged(bool)', self.onPortListSelectorChanged) - self.targetSelector.connect('currentNodeChanged(bool)', self.onTargetSelectorChanged) - self.addPortListButton.connect('clicked(bool)', self.onAddPortListButton) - self.removePortButton.connect('clicked(bool)', self.onRemovePortButton) - self.retargetButton.connect('clicked(bool)', self.onRetargetButton) - self.radiusSpinBox.connect('valueChanged(double)', self.onToolShapeChanged) - self.lengthSpinBox.connect('valueChanged(double)', self.onToolShapeChanged) - - # Add vertical spacer - self.layout.addStretch(1) - - # Add observer to scene for removal of fiducial nodes - self.sceneObserverTag = slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeRemovedEvent, - self.onNodeRemoved) - - # instantiate port placement module logic - self.logic = PortPlacementLogic() - - self.currentMarkupsNode = None - self.onMarkupAddedTag = None - - def __del__(self): - slicer.mrmlScene.RemoveObserver(self.sceneObserverTag) - if not (self.currentMarkupsNode is None): - self.currentMarkupsNode.RemoveObserver(self.onMarkupAddedTag) - - def onPortListSelectorChanged(self, valid): - self.addPortListButton.enabled = valid - - def onTargetSelectorChanged(self, valid): - self.retargetButton.enabled = valid - - def onAddPortListButton(self): - self.logic.setMarkupsNode(self.portListSelector.currentNode(), - self.radiusSpinBox.value, - self.lengthSpinBox.value) - if not (self.currentMarkupsNode is None): - self.currentMarkupsNode.RemoveObserver(self.onMarkupAddedTag) - - self.currentMarkupsNode = self.portListSelector.currentNode() - self.onMarkupAddedTag = self.currentMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.MarkupAddedEvent, self.onMarkupAdded) - - self.updateTable() - - def onRemovePortButton(self): - # Problem: currentIndex() can be valid even when there is no - # visible selection. This will have to do for now while I figure - # out how to making Python bindings to QList (ugh) - index = self.portsTable.selectionModel().currentIndex - self.logic.markupsNode.RemoveMarkup(index.row()) - self.updateTable() - - def onTableItemChanged(self,item): - # Get markup index corresponding to the table item - idx = self.itemPortIdxMap[item] - - # If the item is checkable, it's a visibility column. If not, it's - # the name column - if item.isCheckable(): - if item.checkState() == 2: # checked enum - self.logic.makeToolVisible(idx) - else: - self.logic.makeToolInvisible(idx) - - else: # name column - self.logic.markupsNode.SetNthFiducialLabel(idx, item.text()) - - def onMarkupAdded(self, node, event): - self.logic.onMarkupAdded(self.radiusSpinBox.value, self.lengthSpinBox.value) - self.updateTable() - - def onNodeRemoved(self, scene, event): - self.logic.onNodeRemoved() - if not (slicer.mrmlScene.IsNodePresent(self.currentMarkupsNode)): - self.currentMarkupsNode = None - self.updateTable() - - def onRetargetButton(self): - self.logic.retargetTools(self.targetSelector.currentNode()) - - def onToolShapeChanged(self): - toolIdx = self.portsTable.selectionModel().currentIndex().row() - if toolIdx < self.portsTableModel.rowCount: - self.logic.setToolShape(toolIdx, self.radiusSpinBox.value, self.lengthSpinBox.value) - - def onCurrentToolChanged(self, newIndex, prevIndex): - self.removePortButton.enabled = True - self.transformSliders.setMRMLTransformNode(self.logic.getToolTransform(newIndex.row())) - self.radiusSpinBox.setValue(self.logic.getToolRadius(newIndex.row())) - self.lengthSpinBox.setValue(self.logic.getToolLength(newIndex.row())) - - def updateTable(self): - self.portsTableModel = qt.QStandardItemModel() - self.portsTable.setModel(self.portsTableModel) - self.transformSliders.reset() - self.removePortButton.enabled = False - self.transformSliders.setMRMLTransformNode(None) - - node = self.logic.markupsNode - - if node is None: - return - - self.itemPortIdxMap = {} - for i in range(node.GetNumberOfFiducials()): - item = qt.QStandardItem() - item.setText(self.logic.getPortName(i)) - self.portsTableModel.setItem(i, 0, item) - self.itemPortIdxMap[item] = i - - item = qt.QStandardItem() - item.setText('Visible') - item.setCheckable(True) - if self.logic.isToolVisible(i): - checkState = 2 # checked enum - else: - checkState = 0 # unchecked enum - item.setCheckState(checkState) - self.portsTableModel.setItem(i, 1, item) - self.itemPortIdxMap[item] = i - self.portsTableModel.setHeaderData(0, 1, "Port Fiducial Name") - self.portsTableModel.setHeaderData(1, 1, " ") - self.portsTable.setColumnWidth(0, 15*len("Port Fiducial Name")) - - self.portsTableModel.itemChanged.connect(self.onTableItemChanged) - self.portsTable.selectionModel().currentRowChanged.connect(self.onCurrentToolChanged) - - def onReload(self,moduleName="PortPlacement"): - """Generic reload method for any scripted module. - ModuleWizard will subsitute correct default moduleName. - """ - import imp, sys, os, slicer - - widgetName = moduleName + "Widget" - - # reload the source code - # - set source file path - # - load the module to the global space - filePath = eval('slicer.modules.%s.path' % moduleName.lower()) - p = os.path.dirname(filePath) - if not sys.path.__contains__(p): - sys.path.insert(0,p) - fp = open(filePath, "r") - globals()[moduleName] = imp.load_module( - moduleName, fp, filePath, ('.py', 'r', imp.PY_SOURCE)) - fp.close() - - # rebuild the widget - # - find and hide the existing widget - # - create a new widget in the existing parent - parent = slicer.util.findChildren(name='%s Reload' % moduleName)[0].parent().parent() - for child in parent.children(): - try: - child.hide() - except AttributeError: - pass - # Remove spacer items - item = parent.layout().itemAt(0) - while item: - parent.layout().removeItem(item) - item = parent.layout().itemAt(0) - # create new widget inside existing parent - globals()[widgetName.lower()] = eval( - 'globals()["%s"].%s(parent)' % (moduleName, widgetName)) - globals()[widgetName.lower()].setup() - - def onReloadAndTest(self,moduleName="PortPlacement"): - try: - self.onReload() - evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName) - tester = eval(evalString) - tester.runTest() - except Exception, e: - import traceback - traceback.print_exc() - qt.QMessageBox.warning(slicer.util.mainWindow(), - "Reload and Test", 'Exception!\n\n' + str(e) + "\n\nSee Python Console for Stack Trace") - - -# -# PortPlacementLogic -# - -class PortPlacementLogic: - """This class should implement all the actual - computation done by your module. The interface - should be such that other python code can import - this class and make use of the functionality without - requiring an instance of the Widget - """ - - class Tool: - - def __init__(self, markupID, radius, length, position): - # We use the markupID to check whether this tool's markup has - # been removed in the onMarkupRemoved event - self.markupID = markupID - - # Create a vtkCylinderSource for rendering the tool - self.toolSrc = vtk.vtkCylinderSource() - self.toolSrc.SetRadius(radius) - self.toolSrc.SetHeight(length) - - # Create tool model using cylinder source - self.modelNode = slicer.vtkMRMLModelNode() - self.modelNode.HideFromEditorsOn() - self.modelNode.SetName(slicer.mrmlScene.GenerateUniqueName("Tool")) - polyData = self.toolSrc.GetOutput() - self.modelNode.SetAndObservePolyData(polyData) - slicer.mrmlScene.AddNode(self.modelNode) - - # Create a DisplayNode for this node (so that it can control its - # own visibility) and have modelNode observe it - self.modelDisplay = slicer.vtkMRMLModelDisplayNode() - self.modelDisplay.SetColor(0,1,1) # cyan - slicer.mrmlScene.AddNode(self.modelDisplay) - self.modelNode.SetAndObserveDisplayNodeID(self.modelDisplay.GetID()) - - # Create a transform for our tool model that will scale it to - # the proper radius, length, and position. Leave the orientation - # at the default. - self.transformNode = slicer.vtkMRMLLinearTransformNode() - self.transformNode.HideFromEditorsOn() - self.transformNode.SetName(slicer.mrmlScene.GenerateUniqueName("Transform")) - self.updatePosition(position) - slicer.mrmlScene.AddNode(self.transformNode) - - # apply the new transform to our tool - self.modelNode.SetAndObserveTransformNodeID(self.transformNode.GetID()) - - # Set tool to be visible - self.visible = True - - def updateShape(self, radius, length): - self.toolSrc.SetRadius(radius) - self.toolSrc.SetHeight(length) - self.toolSrc.Update() - - def updatePosition(self, p): - mat = vtk.vtkMatrix4x4() - self.transformNode.GetMatrixTransformToWorld(mat) - currPos = [mat.GetElement(j, 3) for j in [0,1,2]] - trans = [x - y for (x,y) in zip(p, currPos)] - - t = vtk.vtkTransform() - t.Translate(trans) - - self.transformNode.ApplyTransformMatrix(t.GetMatrix()) - - def makeVisible(self): - self.modelDisplay.VisibilityOn() - self.visible = True - - def makeInvisible(self): - self.modelDisplay.VisibilityOff() - self.visible = False - - def __del__(self): - slicer.mrmlScene.RemoveNode(self.modelNode) - - # This line causes a "node already removed" error. Weird... - # slicer.mrmlScene.RemoveNode(self.modelDisplay) - - slicer.mrmlScene.RemoveNode(self.transformNode) - - # End Tool - - - def __init__(self): - self.markupsNode = None - self.toolList = [] - - def __del__(self): - if not (self.markupsNode is None): - self.markupsNode.RemoveObserver(self.nodeObserverTag) - - # CAREFUL: the order of these operations is important - def clearMarkupsNode(self): - # If we were observing another node, remove first remove ourself - # as an observer - if not (self.markupsNode is None): - self.markupsNode.RemoveObserver(self.pointModifiedObserverTag) - self.markupsNode.RemoveObserver(self.markupRemovedObserverTag) - - self.markupsNode = None - - # Clear the current tool list - self.toolList = [] - - def setMarkupsNode(self, node, radius, length): - self.clearMarkupsNode() - - self.markupsNode = node - if not (node is None): - self.pointModifiedObserverTag = node.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, self.onMarkupModified) - self.markupRemovedObserverTag = node.AddObserver(slicer.vtkMRMLMarkupsNode.MarkupRemovedEvent, self.onMarkupRemoved) - - # Add tools associated with the markups in markupsNode - for i in range(node.GetNumberOfFiducials()): - p = [0,0,0] - node.GetNthFiducialPosition(i, p) - self.toolList.append(self.Tool(node.GetNthMarkupID(i), radius, length, p)) - - def setToolShape(self, idx, radius, length): - self.toolList[idx].updateShape(radius, length) - - def isToolVisible(self, idx): - return self.toolList[idx].visible - - def makeToolVisible(self, idx): - self.toolList[idx].makeVisible() - - def makeToolInvisible(self, idx): - self.toolList[idx].makeInvisible() - - def getToolTransform(self, idx): - return self.toolList[idx].transformNode - - def getToolRadius(self, idx): - return self.toolList[idx].toolSrc.GetRadius() - - def getToolLength(self, idx): - return self.toolList[idx].toolSrc.GetHeight() - - def getPortName(self, idx): - return self.markupsNode.GetNthFiducialLabel(idx) - - def onMarkupAdded(self, radius, length): - newMarkupIdx = self.markupsNode.GetNumberOfFiducials() - 1 - p = [0,0,0] - self.markupsNode.GetNthFiducialPosition(newMarkupIdx, p) - self.toolList.append(self.Tool(self.markupsNode.GetNthMarkupID(newMarkupIdx), radius, length, p)) - - # If the node we're observing has been modified, just iterate - # through and update all the tools since for now we don't know how - # to distinguish which markup got modified. - def onMarkupModified(self, node, event): - for i in range(node.GetNumberOfFiducials()): - p = [0,0,0] - node.GetNthFiducialPosition(i, p) - self.toolList[i].updatePosition(p) - - # If the markupsNode this logic is associated with was deleted, - # clear the tools - def onNodeRemoved(self): - if (self.markupsNode is None): - return - - if not slicer.mrmlScene.IsNodePresent(self.markupsNode): - self.clearMarkupsNode() - - # If a markup has been removed from the markupsNode we're associated - # with, remove the corresponding tool - def onMarkupRemoved(self, node, event): - if self.markupsNode is None: - return - - markupsSet = set() - for i in range(self.markupsNode.GetNumberOfFiducials()): - markupsSet.add(self.markupsNode.GetNthMarkupID(i)) - - prevLen = len(self.toolList) - for i in range(len(self.toolList)): - if not (self.toolList[i].markupID in markupsSet): - del self.toolList[i] - break - if prevLen == len(self.toolList): - print("One of our nodes wasn't removed...") - - # Target all tools toward the targetFid - def retargetTools(self, targetFid): - if self.markupsNode is None or \ - targetFid.GetNumberOfFiducials() < 1 or \ - targetFid == self.markupsNode: - return - - import numpy - import numpy.linalg - import math - # Get target coordinates - targetLoc = [0,0,0] - targetFid.GetNthFiducialPosition(0, targetLoc) - targetLoc = numpy.array(targetLoc) - - # Iterate through port tool map and retarget their associated tools - for i in range(self.markupsNode.GetNumberOfFiducials()): - portLocList = [0,0,0] - self.markupsNode.GetNthFiducialPosition(i, portLocList) - portLoc = numpy.array(portLocList) - - # Assume tools get drawn aligned with the global y-axis. We - # want to transform the tool to be oriented along the port - # toward the target point. We begin by finding the axis to - # rotate the tool by, which is the cross product of the - # global y and the target vector - targetVec = targetLoc - portLoc - - if numpy.linalg.norm(targetVec) <= 0.0000001: - continue - - targetVec = targetVec / numpy.linalg.norm(targetVec) - - mat = vtk.vtkMatrix4x4() - self.toolList[i].transformNode.GetMatrixTransformToWorld(mat) - currentVec = numpy.array([mat.GetElement(j, 1) for j in [0,1,2]]) - - rotAxis = numpy.cross(currentVec, targetVec) - normRotAxis = numpy.linalg.norm(rotAxis) - if normRotAxis <= 0.000001: - continue - - # get rotation angle and normalize rotation axis - rotAxis = rotAxis / numpy.linalg.norm(rotAxis) - angle = math.acos(numpy.dot(currentVec, targetVec)) * 180. / math.pi - - # generate our transform - t = vtk.vtkTransform() - trans = [mat.GetElement(j, 3) for j in [0,1,2]] - negTrans = [-x for x in trans] - - t.Translate(trans) - t.RotateWXYZ(angle, rotAxis.tolist()) - t.Translate(negTrans) - - self.toolList[i].transformNode.ApplyTransformMatrix(t.GetMatrix()) - - -class PortPlacementTest(unittest.TestCase): - """ - This is the test case for your scripted module. - """ - - def delayDisplay(self,message,msec=1000): - """This utility method displays a small dialog and waits. - This does two things: 1) it lets the event loop catch up - to the state of the test so that rendering and widget updates - have all taken place before the test continues and 2) it - shows the user/developer/tester the state of the test - so that we'll know when it breaks. - """ - print(message) - self.info = qt.QDialog() - self.infoLayout = qt.QVBoxLayout() - self.info.setLayout(self.infoLayout) - self.label = qt.QLabel(message,self.info) - self.infoLayout.addWidget(self.label) - qt.QTimer.singleShot(msec, self.info.close) - self.info.exec_() - - def setUp(self): - """ Do whatever is needed to reset the state - typically a scene clear will be enough. - """ - slicer.mrmlScene.Clear(0) - - def runTest(self): - """Run as few or as many tests as needed here. - """ - self.setUp() - self.test_PortPlacement1() - - def test_PortPlacement1(self): - import numpy - import numpy.linalg - import random - - self.delayDisplay("Starting test...") - - m = slicer.util.mainWindow() - m.moduleSelector().selectModule('PortPlacement') - self.widget = slicer.modules.PortPlacementWidget - - logic = self.widget.logic - markupsLogic = slicer.modules.markups.logic() - - # check init - self.assertTrue(self.widget.portsTableModel.rowCount() == 0) - self.assertTrue(not self.widget.addPortListButton.enabled) - - # add a list of ports - nodeID = markupsLogic.AddNewFiducialNode() - fidNode = slicer.mrmlScene.GetNodeByID(nodeID) - numPorts = 4 - for i in range(numPorts): - fidNode.AddFiducial(*[random.uniform(-100.,100.) for j in range(3)]) - self.widget.portListSelector.setCurrentNode(fidNode) - self.assertTrue(self.widget.addPortListButton.enabled) - self.widget.onAddPortListButton() - - # check tool transforms - self.assertTrue(len(logic.toolList) == numPorts) - for i in range(numPorts): - p = [0,0,0] - fidNode.GetNthFiducialPosition(i, p) - tool_mat = vtk.vtkMatrix4x4() - logic.toolList[i].transformNode.GetMatrixTransformToWorld(tool_mat) - tool_pos = [tool_mat.GetElement(j, 3) for j in [0,1,2]] - diff = numpy.array(p) - numpy.array(tool_pos) - self.assertTrue(numpy.dot(diff,diff) < 1e-10) - - - # check table - self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts) - for i in range(numPorts): - self.assertTrue(self.widget.portsTableModel.item(i).text() == fidNode.GetNthMarkupLabel(i)) - - # check removal - self.assertTrue(not self.widget.removePortButton.enabled) - index = self.widget.portsTableModel.index(0,0) - self.widget.portsTable.selectionModel().setCurrentIndex(index, qt.QItemSelectionModel.Current) - self.assertTrue(self.widget.removePortButton.enabled) - self.widget.onRemovePortButton() - self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts - 1) - self.assertTrue(len(logic.toolList) == numPorts - 1) - self.assertTrue(fidNode.GetNumberOfFiducials() == numPorts - 1) - - # check add - fidNode.AddFiducial(*[random.uniform(-100.,100.) for i in range(3)]) - self.assertTrue(len(logic.toolList) == numPorts) - self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts) - - # check retarget - self.assertTrue(not self.widget.retargetButton.enabled) - targetNodeID = markupsLogic.AddNewFiducialNode() - targetNode = slicer.mrmlScene.GetNodeByID(targetNodeID) - targetNode.AddFiducial(*[random.uniform(-100.,100.) for i in range(3)]) - self.widget.targetSelector.setCurrentNode(targetNode) - self.assertTrue(self.widget.retargetButton.enabled) - self.widget.onRetargetButton() - - target_p = [0,0,0] - targetNode.GetNthFiducialPosition(0, target_p) - targetWorld = target_p - - # check retargeting by verifying that tools' positions are - # unchanged and that their y-axes are oriented toward point - for i in range(numPorts): - p = [0,0,0] - fidNode.GetNthFiducialPosition(i, p) - tool_mat = vtk.vtkMatrix4x4() - logic.toolList[i].transformNode.GetMatrixTransformToWorld(tool_mat) - tool_pos = [tool_mat.GetElement(j, 3) for j in [0,1,2]] - diff = numpy.array(p) - numpy.array(tool_pos) - self.assertTrue(numpy.dot(diff,diff) < 1e-10) - - targetLocal = [0,0,0] - logic.toolList[i].modelNode.TransformPointFromWorld(targetWorld, targetLocal) - - targetLocal = numpy.array(targetLocal)[0:3] - targetLocal = targetLocal / numpy.linalg.norm(targetLocal) - - # target local should be the unit y-axis (e2) - yAxis = numpy.array([0.,1.,0.]) - diff = yAxis - targetLocal - - self.assertTrue(numpy.dot(diff,diff) < 1e-10) - - # test fidNode deletion - slicer.mrmlScene.RemoveNode(fidNode) - self.assertTrue(len(logic.toolList) == 0) - self.assertTrue(self.widget.portsTableModel.rowCount() == 0) - - self.delayDisplay("Test passed!") +import os +import unittest +from __main__ import vtk, qt, ctk, slicer +import string +# +# PortPlacement +# + +class PortPlacement: + def __init__(self, parent): + parent.title = "Port Placement" + parent.categories = ["IGT"] + parent.dependencies = [] + parent.contributors = ["Andinet Enquobahrie (Kitware), Luis G. Torres (UNC)"] + parent.helpText = string.Template(""" + The PortPlacement module assists in the planning of surgical port placement in a laparoscopic procedure. Users can specify ports using fiducial markers and the module will automatically visualize simulated surgical tools at the port locations. Users can freely pivot the simulated surgical tools about the ports, as well as automatically orient all surgical tools toward a specified surgical target. Simulated tools are represented by cylinders whose lengths and radii can be varied from tool to tool. + + See the module documentation for more details. + """).substitute({ 'a':parent.slicerWikiUrl}) + parent.acknowledgementText = """ + This work was supported by NSF GRFP Grant No. DGE-1144081 and partially supported by NIH/NIBIB Grant No. 1R43EB014074-01. Anatomical atlas volume in module and extension icons courtesy of the Surgical Planning Laboratory (Link) +""" + self.parent = parent + + # Add this test to the SelfTest module's list for discovery when the module + # is created. Since this module may be discovered before SelfTests itself, + # create the list if it doesn't already exist. + try: + slicer.selfTests + except AttributeError: + slicer.selfTests = {} + slicer.selfTests['PortPlacement'] = self.runTest + + def runTest(self): + tester = PortPlacement() + tester.runTest() + +# +# PortPlacementWidget +# + +class PortPlacementWidget: + def __init__(self, parent = None): + if not parent: + self.parent = slicer.qMRMLWidget() + self.parent.setLayout(qt.QVBoxLayout()) + self.parent.setMRMLScene(slicer.mrmlScene) + else: + self.parent = parent + self.layout = self.parent.layout() + if not parent: + self.setup() + self.parent.show() + + def setup(self): + # Instantiate and connect widgets ... + + # # + # # Reload and Test area + # # + # reloadCollapsibleButton = ctk.ctkCollapsibleButton() + # reloadCollapsibleButton.text = "Reload && Test" + # self.layout.addWidget(reloadCollapsibleButton) + # reloadFormLayout = qt.QFormLayout(reloadCollapsibleButton) + + # # reload button + # # (use this during development, but remove it when delivering + # # your module to users) + # self.reloadButton = qt.QPushButton("Reload") + # self.reloadButton.toolTip = "Reload this module." + # self.reloadButton.name = "PortPlacement Reload" + # reloadFormLayout.addWidget(self.reloadButton) + # self.reloadButton.connect('clicked()', self.onReload) + + # # reload and test button + # # (use this during development, but remove it when delivering + # # your module to users) + # self.reloadAndTestButton = qt.QPushButton("Reload and Test") + # self.reloadAndTestButton.toolTip = "Reload this module and then run the self tests." + # reloadFormLayout.addWidget(self.reloadAndTestButton) + # self.reloadAndTestButton.connect('clicked()', self.onReloadAndTest) + + # + # Ports Area + # + portsCollapsibleButton = ctk.ctkCollapsibleButton() + portsCollapsibleButton.text = "Surgical Ports" + self.layout.addWidget(portsCollapsibleButton) + portsFormLayout = qt.QFormLayout(portsCollapsibleButton) + + # + # port fiducial list selector + # + self.portListSelector = slicer.qMRMLNodeComboBox() + self.portListSelector.nodeTypes = ["vtkMRMLMarkupsFiducialNode"] + self.portListSelector.addEnabled = False + self.portListSelector.removeEnabled = False + self.portListSelector.noneEnabled = True + self.portListSelector.setMRMLScene(slicer.mrmlScene) + self.portListSelector.setToolTip("Add surgical ports from a markups node.") + portsFormLayout.addRow("Markups node of surgical ports", self.portListSelector) + + # + # Add Port List button + # + self.addPortListButton = qt.QPushButton("Set Port Markups Node") + self.addPortListButton.enabled = False + portsFormLayout.addRow(self.addPortListButton) + + # + # Port table + # + self.portsTable = qt.QTableView() + self.portsTableModel = qt.QStandardItemModel() + self.portsTable.setModel(self.portsTableModel) + portsFormLayout.addRow(self.portsTable) + + # + # Remove Port button + # + self.removePortButton = qt.QPushButton("Remove Selected Port") + self.removePortButton.enabled = False + portsFormLayout.addRow(self.removePortButton) + + # + # Target area + # + targetCollapsibleButton = ctk.ctkCollapsibleButton() + targetCollapsibleButton.text = "Surgical Target" + self.layout.addWidget(targetCollapsibleButton) + targetFormLayout = qt.QFormLayout(targetCollapsibleButton) + + # + # target selector + # + self.targetSelector = slicer.qMRMLNodeComboBox() + self.targetSelector.nodeTypes = ["vtkMRMLMarkupsFiducialNode"] + self.targetSelector.addEnabled = False + self.targetSelector.removeEnabled = False + self.targetSelector.noneEnabled = True + self.targetSelector.setMRMLScene(slicer.mrmlScene) + self.targetSelector.setToolTip("Pick the surgical target that the tools should face.") + targetFormLayout.addRow("Surgical Target Fiducial", self.targetSelector) + + # + # Retarget button + # + self.retargetButton = qt.QPushButton("Aim Tools at Target") + self.retargetButton.toolTip = "Reset tool orientations to face target fiducial" + self.retargetButton.enabled = False + targetFormLayout.addRow(self.retargetButton) + + # + # Port Tool Display Options + # + toolsCollapsibleButton = ctk.ctkCollapsibleButton() + toolsCollapsibleButton.text = "Port Tool Display Options" + self.layout.addWidget(toolsCollapsibleButton) + toolsFormLayout = qt.QFormLayout(toolsCollapsibleButton) + + # + # Transform sliders + # + self.transformSliders = slicer.qMRMLTransformSliders() + self.transformSliders.TypeOfTransform = slicer.qMRMLTransformSliders.ROTATION + self.transformSliders.CoordinateReference = slicer.qMRMLTransformSliders.LOCAL + self.transformSliders.Title = 'Tool Orientation' + self.transformSliders.minMaxVisible = False + toolsFormLayout.addRow(self.transformSliders) + + # + # radius spin box + # + self.radiusSpinBox = qt.QDoubleSpinBox() + self.radiusSpinBox.setMinimum(0.0) + self.radiusSpinBox.setMaximum(10.0) + self.radiusSpinBox.setValue(2.0) + toolsFormLayout.addRow("Tool radius", self.radiusSpinBox) + + # + # length spin box + # + self.lengthSpinBox = qt.QDoubleSpinBox() + self.lengthSpinBox.setMinimum(0.0) + self.lengthSpinBox.setMaximum(250.0) + self.lengthSpinBox.setValue(150.0) + toolsFormLayout.addRow("Tool length", self.lengthSpinBox) + + # connections + self.portListSelector.connect('currentNodeChanged(bool)', self.onPortListSelectorChanged) + self.targetSelector.connect('currentNodeChanged(bool)', self.onTargetSelectorChanged) + self.addPortListButton.connect('clicked(bool)', self.onAddPortListButton) + self.removePortButton.connect('clicked(bool)', self.onRemovePortButton) + self.retargetButton.connect('clicked(bool)', self.onRetargetButton) + self.radiusSpinBox.connect('valueChanged(double)', self.onToolShapeChanged) + self.lengthSpinBox.connect('valueChanged(double)', self.onToolShapeChanged) + + # Add vertical spacer + self.layout.addStretch(1) + + # Add observer to scene for removal of fiducial nodes + self.sceneObserverTag = slicer.mrmlScene.AddObserver(slicer.mrmlScene.NodeRemovedEvent, + self.onNodeRemoved) + + # instantiate port placement module logic + self.logic = PortPlacementLogic() + + self.currentMarkupsNode = None + self.onMarkupAddedTag = None + + def __del__(self): + slicer.mrmlScene.RemoveObserver(self.sceneObserverTag) + if not (self.currentMarkupsNode is None): + self.currentMarkupsNode.RemoveObserver(self.onMarkupAddedTag) + + def onPortListSelectorChanged(self, valid): + self.addPortListButton.enabled = valid + + def onTargetSelectorChanged(self, valid): + self.retargetButton.enabled = valid + + def onAddPortListButton(self): + self.logic.setMarkupsNode(self.portListSelector.currentNode(), + self.radiusSpinBox.value, + self.lengthSpinBox.value) + if not (self.currentMarkupsNode is None): + self.currentMarkupsNode.RemoveObserver(self.onMarkupAddedTag) + + self.currentMarkupsNode = self.portListSelector.currentNode() + self.onMarkupAddedTag = self.currentMarkupsNode.AddObserver(slicer.vtkMRMLMarkupsNode.PointAddedEvent, self.onMarkupAdded) + + self.updateTable() + + def onRemovePortButton(self): + # Problem: currentIndex() can be valid even when there is no + # visible selection. This will have to do for now while I figure + # out how to making Python bindings to QList (ugh) + index = self.portsTable.selectionModel().currentIndex + self.logic.markupsNode.RemoveMarkup(index.row()) + self.updateTable() + + def onTableItemChanged(self,item): + # Get markup index corresponding to the table item + idx = self.itemPortIdxMap[item] + + # If the item is checkable, it's a visibility column. If not, it's + # the name column + if item.isCheckable(): + if item.checkState() == 2: # checked enum + self.logic.makeToolVisible(idx) + else: + self.logic.makeToolInvisible(idx) + + else: # name column + self.logic.markupsNode.SetNthFiducialLabel(idx, item.text()) + + def onMarkupAdded(self, node, event): + self.logic.onMarkupAdded(self.radiusSpinBox.value, self.lengthSpinBox.value) + self.updateTable() + + def onNodeRemoved(self, scene, event): + self.logic.onNodeRemoved() + if not (slicer.mrmlScene.IsNodePresent(self.currentMarkupsNode)): + self.currentMarkupsNode = None + self.updateTable() + + def onRetargetButton(self): + self.logic.retargetTools(self.targetSelector.currentNode()) + + def onToolShapeChanged(self): + toolIdx = self.portsTable.selectionModel().currentIndex.row() + if toolIdx < self.portsTableModel.rowCount: + self.logic.setToolShape(toolIdx, self.radiusSpinBox.value, self.lengthSpinBox.value) + + def onCurrentToolChanged(self, newIndex, prevIndex): + self.removePortButton.enabled = True + self.transformSliders.setMRMLTransformNode(self.logic.getToolTransform(newIndex.row())) + self.radiusSpinBox.setValue(self.logic.getToolRadius(newIndex.row())) + self.lengthSpinBox.setValue(self.logic.getToolLength(newIndex.row())) + + def updateTable(self): + self.portsTableModel = qt.QStandardItemModel() + self.portsTable.setModel(self.portsTableModel) + self.transformSliders.reset() + self.removePortButton.enabled = False + self.transformSliders.setMRMLTransformNode(None) + + node = self.logic.markupsNode + + if node is None: + return + + self.itemPortIdxMap = {} + for i in range(node.GetNumberOfFiducials()): + item = qt.QStandardItem() + item.setText(self.logic.getPortName(i)) + self.portsTableModel.setItem(i, 0, item) + self.itemPortIdxMap[item] = i + + item = qt.QStandardItem() + item.setText('Visible') + item.setCheckable(True) + if self.logic.isToolVisible(i): + checkState = 2 # checked enum + else: + checkState = 0 # unchecked enum + item.setCheckState(checkState) + self.portsTableModel.setItem(i, 1, item) + self.itemPortIdxMap[item] = i + self.portsTableModel.setHeaderData(0, 1, "Port Fiducial Name") + self.portsTableModel.setHeaderData(1, 1, " ") + self.portsTable.setColumnWidth(0, 15*len("Port Fiducial Name")) + + self.portsTableModel.itemChanged.connect(self.onTableItemChanged) + self.portsTable.selectionModel().currentRowChanged.connect(self.onCurrentToolChanged) + + def onReload(self,moduleName="PortPlacement"): + """Generic reload method for any scripted module. + ModuleWizard will subsitute correct default moduleName. + """ + import imp, sys, os, slicer + + widgetName = moduleName + "Widget" + + # reload the source code + # - set source file path + # - load the module to the global space + filePath = eval('slicer.modules.%s.path' % moduleName.lower()) + p = os.path.dirname(filePath) + if not sys.path.__contains__(p): + sys.path.insert(0,p) + fp = open(filePath, "r") + globals()[moduleName] = imp.load_module( + moduleName, fp, filePath, ('.py', 'r', imp.PY_SOURCE)) + fp.close() + + # rebuild the widget + # - find and hide the existing widget + # - create a new widget in the existing parent + parent = slicer.util.findChildren(name='%s Reload' % moduleName)[0].parent().parent() + for child in parent.children(): + try: + child.hide() + except AttributeError: + pass + # Remove spacer items + item = parent.layout().itemAt(0) + while item: + parent.layout().removeItem(item) + item = parent.layout().itemAt(0) + # create new widget inside existing parent + globals()[widgetName.lower()] = eval( + 'globals()["%s"].%s(parent)' % (moduleName, widgetName)) + globals()[widgetName.lower()].setup() + + def onReloadAndTest(self,moduleName="PortPlacement"): + try: + self.onReload() + evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName) + tester = eval(evalString) + tester.runTest() + except Exception as e: + import traceback + traceback.print_exc() + qt.QMessageBox.warning(slicer.util.mainWindow(), + "Reload and Test", 'Exception!\n\n' + str(e) + "\n\nSee Python Console for Stack Trace") + + +# +# PortPlacementLogic +# + +class PortPlacementLogic: + """This class should implement all the actual + computation done by your module. The interface + should be such that other python code can import + this class and make use of the functionality without + requiring an instance of the Widget + """ + + class Tool: + + def __init__(self, markupID, radius, length, position): + # We use the markupID to check whether this tool's markup has + # been removed in the onMarkupRemoved event + self.markupID = markupID + + # Create a vtkCylinderSource for rendering the tool + self.toolSrc = vtk.vtkCylinderSource() + self.toolSrc.SetRadius(radius) + self.toolSrc.SetHeight(length) + + # Create tool model using cylinder source + self.modelNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLModelNode', slicer.mrmlScene.GenerateUniqueName("Tool")) + #self.modelNode.HideFromEditorsOn() + self.modelNode.SetSelectable(False) # Prevent picking on the tool model (endless loop until tip reaches camera) + self.modelNode.SetPolyDataConnection(self.toolSrc.GetOutputPort()) + self.modelNode.CreateDefaultDisplayNodes() + + # Create a DisplayNode for this node (so that it can control its + # own visibility) and have modelNode observe it + self.modelDisplay = self.modelNode.GetDisplayNode() + self.modelDisplay.SetColor(0,1,1) # cyan + + # Create a transform for our tool model that will scale it to + # the proper radius, length, and position. Leave the orientation + # at the default. + self.transformNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLLinearTransformNode', slicer.mrmlScene.GenerateUniqueName("Transform")) + #self.transformNode.HideFromEditorsOn() + self.updatePosition(position) + + # apply the new transform to our tool + self.modelNode.SetAndObserveTransformNodeID(self.transformNode.GetID()) + + # Set tool to be visible + self.visible = True + + def updateShape(self, radius, length): + self.toolSrc.SetRadius(radius) + self.toolSrc.SetHeight(length) + self.toolSrc.Update() + + def updatePosition(self, p): + mat = vtk.vtkMatrix4x4() + self.transformNode.GetMatrixTransformToWorld(mat) + currPos = [mat.GetElement(j, 3) for j in [0,1,2]] + trans = [x - y for (x,y) in zip(p, currPos)] + + t = vtk.vtkTransform() + t.Translate(trans) + + self.transformNode.ApplyTransformMatrix(t.GetMatrix()) + + def makeVisible(self): + self.modelDisplay.VisibilityOn() + self.visible = True + + def makeInvisible(self): + self.modelDisplay.VisibilityOff() + self.visible = False + + def __del__(self): + slicer.mrmlScene.RemoveNode(self.modelNode) + + # This line causes a "node already removed" error. Weird... + # slicer.mrmlScene.RemoveNode(self.modelDisplay) + + slicer.mrmlScene.RemoveNode(self.transformNode) + + # End Tool + + + def __init__(self): + self.markupsNode = None + self.toolList = [] + + def __del__(self): + if not (self.markupsNode is None): + self.markupsNode.RemoveObserver(self.nodeObserverTag) + + # CAREFUL: the order of these operations is important + def clearMarkupsNode(self): + # If we were observing another node, remove first remove ourself + # as an observer + if not (self.markupsNode is None): + self.markupsNode.RemoveObserver(self.pointModifiedObserverTag) + self.markupsNode.RemoveObserver(self.markupRemovedObserverTag) + + self.markupsNode = None + + # Clear the current tool list + self.toolList = [] + + def setMarkupsNode(self, node, radius, length): + self.clearMarkupsNode() + + self.markupsNode = node + if node: + self.pointModifiedObserverTag = node.AddObserver(slicer.vtkMRMLMarkupsNode.PointModifiedEvent, self.onMarkupModified) + self.markupRemovedObserverTag = node.AddObserver(slicer.vtkMRMLMarkupsNode.PointRemovedEvent, self.onMarkupRemoved) + + # Add tools associated with the markups in markupsNode + for i in range(node.GetNumberOfFiducials()): + p = [0,0,0] + node.GetNthFiducialPosition(i, p) + self.toolList.append(self.Tool(node.GetNthMarkupID(i), radius, length, p)) + + def setToolShape(self, idx, radius, length): + self.toolList[idx].updateShape(radius, length) + + def isToolVisible(self, idx): + return self.toolList[idx].visible + + def makeToolVisible(self, idx): + self.toolList[idx].makeVisible() + + def makeToolInvisible(self, idx): + self.toolList[idx].makeInvisible() + + def getToolTransform(self, idx): + return self.toolList[idx].transformNode + + def getToolRadius(self, idx): + return self.toolList[idx].toolSrc.GetRadius() + + def getToolLength(self, idx): + return self.toolList[idx].toolSrc.GetHeight() + + def getPortName(self, idx): + return self.markupsNode.GetNthFiducialLabel(idx) + + def onMarkupAdded(self, radius, length): + newMarkupIdx = self.markupsNode.GetNumberOfFiducials() - 1 + p = [0,0,0] + self.markupsNode.GetNthFiducialPosition(newMarkupIdx, p) + self.toolList.append(self.Tool(self.markupsNode.GetNthMarkupID(newMarkupIdx), radius, length, p)) + + # If the node we're observing has been modified, just iterate + # through and update all the tools since for now we don't know how + # to distinguish which markup got modified. + def onMarkupModified(self, node, event): + for i in range(node.GetNumberOfFiducials()): + p = [0,0,0] + node.GetNthFiducialPosition(i, p) + self.toolList[i].updatePosition(p) + + # If the markupsNode this logic is associated with was deleted, + # clear the tools + def onNodeRemoved(self): + if not self.markupsNode: + return + + if not slicer.mrmlScene.IsNodePresent(self.markupsNode): + self.clearMarkupsNode() + + # If a markup has been removed from the markupsNode we're associated + # with, remove the corresponding tool + def onMarkupRemoved(self, node, event): + if self.markupsNode is None: + return + + markupsSet = set() + for i in range(self.markupsNode.GetNumberOfFiducials()): + markupsSet.add(self.markupsNode.GetNthMarkupID(i)) + + prevLen = len(self.toolList) + for i in range(len(self.toolList)): + if not (self.toolList[i].markupID in markupsSet): + del self.toolList[i] + break + if prevLen == len(self.toolList): + print("One of our nodes wasn't removed...") + + # Target all tools toward the targetFid + def retargetTools(self, targetFid): + if (self.markupsNode is None or + targetFid.GetNumberOfFiducials() < 1 or + targetFid == self.markupsNode): + return + + import numpy + import numpy.linalg + import math + # Get target coordinates + targetLoc = [0,0,0] + targetFid.GetNthFiducialPosition(0, targetLoc) + targetLoc = numpy.array(targetLoc) + + # Iterate through port tool map and retarget their associated tools + for i in range(self.markupsNode.GetNumberOfFiducials()): + portLocList = [0,0,0] + self.markupsNode.GetNthFiducialPosition(i, portLocList) + portLoc = numpy.array(portLocList) + + # Assume tools get drawn aligned with the global y-axis. We + # want to transform the tool to be oriented along the port + # toward the target point. We begin by finding the axis to + # rotate the tool by, which is the cross product of the + # global y and the target vector + targetVec = targetLoc - portLoc + + if numpy.linalg.norm(targetVec) <= 0.0000001: + continue + + targetVec = targetVec / numpy.linalg.norm(targetVec) + + mat = vtk.vtkMatrix4x4() + self.toolList[i].transformNode.GetMatrixTransformToWorld(mat) + currentVec = numpy.array([mat.GetElement(j, 1) for j in [0,1,2]]) + + rotAxis = numpy.cross(currentVec, targetVec) + normRotAxis = numpy.linalg.norm(rotAxis) + if normRotAxis <= 0.000001: + continue + + # get rotation angle and normalize rotation axis + rotAxis = rotAxis / numpy.linalg.norm(rotAxis) + angle = math.acos(numpy.dot(currentVec, targetVec)) * 180. / math.pi + + # generate our transform + t = vtk.vtkTransform() + trans = [mat.GetElement(j, 3) for j in [0,1,2]] + negTrans = [-x for x in trans] + + t.Translate(trans) + t.RotateWXYZ(angle, rotAxis.tolist()) + t.Translate(negTrans) + + self.toolList[i].transformNode.ApplyTransformMatrix(t.GetMatrix()) + + +class PortPlacementTest(unittest.TestCase): + """ + This is the test case for your scripted module. + """ + + def delayDisplay(self,message,msec=1000): + """This utility method displays a small dialog and waits. + This does two things: 1) it lets the event loop catch up + to the state of the test so that rendering and widget updates + have all taken place before the test continues and 2) it + shows the user/developer/tester the state of the test + so that we'll know when it breaks. + """ + print(message) + self.info = qt.QDialog() + self.infoLayout = qt.QVBoxLayout() + self.info.setLayout(self.infoLayout) + self.label = qt.QLabel(message,self.info) + self.infoLayout.addWidget(self.label) + qt.QTimer.singleShot(msec, self.info.close) + self.info.exec_() + + def setUp(self): + """ Do whatever is needed to reset the state - typically a scene clear will be enough. + """ + slicer.mrmlScene.Clear(0) + + def runTest(self): + """Run as few or as many tests as needed here. + """ + self.setUp() + self.test_PortPlacement1() + + def test_PortPlacement1(self): + import numpy + import numpy.linalg + import random + + self.delayDisplay("Starting test...") + + m = slicer.util.mainWindow() + m.moduleSelector().selectModule('PortPlacement') + self.widget = slicer.modules.PortPlacementWidget + + logic = self.widget.logic + markupsLogic = slicer.modules.markups.logic() + + # check init + self.assertTrue(self.widget.portsTableModel.rowCount() == 0) + self.assertTrue(not self.widget.addPortListButton.enabled) + + # add a list of ports + nodeID = markupsLogic.AddNewFiducialNode() + fidNode = slicer.mrmlScene.GetNodeByID(nodeID) + numPorts = 4 + for i in range(numPorts): + fidNode.AddFiducial(*[random.uniform(-100.,100.) for j in range(3)]) + self.widget.portListSelector.setCurrentNode(fidNode) + self.assertTrue(self.widget.addPortListButton.enabled) + self.widget.onAddPortListButton() + + # check tool transforms + self.assertTrue(len(logic.toolList) == numPorts) + for i in range(numPorts): + p = [0,0,0] + fidNode.GetNthFiducialPosition(i, p) + tool_mat = vtk.vtkMatrix4x4() + logic.toolList[i].transformNode.GetMatrixTransformToWorld(tool_mat) + tool_pos = [tool_mat.GetElement(j, 3) for j in [0,1,2]] + diff = numpy.array(p) - numpy.array(tool_pos) + self.assertTrue(numpy.dot(diff,diff) < 1e-10) + + + # check table + self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts) + for i in range(numPorts): + self.assertTrue(self.widget.portsTableModel.item(i).text() == fidNode.GetNthMarkupLabel(i)) + + # check removal + self.assertTrue(not self.widget.removePortButton.enabled) + index = self.widget.portsTableModel.index(0,0) + self.widget.portsTable.selectionModel().setCurrentIndex(index, qt.QItemSelectionModel.Current) + self.assertTrue(self.widget.removePortButton.enabled) + self.widget.onRemovePortButton() + self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts - 1) + self.assertTrue(len(logic.toolList) == numPorts - 1) + self.assertTrue(fidNode.GetNumberOfFiducials() == numPorts - 1) + + # check add + fidNode.AddFiducial(*[random.uniform(-100.,100.) for i in range(3)]) + self.assertTrue(len(logic.toolList) == numPorts) + self.assertTrue(self.widget.portsTableModel.rowCount() == numPorts) + + # check retarget + self.assertTrue(not self.widget.retargetButton.enabled) + targetNodeID = markupsLogic.AddNewFiducialNode() + targetNode = slicer.mrmlScene.GetNodeByID(targetNodeID) + targetNode.AddFiducial(*[random.uniform(-100.,100.) for i in range(3)]) + self.widget.targetSelector.setCurrentNode(targetNode) + self.assertTrue(self.widget.retargetButton.enabled) + self.widget.onRetargetButton() + + target_p = [0,0,0] + targetNode.GetNthFiducialPosition(0, target_p) + targetWorld = target_p + + # check retargeting by verifying that tools' positions are + # unchanged and that their y-axes are oriented toward point + for i in range(numPorts): + p = [0,0,0] + fidNode.GetNthFiducialPosition(i, p) + tool_mat = vtk.vtkMatrix4x4() + logic.toolList[i].transformNode.GetMatrixTransformToWorld(tool_mat) + tool_pos = [tool_mat.GetElement(j, 3) for j in [0,1,2]] + diff = numpy.array(p) - numpy.array(tool_pos) + self.assertTrue(numpy.dot(diff,diff) < 1e-10) + + targetLocal = [0,0,0] + logic.toolList[i].modelNode.TransformPointFromWorld(targetWorld, targetLocal) + + targetLocal = numpy.array(targetLocal)[0:3] + targetLocal = targetLocal / numpy.linalg.norm(targetLocal) + + # target local should be the unit y-axis (e2) + yAxis = numpy.array([0.,1.,0.]) + diff = yAxis - targetLocal + + self.assertTrue(numpy.dot(diff,diff) < 1e-10) + + # test fidNode deletion + slicer.mrmlScene.RemoveNode(fidNode) + self.assertTrue(len(logic.toolList) == 0) + self.assertTrue(self.widget.portsTableModel.rowCount() == 0) + + self.delayDisplay("Test passed!") diff --git a/PortPlacement/Resources/Icons/ScriptedLoadableModuleTemplate.png b/PortPlacement/Resources/Icons/PortPlacement.png similarity index 100% rename from PortPlacement/Resources/Icons/ScriptedLoadableModuleTemplate.png rename to PortPlacement/Resources/Icons/PortPlacement.png diff --git a/SuperBuild.cmake b/SuperBuild.cmake index b95fc33..899f843 100644 --- a/SuperBuild.cmake +++ b/SuperBuild.cmake @@ -1,33 +1,26 @@ #----------------------------------------------------------------------------- -# Git protocol option -#----------------------------------------------------------------------------- -option(Slicer_USE_GIT_PROTOCOL "If behind a firewall turn this off to use http instead." ON) - -set(git_protocol "git") -if(NOT Slicer_USE_GIT_PROTOCOL) - set(git_protocol "http") -endif() - -#----------------------------------------------------------------------------- -# Enable and setup External project global properties +# External project common settings #----------------------------------------------------------------------------- set(ep_common_c_flags "${CMAKE_C_FLAGS_INIT} ${ADDITIONAL_C_FLAGS}") set(ep_common_cxx_flags "${CMAKE_CXX_FLAGS_INIT} ${ADDITIONAL_CXX_FLAGS}") #----------------------------------------------------------------------------- -# Project dependencies +# Top-level "external" project #----------------------------------------------------------------------------- -include(ExternalProject) - +# Extension dependencies foreach(dep ${EXTENSION_DEPENDS}) mark_as_superbuild(${dep}_DIR) endforeach() set(proj ${SUPERBUILD_TOPLEVEL_PROJECT}) -set(${proj}_DEPENDS NLopt) + +# Project dependencies +set(${proj}_DEPENDS + NLopt + ) ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj @@ -40,26 +33,27 @@ ExternalProject_Add(${proj} INSTALL_COMMAND "" SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR} BINARY_DIR ${EXTENSION_BUILD_SUBDIRECTORY} - BUILD_ALWAYS 1 CMAKE_CACHE_ARGS - -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} + # Compiler settings -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} + -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} + -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + # Output directories -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_LIBRARY_OUTPUT_DIRECTORY} -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} - -DSubversion_SVN_EXECUTABLE:FILEPATH=${Subversion_SVN_EXECUTABLE} - -DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE} + # Packaging -DMIDAS_PACKAGE_EMAIL:STRING=${MIDAS_PACKAGE_EMAIL} -DMIDAS_PACKAGE_API_KEY:STRING=${MIDAS_PACKAGE_API_KEY} + # Superbuild -D${EXTENSION_NAME}_SUPERBUILD:BOOL=OFF -DEXTENSION_SUPERBUILD_BINARY_DIR:PATH=${${EXTENSION_NAME}_BINARY_DIR} DEPENDS ${${proj}_DEPENDS} ) +ExternalProject_AlwaysConfigure(${proj}) diff --git a/SuperBuild/External_NLopt.cmake b/SuperBuild/External_NLopt.cmake index 221a83f..78adaa1 100644 --- a/SuperBuild/External_NLopt.cmake +++ b/SuperBuild/External_NLopt.cmake @@ -1,14 +1,15 @@ - set(proj NLopt) # Set dependency list -set(${proj}_DEPENDS "") +set(${proj}_DEPENDS + "" + ) # Include dependent projects if any ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) -if(${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) - message(FATAL_ERROR "Enabling ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj} is not supported !") +if(${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + message(FATAL_ERROR "Enabling ${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj} is not supported !") endif() # Sanity checks @@ -16,47 +17,65 @@ if(DEFINED NLopt_DIR AND NOT EXISTS ${NLopt_DIR}) message(FATAL_ERROR "NLopt_DIR variable is defined but corresponds to nonexistent directory") endif() -if(NOT DEFINED ${proj}_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) +if(NOT DEFINED ${proj}_DIR AND NOT ${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + + ExternalProject_SetIfNotDefined( + ${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_REPOSITORY + "${EP_GIT_PROTOCOL}://github.com/stevengj/nlopt.git" + QUIET + ) + + ExternalProject_SetIfNotDefined( + ${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_TAG + "014208e6bd03531aab8c45cf1902bec8b720df99" # master 2017-08-05 + QUIET + ) - if(NOT DEFINED git_protocol) - set(git_protocol "git") - endif() + set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) + set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) ExternalProject_Add(${proj} ${${proj}_EP_ARGS} - GIT_REPOSITORY "${git_protocol}://github.com/stevengj/nlopt.git" - GIT_TAG "014208e6bd03531aab8c45cf1902bec8b720df99" # master 2017-08-05 - SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} - BINARY_DIR ${proj}-build + GIT_REPOSITORY "${${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_REPOSITORY}" + GIT_TAG "${${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_TAG}" + SOURCE_DIR ${EP_SOURCE_DIR} + BINARY_DIR ${EP_BINARY_DIR} INSTALL_DIR ${proj}-install CMAKE_CACHE_ARGS - -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} + # Compiler settings -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} + -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} + -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - -DBUILD_TESTING:BOOL=OFF - -DCMAKE_MACOSX_RPATH:BOOL=0 + # Output directories -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY:PATH=${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} - # The following two variables should be udpated to match the - # requirements of a real CMake based external project. + # Install directories -DNLopt_INSTALL_RUNTIME_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} -DNLopt_INSTALL_LIBRARY_DIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} -DCMAKE_INSTALL_PREFIX:PATH= + + -DBUILD_TESTING:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF + + # TODO: check if needed + #-DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + #-DCMAKE_MACOSX_RPATH:BOOL=0 + DEPENDS ${${proj}_DEPENDS} ) - set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-install/lib/cmake/nlopt/) + set(${proj}_DIR ${EP_BINARY_DIR}) + + #TODO: check if needed + #set(${proj}_DIR ${CMAKE_BINARY_DIR}/${proj}-install/lib/cmake/nlopt/) else() ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) endif() mark_as_superbuild(${proj}_DIR:PATH) -