diff --git a/CMakeLists.txt b/CMakeLists.txt index 73fe606..4abe803 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,12 +3,12 @@ cmake_minimum_required(VERSION 2.8.9) project(PercutaneousApproachAnalysis) #----------------------------------------------------------------------------- -set(EXTENSION_HOMEPAGE "http://www.slicer.org/slicerWiki/index.php/Documentation/Nightly/PercutaneousApproachAnalysis") +set(EXTENSION_HOMEPAGE "https://www.slicer.org/slicerWiki/index.php/Documentation/Nightly/PercutaneousApproachAnalysis") set(EXTENSION_CATEGORY "IGT") -set(EXTENSION_CONTRIBUTORS "Koichiro Murakami (Shiga University of Medical Science, Japan, SPL), Laurent Chauvin (SPL), Junichi Tokuda (SPL)") +set(EXTENSION_CONTRIBUTORS "Koichiro Murakami (Shiga University of Medical Science, Japan, SPL), Atsushi Yamada (Shiga University of Medical Science, Japan), Laurent Chauvin (SPL), Junichi Tokuda (SPL)") set(EXTENSION_DESCRIPTION "The Percutaneous Approach Analysis is used to calculate and visualize the accessibility of liver tumor with a percutaneous approach.") -set(EXTENSION_ICONURL "http://www.slicer.org/slicerWiki/images/a/ac/PAAlogo-small.png") -set(EXTENSION_SCREENSHOTURLS "http://www.slicer.org/slicerWiki/images/4/42/Accessibility_clinical.png") +set(EXTENSION_ICONURL "https://www.slicer.org/slicerWiki/images/a/ac/PAAlogo-small.png") +set(EXTENSION_SCREENSHOTURLS "https://www.slicer.org/slicerWiki/images/4/42/Accessibility_clinical.png") #----------------------------------------------------------------------------- find_package(Slicer REQUIRED) diff --git a/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.py b/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.py index 2fa0d5f..17c739d 100644 --- a/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.py +++ b/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.py @@ -3,6 +3,7 @@ from __main__ import vtk, qt, ctk, slicer import time import math +import string class PercutaneousApproachAnalysis: @@ -11,10 +12,10 @@ def __init__(self, parent): parent.categories = ["IGT"] parent.dependencies = [] parent.contributors = ["Atsushi Yamada (Shiga University of Medical Science),Koichiro Murakami (Shiga University of Medical Science, Japan, SPL), Laurent Chauvin (SPL), Junichi Tokuda (SPL)"] - parent.helpText = string.Template(""" - The Percutaneous Approach Analysis is used to calculate and visualize the accessibility to liver tumor with a percutaneous approach. - See the online documentation to know how to use in detail. - """).substitute({ 'a':parent.slicerWikiUrl, 'b':slicer.app.majorVersion, 'c':slicer.app.minorVersion }) + # parent.helpText = string.Template(""" + # The Percutaneous Approach Analysis is used to calculate and visualize the accessibility to liver tumor with a percutaneous approach. + # See the online documentation to know how to use in detail. + # """).substitute({ 'a':parent.slicerWikiUrl, 'b':slicer.app.majorVersion, 'c':slicer.app.minorVersion }) parent.acknowledgementText = """ This work is supported by Bio-Medical Innovation Center and Department of Surgery, Shiga University of Medical Science in Japan. This work is also supported in part by the NIH (R01CA111288, P01CA067165, P41RR019703, P41EB015898, R01CA124377, R01CA138586, R42CA137886). @@ -50,6 +51,7 @@ def __init__(self, parent = None): if not parent: self.setup() self.parent.show() + self.logic = PercutaneousApproachAnalysisLogic() def setup(self): # Instantiate and connect widgets ... @@ -121,19 +123,6 @@ def setup(self): self.entryPointsSelector.setMRMLScene( slicer.mrmlScene ) parametersFormLayout.addRow("Output Fiducial List: ", self.entryPointsSelector) - # - # target model (vtkMRMLModelNode) - # - self.targetModelSelector = slicer.qMRMLNodeComboBox() - self.targetModelSelector.nodeTypes = ( ("vtkMRMLModelNode"), "" ) - self.targetModelSelector.addEnabled = False - self.targetModelSelector.removeEnabled = False - self.targetModelSelector.noneEnabled = True - self.targetModelSelector.showHidden = False - self.targetModelSelector.showChildNodeTypes = False - self.targetModelSelector.setMRMLScene( slicer.mrmlScene ) - self.targetModelSelector.setToolTip( "Pick the target model to the algorithm." ) - # # Skin model (vtkMRMLModelNode) # @@ -195,7 +184,6 @@ def setup(self): # connections self.applyButton.connect('clicked(bool)', self.onApplyButton) self.targetSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect) - self.targetModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect) self.obstacleModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect) self.skinModelSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect) self.entryPointsSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect) @@ -300,23 +288,23 @@ def setup(self): # # Maximum Length # - self.maximumLengthSpinBox = qt.QLineEdit() - self.maximumLengthSpinBox.enabled = True - self.maximumLengthSpinBox.maximumWidth = 70 - self.maximumLengthSpinBox.setReadOnly(True) - self.maximumLengthSpinBox.maxLength = 8 + self.maximumLengthEdit = qt.QLineEdit() + self.maximumLengthEdit.enabled = True + self.maximumLengthEdit.maximumWidth = 70 + self.maximumLengthEdit.setReadOnly(True) + self.maximumLengthEdit.maxLength = 8 # # Maximum Length Path # - self.maximumLengthPathSpinBox = qt.QLineEdit() - self.maximumLengthPathSpinBox.enabled = True - self.maximumLengthPathSpinBox.maximumWidth = 70 - self.maximumLengthPathSpinBox.setReadOnly(True) - self.maximumLengthPathSpinBox.maxLength = 8 + self.maximumLengthPathEdit = qt.QLineEdit() + self.maximumLengthPathEdit.enabled = True + self.maximumLengthPathEdit.maximumWidth = 70 + self.maximumLengthPathEdit.setReadOnly(True) + self.maximumLengthPathEdit.maxLength = 8 - outcomesFormLayout.addRow(" Path (No.): ", self.maximumLengthPathSpinBox) - outcomesFormLayout.addRow(" Length (mm): ", self.maximumLengthSpinBox) + outcomesFormLayout.addRow(" Path (No.): ", self.maximumLengthPathEdit) + outcomesFormLayout.addRow(" Length (mm): ", self.maximumLengthEdit) # # Check box for displaying each path @@ -328,23 +316,23 @@ def setup(self): # # Minimum Length # - self.minimumLengthSpinBox = qt.QLineEdit() - self.minimumLengthSpinBox.enabled = True - self.minimumLengthSpinBox.maximumWidth = 70 - self.minimumLengthSpinBox.setReadOnly(True) - self.minimumLengthSpinBox.maxLength = 8 + self.minimumLengthEdit = qt.QLineEdit() + self.minimumLengthEdit.enabled = True + self.minimumLengthEdit.maximumWidth = 70 + self.minimumLengthEdit.setReadOnly(True) + self.minimumLengthEdit.maxLength = 8 # # Minimum Length Path # - self.minimumLengthPathSpinBox = qt.QLineEdit() - self.minimumLengthPathSpinBox.enabled = True - self.minimumLengthPathSpinBox.maximumWidth = 70 - self.minimumLengthPathSpinBox.setReadOnly(True) - self.minimumLengthPathSpinBox.maxLength = 8 + self.minimumLengthPathEdit = qt.QLineEdit() + self.minimumLengthPathEdit.enabled = True + self.minimumLengthPathEdit.maximumWidth = 70 + self.minimumLengthPathEdit.setReadOnly(True) + self.minimumLengthPathEdit.maxLength = 8 - outcomesFormLayout.addRow(" Path (No.): ", self.minimumLengthPathSpinBox) - outcomesFormLayout.addRow(" Length (mm): ", self.minimumLengthSpinBox) + outcomesFormLayout.addRow(" Path (No.): ", self.minimumLengthPathEdit) + outcomesFormLayout.addRow(" Length (mm): ", self.minimumLengthEdit) # # Check box for displaying each path @@ -362,10 +350,11 @@ def setup(self): outcomesFormLayout.addRow(" Path Candidate (No.):", self.pathSlider) # Point slider + self.pointSliderLength = 5000 self.pointSlider = ctk.ctkSliderWidget() self.pointSlider.decimals = 0 - self.pointSlider.maximum = 5000 - self.pointSlider.minimum = -5000 + self.pointSlider.maximum = self.pointSliderLength + self.pointSlider.minimum = -self.pointSliderLength self.pointSlider.enabled = False outcomesFormLayout.addRow(" Point Candidate on the Path:", self.pointSlider) @@ -389,7 +378,7 @@ def setup(self): self.allPathsCheckBox.connect("clicked(bool)", self.onCheckAllPaths) self.colorMapCheckBox.connect("clicked(bool)", self.onCheckColorMappedSkin) self.createPointOnThePathButton.connect('clicked(bool)', self.onCreatePointOnThePathButton) - self.pathSlider.connect('valueChanged(double)', self.pathSliderValueChanged) + self.pathSlider.connect('valueChanged(double)', self.selectedPathIndexChanged) self.pointSlider.connect('valueChanged(double)', self.pointSliderValueChanged) self.allPathsOpacitySlider.connect('valueChanged(double)', self.allPathsOpacitySliderValueChanged) @@ -417,75 +406,25 @@ def setup(self): self.deleteModelsButton.enabled = False cleaningFormLayout.addWidget(self.deleteModelsButton) - # - # Delete Transforms Button - # - self.deleteTransformsButton = qt.QPushButton("Delete the Created Transforms") - self.deleteTransformsButton.toolTip = "Delete Created Transforms" - self.deleteTransformsButton.enabled = False - cleaningFormLayout.addWidget(self.deleteTransformsButton) - self.deleteModelsButton.connect('clicked()', self.onDeleteModelsButton) - self.deleteTransformsButton.connect('clicked()', self.onDeleteTransformsButton) # Add vertical spacer self.layout.addStretch(1) - # Switch to distinguish between a point target and a target model - self.targetSwitch = 0 - - # Create an array for all approachable points - # tempolary solution - self.apReceived = numpy.zeros([10000,3]) - - self.nPointsReceived = 0 - self.nPathReceived = 0 - self.frameSliderValue = 0 - - self.pathSliderValue = 0 - self.pointSliderValue = 0 - - # avoid initializing error for frameSliderValueChanged(self, newValue) function - self.tmpSwitch = 0 - - self.pointMarker = slicer.vtkMRMLModelDisplayNode() - self.pointMarkerTransform = slicer.vtkMRMLLinearTransformNode() - self.virtualMarker = slicer.vtkMRMLModelDisplayNode() - self.virtualMarkerTransform = slicer.vtkMRMLLinearTransformNode() - - self.allPaths = slicer.vtkMRMLModelDisplayNode() - self.candidatePath = slicer.vtkMRMLModelDisplayNode() - - self.pathReceived = numpy.zeros([10000,3]) - - self.markerPosition = numpy.zeros([3]) - self.virtualMarkerPosition = numpy.zeros([3]) - - self.onePathDistance = 0 - self.virtualPathDistance = 0 - - self.distanceDummy = 0; - - # switch - self.ON = 1 - self.OFF = 0 - self.VISIBLE = 1 - self.INVISIBLE = 0 + self.selectedPathIndex = 0 + self.positionAlongSelectedPath = 0 # model variables - self.modelReceived = slicer.vtkMRMLModelNode() - self.singlePathModel = slicer.vtkMRMLModelNode() - self.virtualPathModel = slicer.vtkMRMLModelNode() - self.selectedPathTipModel = slicer.vtkMRMLModelNode() - self.extendedPathTipModel = slicer.vtkMRMLModelNode() - self.longestPathTipModel = slicer.vtkMRMLModelNode() - self.shortestPathTipModel = slicer.vtkMRMLModelNode() - self.plannedEntryPointModel = slicer.vtkMRMLModelNode() - - self.minimumPoint = 0 - self.maximumPoint = 0 - self.minimumDistance = 0 - self.maximumDistance = 0 + self.allPathsModel = None + self.selectedPathModel = None + self.selectedPathTipModel = None + self.extendedPathModel = None + self.extendedPathTipModel = None + self.longestPathModel = None + self.longestPathTipModel = None + self.shortestPathModel = None + self.shortestPathTipModel = None + self.plannedEntryPointModels = [] # line colors self.yellow = [1, 1, 0] @@ -493,67 +432,54 @@ def setup(self): self.green = [0, 1, 0] self.blue = [0, 0, 1] - # line models - self.singlePathPoints = vtk.vtkPoints() - self.allPathsPoints = vtk.vtkPoints() - self.virtualPathPoints = vtk.vtkPoints() - self.allLines = vtk.vtkPolyData() - self.singleLine = vtk.vtkPolyData() - self.extendedLine = vtk.vtkPolyData() - self.longestLine = vtk.vtkPolyData() - self.shortestLine = vtk.vtkPolyData() - def cleanup(self): pass - def pathSliderValueChanged(self,newValue): - logic = PercutaneousApproachAnalysisLogic() - self.pathSliderValue = newValue - - self.onePath, self.onePathDistance = logic.makeSinglePath(self.apReceived, self.pathSliderValue) - NeedlePathModel().modify(self.onePath, 1, self.VISIBLE, self.red, "pathCandidate", self.singleLine, self.singlePathModel, self.singlePath, self.singlePathPoints) + def selectedPathIndexChanged(self,newValue): + + self.selectedPathIndex = int(newValue) + onePath = self.logic.makeSinglePath(self.selectedPathIndex) + self.logic.updatePathModel(self.selectedPathModel, onePath) + self.logic.setSpherePositionAlongPath(self.selectedPathTipModel, self.selectedPathIndex, 0) - self.markerPosition = SphereModel().move(self.apReceived, self.pathSliderValue, 0, self.pointMarkerTransform) - self.virtualMarkerPosition = SphereModel().move(self.apReceived, self.pathSliderValue, self.pointSliderValue, self.virtualMarkerTransform) - - self.virtualPath, self.virtualPathDistance = logic.makeVirtualPath(self.apReceived, self.pathSliderValue, self.virtualMarkerPosition) - NeedlePathModel().modify(self.virtualPath, 1, self.VISIBLE, self.red, "extendedPath", self.extendedLine, self.virtualPathModel, self.virtualPath, self.virtualPathPoints) - - self.lengthOfPathSpinBox.text = round(self.virtualPathDistance + self.onePathDistance,1) + self.pointSliderValueChanged(self.pointSlider.value) def pointSliderValueChanged(self,newValue): - logic = PercutaneousApproachAnalysisLogic() - self.pointSliderValue = newValue + self.positionAlongSelectedPath = newValue / self.pointSliderLength - self.virtualMarkerPosition = SphereModel().move(self.apReceived, self.pathSliderValue, self.pointSliderValue, self.virtualMarkerTransform) - self.virtualPath, self.virtualPathDistance = logic.makeVirtualPath(self.apReceived, self.pathSliderValue, self.virtualMarkerPosition) - NeedlePathModel().modify(self.virtualPath, 1, self.VISIBLE, self.red, "extendedPath", self.extendedLine, self.virtualPathModel, self.virtualPath, self.virtualPathPoints) + self.virtualMarkerPosition = self.logic.setSpherePositionAlongPath(self.extendedPathTipModel, self.selectedPathIndex, self.positionAlongSelectedPath) + virtualPath = self.logic.makeVirtualPath(self.selectedPathIndex, self.virtualMarkerPosition) + self.logic.updatePathModel(self.extendedPathModel, virtualPath) - self.lengthOfPathSpinBox.text = round(self.virtualPathDistance,1) + #import numpy as np + #targetPointPosition = self.logic.paths[self.selectedPathIndex][0] + #skinPointPosition = self.virtualMarkerPosition + #virtualPathLength = np.linalg.norm(skinPointPosition - targetPointPosition) + #self.lengthOfPathSpinBox.text = round(virtualPathLength,1) + self.lengthOfPathSpinBox.text = round(PercutaneousApproachAnalysisLogic.pathLength(virtualPath), 1) def skinModelOpacitySliderValueChanged(self,newValue): - if(self.skinModelSelector.currentNode() != None): - skinModel = self.skinModelSelector.currentNode() - modelDisplay = skinModel.GetDisplayNode() - modelDisplay.SetOpacity(newValue/1000.0) - self.skinModelOpacitySlider.value = newValue - self.coloredSkinModelOpacitySlider.value = newValue + if not self.skinModelSelector.currentNode(): + return + skinModel = self.skinModelSelector.currentNode() + modelDisplay = skinModel.GetDisplayNode() + modelDisplay.SetOpacity(newValue/1000.0) + self.skinModelOpacitySlider.value = newValue + self.coloredSkinModelOpacitySlider.value = newValue def obstacleModelOpacitySliderValueChanged(self,newValue): - if(self.obstacleModelSelector.currentNode() != None): - obstacleModel = self.obstacleModelSelector.currentNode() - modelDisplay = obstacleModel.GetDisplayNode() - modelDisplay.SetOpacity(newValue/1000.0) + if not self.obstacleModelSelector.currentNode(): + return + obstacleModel = self.obstacleModelSelector.currentNode() + modelDisplay = obstacleModel.GetDisplayNode() + modelDisplay.SetOpacity(newValue/1000.0) def allPathsOpacitySliderValueChanged(self,newValue): - self.allPaths.SetOpacity(newValue/1000.0) + self.allPathsMode.GetDisplayNode().SetOpacity(newValue/1000.0) def onSelect(self): if (self.targetSelector.currentNode() != None) and (self.obstacleModelSelector.currentNode() != None) and (self.skinModelSelector.currentNode() != None): self.applyButton.enabled = True - if (self.targetModelSelector.currentNode() != None) and (self.obstacleModelSelector.currentNode() != None) and (self.skinModelSelector.currentNode() != None): - self.applyButton.enabled = True - self.targetSwitch = 1 self.skinModelOpacitySliderValueChanged(self.skinModelOpacitySlider.value) self.obstacleModelOpacitySliderValueChanged(self.obstacleModelOpacitySlider.value) @@ -565,129 +491,98 @@ def onCreatePointOnThePathButton(self): entryPointsNode.SetNthFiducialLabel(n, "SkinEntryPoint") entryPointsNode.SetNthFiducialVisibility(n,1) - self.plannedEntryPointModel, droppedMarker, droppedMarkerTransform = SphereModel().make(self.VISIBLE, self.red, "plannedEntryPoint") - SphereModel().drop(self.virtualMarkerPosition[0], self.virtualMarkerPosition[1], self.virtualMarkerPosition[2], droppedMarkerTransform) + plannedEntryPointModel = self.logic.createSphereModel("plannedEntryPoint", self.red) + PercutaneousApproachAnalysisLogic.setSpherePosition(plannedEntryPointModel, self.virtualMarkerPosition) + self.plannedEntryPointModels.append(plannedEntryPointModel) def onCheckAllPaths(self): - if self.allPathsCheckBox.checked == True: - self.allPaths.SetVisibility(self.ON) - self.allPathsOpacitySlider.enabled = True - else: - self.allPaths.SetVisibility(self.OFF) - self.allPathsOpacitySlider.enabled = False + visible = self.allPathsCheckBox.checked + self.allPathsModel.GetDisplayNode().SetVisibility(visible) + self.allPathsOpacitySlider.enabled = visible def onCheckColorMappedSkin(self): skinModel = self.skinModelSelector.currentNode() modelDisplay = skinModel.GetDisplayNode() - scalarSetting = slicer.modulewidget.qMRMLModelDisplayNodeWidget() - scalarSetting.setMRMLModelDisplayNode(modelDisplay) displayNode = skinModel.GetModelDisplayNode() - displayNode.SetActiveScalarName("Normals") + #displayNode.SetActiveScalar('Normals', vtk.vtkAssignAttribute.POINT_DATA) - visible = 1 - invisible = 0 - - if self.colorMapCheckBox.checked == True: - scalarSetting.setScalarsVisibility(visible) + if self.colorMapCheckBox.checked: + #displayNode.SetScalarVisibility(True) # Need to reload the skin model after "displayNode.SetActiveScalarName("Normals")" # to display color map correctly - displayNode.SetActiveScalarName("Colors") - scalarSetting.setScalarsVisibility(visible) - + #displayNode.SetActiveScalar('Colors', vtk.vtkAssignAttribute.CELL_DATA) + displayNode.SetActiveScalar("Accessibility", vtk.vtkAssignAttribute.POINT_DATA) + displayNode.SetScalarVisibility(True) + self.coloredSkinModelOpacitySlider.enabled = True else: - scalarSetting.setScalarsVisibility(invisible) + displayNode.SetScalarVisibility(False) self.coloredSkinModelOpacitySlider.enabled = False def onCheckTheLongestPath(self): - print(self.maximumPoint-1) - if self.maximumLengthPathCheckBox.checked == True: - self.theLongestPath.SetVisibility(self.ON) - self.theLongestPathPointMarker.SetVisibility(self.ON) - else: - self.theLongestPath.SetVisibility(self.OFF) - self.theLongestPathPointMarker.SetVisibility(self.OFF) + visible = self.maximumLengthPathCheckBox.checked + self.longestPathModel.GetDisplayNode().SetVisibility(visible) + self.longestPathTipModel.GetDisplayNode().SetVisibility(visible) def onCheckTheShortestPath(self): - print(self.minimumPoint-1) - if self.minimumLengthPathCheckBox.checked == True: - self.theShortestPath.SetVisibility(self.ON) - self.theShortestPathPointMarker.SetVisibility(self.ON) - else: - self.theShortestPath.SetVisibility(self.OFF) - self.theShortestPathPointMarker.SetVisibility(self.OFF) + visible = self.minimumLengthPathCheckBox.checked + self.shortestPathModel.GetDisplayNode().SetVisibility(visible) + self.shortestPathTipModel.GetDisplayNode().SetVisibility(visible) def onCheckPathCandidate(self): - if self.pathCandidateCheckBox.checked == True: - self.singlePath.SetVisibility(self.ON) - self.pointMarker.SetVisibility(self.ON) - self.virtualPath2.SetVisibility(self.ON) - self.virtualMarker.SetVisibility(self.ON) - self.pointSlider.enabled = True - self.pathSlider.enabled = True - self.createPointOnThePathButton.enabled = True - else: - self.singlePath.SetVisibility(self.OFF) - self.pointMarker.SetVisibility(self.OFF) - self.virtualPath2.SetVisibility(self.OFF) - self.virtualMarker.SetVisibility(self.OFF) - self.pointSlider.enabled = False - self.pathSlider.enabled = False - self.createPointOnThePathButton.enabled = False - + show = self.pathCandidateCheckBox.checked + self.selectedPathModel.GetDisplayNode().SetVisibility(show) + self.selectedPathTipModel.GetDisplayNode().SetVisibility(show) + self.extendedPathModel.GetDisplayNode().SetVisibility(show) + self.extendedPathTipModel.GetDisplayNode().SetVisibility(show) + self.pointSlider.enabled = show + self.pathSlider.enabled = show + self.createPointOnThePathButton.enabled = show + def onApplyButton(self): - logic = PercutaneousApproachAnalysisLogic() print("onApplyButton() is called ") targetPoint = self.targetSelector.currentNode() - targetModel = self.targetModelSelector.currentNode() obstacleModel = self.obstacleModelSelector.currentNode() skinModel = self.skinModelSelector.currentNode() # clean up work space self.onDeleteModelsButton() - self.onDeleteTransformsButton() - # make all paths - self.pathReceived, self.nPathReceived, self.apReceived, self.minimumPoint, self.minimumDistance, self.maximumPoint, self.maximumDistance = logic.makePaths(targetPoint, targetModel, 0, obstacleModel, skinModel) - # display all paths model - self.modelReceived, pReceived, self.allPaths, self.allPathPoints = NeedlePathModel().make(self.pathReceived, self.nPathReceived, self.VISIBLE, self.yellow, "candidatePaths", self.allLines) + # create model to show all paths + self.logic.computePaths(targetPoint, obstacleModel, skinModel) + self.allPathsModel = PercutaneousApproachAnalysisLogic.createPathsModel(self.logic.paths, "candidatePaths", self.yellow) # display sphere model - self.selectedPathTipModel, self.pointMarker, self.pointMarkerTransform = SphereModel().make(self.INVISIBLE, self.red, "selectedPathTip") - self.markerPosition = SphereModel().move(self.apReceived, self.pathSliderValue, self.pointSliderValue, self.pointMarkerTransform) - self.extendedPathTipModel, self.virtualMarker, self.virtualMarkerTransform = SphereModel().make(self.INVISIBLE, self.red, "extendedPathTip") - self.virtualMarkerPosition = SphereModel().move(self.apReceived, self.pathSliderValue, self.pointSliderValue, self.virtualMarkerTransform) + self.selectedPathTipModel = self.logic.createSphereModel("selectedPathTip", self.red, False) + self.logic.setSpherePositionAlongPath(self.selectedPathTipModel, self.selectedPathIndex, self.positionAlongSelectedPath) + self.extendedPathTipModel = self.logic.createSphereModel("extendedPathTip", self.red, False) + self.logic.setSpherePositionAlongPath(self.extendedPathTipModel, self.selectedPathIndex, self.positionAlongSelectedPath) # make and display single path candidate - self.onePath, self.onePathDistance = logic.makeSinglePath(self.apReceived, self.pathSliderValue) - self.singlePathModel, self.singleP, self.singlePath, self.singlePathPoints = NeedlePathModel().make(self.onePath, 2, self.INVISIBLE, self.red, "selectedPath", self.singleLine) - # make and display virtual path candidate - self.virtualPath, self.virtualPathDistance = logic.makeVirtualPath(self.apReceived, self.pathSliderValue, self.virtualMarkerPosition) - self.virtualPathModel, self.virtualP, self.virtualPath2, self.virtualPathPoints = NeedlePathModel().make(self.virtualPath, 2, self.INVISIBLE, self.red, "extendedPath", self.extendedLine) - - self.lengthOfPathSpinBox.text = round(self.virtualPathDistance,1) + selectedPath = self.logic.makeSinglePath(self.selectedPathIndex) + self.selectedPathModel = self.logic.createPathsModel(selectedPath, "selectedPath", self.red, False) + # make and display virtual path candidate + virtualPath = self.logic.makeSinglePath(self.selectedPathIndex) + self.extendedPathModel = self.logic.createPathsModel(virtualPath, "extendedPath", self.red, False) + self.lengthOfPathSpinBox.text = round(PercutaneousApproachAnalysisLogic.pathLength(virtualPath), 1) # make the longest path - self.theLongestPathTmp, self.distanceDummy = logic.makeSinglePath(self.apReceived, self.maximumPoint-1) - # display the longest path - self.theLongestPathModel, self.theLongestPathP, self.theLongestPath, self.points = NeedlePathModel().make(self.theLongestPathTmp, 2, self.INVISIBLE, self.green, "longestPath", self.longestLine) - # make the point marker on the longest path - self.longestPathTipModel, self.theLongestPathPointMarker, self.theLongestPathPointMarkerTransform = SphereModel().make(self.INVISIBLE, self.green, "longestPathTip") - self.theLongestPathPointMarkerPosition = SphereModel().move(self.apReceived, self.maximumPoint-1, 0, self.theLongestPathPointMarkerTransform) + longestPath = self.logic.makeSinglePath(self.logic.longestPathIndex) + self.longestPathModel = self.logic.createPathsModel(longestPath, "longestPath", self.green, False) + self.longestPathTipModel = self.logic.createSphereModel("longestPathTip", self.green, False) + self.logic.setSpherePositionAlongPath(self.longestPathTipModel, self.logic.longestPathIndex, 0) # make the shortest path - self.theShortestPathTmp, self.distanceDummy = logic.makeSinglePath(self.apReceived, self.minimumPoint-1) - # display the shortest path - self.theShortestPathModel, self.theShortestPathP, self.theShortestPath, self.points = NeedlePathModel().make(self.theShortestPathTmp, 2, self.INVISIBLE, self.blue,"shortestPath", self.shortestLine) - # make the point marker on the shortest path - self.shortestPathTipModel, self.theShortestPathPointMarker, self.theShortestPathPointMarkerTransform = SphereModel().make(self.INVISIBLE, self.blue, "shortestPathTip") - self.theShortestPathPointMarkerPosition = SphereModel().move(self.apReceived, self.minimumPoint-1, 0, self.theShortestPathPointMarkerTransform) + shortestPath = self.logic.makeSinglePath(self.logic.shortestPathIndex) + self.shortestPathModel = self.logic.createPathsModel(shortestPath, "shortestPath", self.blue, False) + self.shortestPathTipModel = self.logic.createSphereModel("shortestPathTip", self.blue, False) + self.logic.setSpherePositionAlongPath(self.shortestPathTipModel, self.logic.shortestPathIndex, 0) # update outcomes - self.numbersOfAllpathsSpinBox.text = self.nPathReceived - self.pathSlider.maximum = self.nPathReceived-1 + self.numbersOfAllpathsSpinBox.text = len(self.logic.paths) + self.pathSlider.maximum = len(self.logic.paths) - 1 self.allPathsCheckBox.checked = True self.allPathsCheckBox.enabled = True self.pathCandidateCheckBox.enabled = True @@ -695,24 +590,24 @@ def onApplyButton(self): self.cleaningList.collapsed = False self.allPathsOpacitySlider.enabled = True self.deleteModelsButton.enabled = True - self.deleteTransformsButton.enabled = True - self.allPaths.SetOpacity(6.0/1000.0) + self.allPathsModel.GetDisplayNode().SetOpacity(0.01) self.skinModelOpacitySliderValueChanged(900.0) self.skinModelOpacitySlider.value = 900 # calculate the length of the Path No.1 and display the length - self.pathSliderValueChanged(0) + self.selectedPathIndexChanged(0) - #self.maximumLengthSpinBox.value = self.maximumDistance - self.maximumLengthSpinBox.text = round(self.maximumDistance,1) - self.maximumLengthPathSpinBox.text = self.maximumPoint-1 - self.minimumLengthSpinBox.text = round(self.minimumDistance,1) - self.minimumLengthPathSpinBox.text = self.minimumPoint-1 + self.minimumLengthEdit.text = round(PercutaneousApproachAnalysisLogic.pathLength(self.logic.paths, self.logic.shortestPathIndex),1) + self.minimumLengthPathEdit.text = self.logic.shortestPathIndex + self.maximumLengthEdit.text = round(PercutaneousApproachAnalysisLogic.pathLength(self.logic.paths, self.logic.longestPathIndex),1) + self.maximumLengthPathEdit.text = self.logic.longestPathIndex # make color mapped skin start = time.time() - (score, mind, mindp) = logic.runPointWise(targetPoint, obstacleModel, skinModel) + (score, minimumDistance, minimumDistancePosition) = self.logic.calcApproachScore(targetPoint, obstacleModel, skinModel) + print(f"Accessible Area = {score}") + print(f"Minimum Distance = {minimumDistance}") self.accessibilityScore.text = round(score,1) @@ -771,56 +666,52 @@ def onReloadAndTest(self,moduleName="PercutaneousApproachAnalysis"): evalString = 'globals()["%s"].%sTest()' % (moduleName, moduleName) tester = eval(evalString) tester.runTest() - except Exception, e: + 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") def onDeleteModelsButton(self): - if self.deleteModelsButton.enabled == True: - # reset all status - self.deleteModelsButton.enabled = False - self.allPathsCheckBox.checked = True - self.allPathsCheckBox.enabled = False - self.colorMapCheckBox.checked = False - - self.onCheckColorMappedSkin() - self.colorMapCheckBox.checked = True - self.colorMapCheckBox.enabled = False - self.pathCandidateCheckBox.checked = False - self.pathCandidateCheckBox.enabled = False - self.pathSlider.enabled = False - self.pointSlider.enabled = False - self.createPointOnThePathButton.enabled = False - self.maximumLengthPathCheckBox.checked = False - self.maximumLengthPathCheckBox.enabled = False - self.minimumLengthPathCheckBox.checked = False - self.minimumLengthPathCheckBox.enabled = False - self.allPathsOpacitySlider.enabled = False - - # delete all models - logic = PercutaneousApproachAnalysisLogic() - logic.removeModel(self.modelReceived) - logic.removeModel(self.singlePathModel) - logic.removeModel(self.virtualPathModel) - logic.removeModel(self.theLongestPathModel) - logic.removeModel(self.theShortestPathModel) - # - logic.removeModel(self.selectedPathTipModel) - logic.removeModel(self.extendedPathTipModel) - logic.removeModel(self.longestPathTipModel) - logic.removeModel(self.shortestPathTipModel) - - def onDeleteTransformsButton(self): - if self.deleteTransformsButton.enabled == True: - self.deleteTransformsButton.enabled = False - # delete all transforms - logic = PercutaneousApproachAnalysisLogic() - logic.removeTransform(self.pointMarkerTransform) - logic.removeTransform(self.virtualMarkerTransform) - logic.removeTransform(self.theLongestPathPointMarkerTransform) - logic.removeTransform(self.theShortestPathPointMarkerTransform) + if not self.deleteModelsButton.enabled: + return + + # reset all status + self.deleteModelsButton.enabled = False + self.allPathsCheckBox.checked = True + self.allPathsCheckBox.enabled = False + self.colorMapCheckBox.checked = False + + self.onCheckColorMappedSkin() + self.colorMapCheckBox.checked = True + self.colorMapCheckBox.enabled = False + self.pathCandidateCheckBox.checked = False + self.pathCandidateCheckBox.enabled = False + self.pathSlider.enabled = False + self.pointSlider.enabled = False + self.createPointOnThePathButton.enabled = False + self.maximumLengthPathCheckBox.checked = False + self.maximumLengthPathCheckBox.enabled = False + self.minimumLengthPathCheckBox.checked = False + self.minimumLengthPathCheckBox.enabled = False + self.allPathsOpacitySlider.enabled = False + + # delete all transforms + self.logic.removeTransform(self.selectedPathTipModel) + self.logic.removeTransform(self.extendedPathTipModel) + self.logic.removeTransform(self.longestPathTipModel) + self.logic.removeTransform(self.shortestPathTipModel) + + # delete all models + self.logic.removeModel(self.allPathsModel) + self.logic.removeModel(self.selectedPathModel) + self.logic.removeModel(self.selectedPathTipModel) + self.logic.removeModel(self.extendedPathModel) + self.logic.removeModel(self.extendedPathTipModel) + self.logic.removeModel(self.shortestPathModel) + self.logic.removeModel(self.longestPathTipModel) + self.logic.removeModel(self.longestPathModel) + self.logic.removeModel(self.shortestPathTipModel) # # PercutaneousApproachAnalysisLogic @@ -834,20 +725,10 @@ class PercutaneousApproachAnalysisLogic: requiring an instance of the Widget """ def __init__(self): - pass - - def hasImageData(self,volumeNode): - """This is a dummy logic method that - returns true if the passed in volume - node has valid image data - """ - if not volumeNode: - print('no volume node') - return False - if volumeNode.GetImageData() == None: - print('no image data') - return False - return True + # List of [targetPoint, skinPoint] pairs + self.paths = [] + self.shortestPathIndex = -1 + self.longestPathIndex = -1 def delayDisplay(self,message,msec=1000): # @@ -864,98 +745,80 @@ def delayDisplay(self,message,msec=1000): def removeModel(self, model): scene = slicer.mrmlScene - scene.RemoveNode(model) + scene.RemoveNode(model) - def removeTransform(self, transform): - scene = slicer.mrmlScene - scene.RemoveNode(transform) - - def makeSinglePath(self, p, pointNumber): - import numpy - - tipPoint = numpy.zeros([2,3]) + def removeTransform(self, transformedNode): + slicer.mrmlScene.RemoveNode(transformedNode.GetParentTransformNode()) - targetP = [p[pointNumber*2][0], p[pointNumber*2][1], p[pointNumber*2][2]] - skinP = [p[pointNumber*2+1][0], p[pointNumber*2+1][1], p[pointNumber*2+1][2]] + @staticmethod + def pathLength(paths, pathIndex=0): + import numpy as np + [targetPoint, skinPoint] = paths[pathIndex] + pathLength = np.linalg.norm(targetPoint - skinPoint) + return pathLength - tipPoint[0] = targetP - tipPoint[1] = skinP + @staticmethod + def updatePathModel(modelNode, paths): + outputPolyData = vtk.vtkPolyData() - onePath = [targetP] - onePath.append(skinP) + points = vtk.vtkPoints() + outputPolyData.SetPoints(points) - distance = numpy.sqrt(numpy.power(p[pointNumber*2]-p[pointNumber*2+1],2).sum()) + lines = vtk.vtkCellArray() + outputPolyData.SetLines(lines) - self.tmpSwitch = 1 + for targetPoint, skinPoint in paths: + pointIndex1 = points.InsertNextPoint(targetPoint) + pointIndex2 = points.InsertNextPoint(skinPoint) + lines.InsertNextCell(2) + lines.InsertCellPoint(pointIndex1) + lines.InsertCellPoint(pointIndex2) - return (onePath, distance) + modelNode.SetAndObservePolyData(outputPolyData) - def makeVirtualPath(self, p, pointNumber, virtualPosition): - import numpy + @staticmethod + def createPathsModel(paths, modelName, color, visibility=True): - tipPoint = numpy.zeros([2,3]) + # Create model node + model = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", slicer.mrmlScene.GenerateUniqueName(modelName)) - targetP = [virtualPosition[0], virtualPosition[1], virtualPosition[2]] - skinP = [p[pointNumber*2+1][0], p[pointNumber*2+1][1], p[pointNumber*2+1][2]] + # Create display node + model.CreateDefaultDisplayNodes() + modelDisplay = model.GetDisplayNode() + modelDisplay.SetColor(color[0], color[1], color[2]) + modelDisplay.SetVisibility(visibility) + modelDisplay.SetSliceIntersectionVisibility(True) # Show in slice view - tipPoint[0] = targetP - tipPoint[1] = skinP + PercutaneousApproachAnalysisLogic.updatePathModel(model, paths) - onePath = [targetP] - onePath.append(skinP) + return model - distance = numpy.sqrt(numpy.power(p[pointNumber*2+1]-virtualPosition,2).sum()) + def makeSinglePath(self, pathIndex): + return [self.paths[pathIndex]] - return (onePath, distance) + def makeVirtualPath(self, pathIndex, virtualPosition): + targetP = self.paths[pathIndex][0] + skinP = virtualPosition + return [[targetP, skinP]] - def makePaths(self, targetPointNode, targetModelNode, targetSwitch, obstacleModelNode, skinModelNode): - """ - Run the actual algorithm + def computePaths(self, targetPointNode, obstacleModelNode, skinModelNode): + """ Compute self.paths """ - print ('makePaths() is called') + import numpy as np - import numpy - - # The variable nPoints represents numbers of polygons for skin model - poly = skinModelNode.GetPolyData() - polyDataNormals = vtk.vtkPolyDataNormals() - - if vtk.VTK_MAJOR_VERSION <= 5: - polyDataNormals.SetInput(poly) - else: - polyDataNormals.SetInputData(poly) + targetPoint = np.array(targetPointNode.GetNthControlPointPositionWorld(0)) + skinPolyData = skinModelNode.GetPolyData() + polyDataNormals = vtk.vtkPolyDataNormals() + polyDataNormals.SetInputData(skinPolyData) polyDataNormals.Update() - polyData = polyDataNormals.GetOutput() - nPoints = polyData.GetNumberOfPoints() - nPoints2 = nPoints*2 + skinPolyData = polyDataNormals.GetOutput() + nSkinPoints = skinPolyData.GetNumberOfPoints() - # The variable nPointsT represents numbers of polygons for target model - if targetSwitch == 1: - polyT = targetModelNode.GetPolyData() - polyTDataNormals = vtk.vtkPolyDataNormals() - - if vtk.VTK_MAJOR_VERSION <= 5: - polyTDataNormals.SetInput(polyT) - else: - polyTDataNormals.SetInputData(polyT) + # Create an array for needle passing points. + # Each element contains a pair of target point and skin point + self.paths = [] - polyTDataNormals.Update() - polyTData = polyTDataNormals.GetOutput() - nPointsT = polyTData.GetNumberOfPoints() - nPointsT2 = nPointsT*2 - p2 = [0.0, 0.0, 0.0] - else: - nPointsT = 1 - nPointsT2 = nPointsT*2 - tPoint = targetPointNode.GetMarkupPointVector(0, 0) - p2 = [tPoint[0], tPoint[1], tPoint[2]] - - # The variable approachablePoints represents number of approachable polygons on the skin model - approachablePoints = 0 - - p1=[0.0, 0.0, 0.0] - tolerance = 0.001 t = vtk.mutable(0.0) x = [0.0, 0.0, 0.0] # The coordinate of the intersection @@ -966,90 +829,91 @@ def makePaths(self, targetPointNode, targetModelNode, targetSwitch, obstacleMode bspTree.SetDataSet(obstacleModelNode.GetPolyData()) bspTree.BuildLocator() - maximumDistance = 0.0 - minimumDistance = 1000.0 - distance = 0.0 - maximumPoint = 0 - minimumPoint = 0 - - # Create an array for needle passing points - self.p = numpy.zeros([nPointsT*nPoints2,3]) - - for indexT in range(0, nPointsT): - - p2 = [tPoint[0], tPoint[1], tPoint[2]] - - for index in range(0, nPoints): - polyData.GetPoint(index, p1) - iD = bspTree.IntersectWithLine(p1, p2, tolerance, t, x, pcoords, subId) - - if iD == 0: - coord1 = [p2[0],p2[1],p2[2]] - self.p[approachablePoints*2] = coord1 - coord2 = [p1[0],p1[1],p1[2]] - self.p[approachablePoints*2+1] = coord2 - approachablePoints = approachablePoints + 1 - - # - # Create length calculation algorithm - # - coord1Array = numpy.array(coord1) - coord2Array = numpy.array(coord2) - - distance = numpy.sqrt(numpy.power(coord1Array-coord2Array,2).sum()) - if distance >= maximumDistance: - maximumDistance = distance - maximumPoint = approachablePoints - if distance <= minimumDistance: - minimumDistance = distance - minimumPoint = approachablePoints - - # Create the list for needle passing points to draw virtual ray - self.path = [self.p[0]] - for index in range(1, approachablePoints*2): - self.path.append(self.p[index]) - - # Create an array for all approachable points - pReceived = numpy.zeros([approachablePoints,3]) - - return (self.path, approachablePoints, self.p, minimumPoint, minimumDistance, maximumPoint, maximumDistance) - - def runPointWise(self, targetPointNode, obstacleModelNode, skinModelNode): - """ - Run point-wise analysis - """ - print ("runPointWise()") - tPoint = targetPointNode.GetMarkupPointVector(0, 0) - pTarget = [tPoint[0], tPoint[1], tPoint[2]] - poly = skinModelNode.GetPolyData() - polyDataNormals = vtk.vtkPolyDataNormals() + self.shortestPathIndex = -1 + shortestPathLength = 1e6 + self.longestPathIndex = -1 + longestPathLength = 0.0 + + for index in range(nSkinPoints): + skinPoint = np.array(skinPolyData.GetPoint(index)) + intersect = bspTree.IntersectWithLine(skinPoint, targetPoint, tolerance, t, x, pcoords, subId) + if intersect: + # This path intersects the obstacle, the target is not approachable from here + continue + self.paths.append([targetPoint.copy(), skinPoint.copy()]) + pathLength = np.linalg.norm(targetPoint-skinPoint) + if pathLength < shortestPathLength: + shortestPathLength = pathLength + self.shortestPathIndex = len(self.paths) - 1 + if pathLength > longestPathLength: + longestPathLength = pathLength + self.longestPathIndex = len(self.paths) - 1 + + return self.paths + + @staticmethod + def createSphereModel(modelName, color, visibility=True): + sphere = vtk.vtkSphereSource() + sphere.SetRadius(2.5) + sphere.SetPhiResolution(100) + sphere.SetThetaResolution(100) + sphere.Update() - if vtk.VTK_MAJOR_VERSION <= 5: - polyDataNormals.SetInput(poly) - else: - polyDataNormals.SetInputData(poly) + # Create model node + sphereCursor = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLModelNode", slicer.mrmlScene.GenerateUniqueName(modelName)) + sphereCursor.SetAndObservePolyData(sphere.GetOutput()) + + # Create display node + sphereCursor.CreateDefaultDisplayNodes() + cursorModelDisplay = sphereCursor.GetDisplayNode() + cursorModelDisplay.SetColor(color[0], color[1], color[2]) + cursorModelDisplay.SetOpacity(0.3) + cursorModelDisplay.SetVisibility(visibility) + cursorModelDisplay.SetSliceIntersectionVisibility(True) # Show in slice view + + # Create transform node + transform = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLLinearTransformNode", slicer.mrmlScene.GenerateUniqueName(modelName + "Transform")) + sphereCursor.SetAndObserveTransformNodeID(transform.GetID()) + + return sphereCursor + def setSpherePositionAlongPath(self, sphereNode, pathIndex, normalizedPosition): + """normalizedPosition is position along the path, value is between 0.0-1.0 + """ + startPos, endPos = self.paths[pathIndex] + position = endPos + (startPos-endPos) * normalizedPosition + PercutaneousApproachAnalysisLogic.setSpherePosition(sphereNode, position) + return position + + @staticmethod + def setSpherePosition(sphereNode, position): + transformToParent = vtk.vtkMatrix4x4() + transformToParent.SetElement(0, 3, position[0]) + transformToParent.SetElement(1, 3, position[1]) + transformToParent.SetElement(2, 3, position[2]) + sphereNode.GetParentTransformNode().SetMatrixTransformToParent(transformToParent) + + def calcApproachScore(self, targetPointNode, obstacleModelNode, skinModelNode): + # skin model point scalar = -1 if inaccessible (occluded or distance >130); + # point scalar = 100..230 if accessible, the larger distance, the better + import numpy as np + pTarget = np.array(targetPointNode.GetNthControlPointPositionWorld(0)) + + skinPolyData = skinModelNode.GetPolyData() + # Triangulate + triangulator = vtk.vtkTriangleFilter() + triangulator.SetInputData(skinPolyData) + # Compute normals + polyDataNormals = vtk.vtkPolyDataNormals() + polyDataNormals.SetInputConnection(triangulator.GetOutputPort()) polyDataNormals.ComputeCellNormalsOn() polyDataNormals.Update() - polyData = polyDataNormals.GetOutput() + skinPolyData = polyDataNormals.GetOutput() bspTree = vtk.vtkModifiedBSPTree() bspTree.SetDataSet(obstacleModelNode.GetPolyData()) bspTree.BuildLocator() - (score, mind, mindp) = self.calcApproachScore(pTarget, polyData, bspTree, skinModelNode) - - print ("Accessible Area = %f" % (score)) - print ("Minmum Distance = %f" % (mind)) - - return (score, mind, mindp) - - def calcApproachScore(self, point, skinPolyData, obstacleBspTree, skinModelNode=None): - - pTarget = point - polyData = skinPolyData - nPoints = polyData.GetNumberOfPoints() - nCells = polyData.GetNumberOfCells() pSurface=[0.0, 0.0, 0.0] minDistancePoint = [0.0, 0.0, 0.0] @@ -1060,303 +924,85 @@ def calcApproachScore(self, point, skinPolyData, obstacleBspTree, skinModelNode= subId = vtk.mutable(0) # Map surface model - if skinModelNode != None: + if skinModelNode: pointValue = vtk.vtkDoubleArray() - pointValue.SetName("Colors") + pointValue.SetName("Accessibility") pointValue.SetNumberOfComponents(1) - pointValue.SetNumberOfTuples(nPoints) - pointValue.Reset() - pointValue.FillComponent(0,0.0); - - bspTree = obstacleBspTree - - cp0=[0.0, 0.0, 0.0] - cp1=[0.0, 0.0, 0.0] - cp2=[0.0, 0.0, 0.0] + pointValue.SetNumberOfTuples(skinPolyData.GetNumberOfPoints()) + pointValue.FillComponent(0,0.0) accessibleArea = 0.0 inaccessibleArea = 0.0 modifiedArea = 0.0 - + minDistance = -1 + ids=vtk.vtkIdList() + cp0=[0.0, 0.0, 0.0] + cp1=[0.0, 0.0, 0.0] + cp2=[0.0, 0.0, 0.0] - minDistance = -1; - + nCells = skinPolyData.GetNumberOfCells() for index in range(nCells): - cell = polyData.GetCell(index) + cell = skinPolyData.GetCell(index) if cell.GetCellType() == vtk.VTK_TRIANGLE: area = cell.ComputeArea() - polyData.GetCellPoints(index, ids) - polyData.GetPoint(ids.GetId(0), cp0) - polyData.GetPoint(ids.GetId(1), cp1) - polyData.GetPoint(ids.GetId(2), cp2) + skinPolyData.GetCellPoints(index, ids) + skinPolyData.GetPoint(ids.GetId(0), cp0) + skinPolyData.GetPoint(ids.GetId(1), cp1) + skinPolyData.GetPoint(ids.GetId(2), cp2) vtk.vtkTriangle.TriangleCenter(cp0, cp1, cp2, pSurface) iD = bspTree.IntersectWithLine(pSurface, pTarget, tolerance, t, x, pcoords, subId) if iD < 1: - if skinModelNode != None: - d = vtk.vtkMath.Distance2BetweenPoints(pSurface, pTarget) - d = math.sqrt(d) - modifiedArea = area * (130 - d) / 130 + # no intersection + if skinModelNode: + d = math.sqrt(vtk.vtkMath.Distance2BetweenPoints(pSurface, pTarget)) if d < minDistance or minDistance < 0: minDistance = d minDistancePoint = [pSurface[0],pSurface[1],pSurface[2]] - - v = 0 - if d < 130: + + maximumAccessibleDistance = 130 + v = -1.0 + if d < maximumAccessibleDistance: + # entry point is not too far from target point v = d + 101 - accessibleArea = accessibleArea + modifiedArea - else: - v = -1.0 - - pointValue.InsertValue(ids.GetId(0), v) - pointValue.InsertValue(ids.GetId(1), v) - pointValue.InsertValue(ids.GetId(2), v) + modifiedArea = area * (maximumAccessibleDistance - d) / maximumAccessibleDistance + accessibleArea += modifiedArea + + pointValue.SetValue(ids.GetId(0), v) + pointValue.SetValue(ids.GetId(1), v) + pointValue.SetValue(ids.GetId(2), v) else: - if skinModelNode != None: + # intersection + if skinModelNode: v = -1.0 - pointValue.InsertValue(ids.GetId(0), v) - pointValue.InsertValue(ids.GetId(1), v) - pointValue.InsertValue(ids.GetId(2), v) - inaccessibleArea = inaccessibleArea + area + pointValue.SetValue(ids.GetId(0), v) + pointValue.SetValue(ids.GetId(1), v) + pointValue.SetValue(ids.GetId(2), v) + inaccessibleArea += area else: print ("ERROR: Non-triangular cell.") score = accessibleArea - if skinModelNode != None: - visible = 1 - invisible = 0 + if skinModelNode: skinModelNode.AddPointScalars(pointValue) - skinModelNode.SetActivePointScalars("Colors", vtk.vtkDataSetAttributes.SCALARS) - skinModelNode.Modified() - displayNode = skinModelNode.GetModelDisplayNode() - displayNode.SetActiveScalarName("Colors") - displayNode.SetScalarRange(0.0,20.0) - scalarSetting = slicer.modulewidget.qMRMLModelDisplayNodeWidget() - scalarSetting.setMRMLModelDisplayNode(displayNode) - scalarSetting.setScalarsVisibility(invisible) - - return (score, minDistance, minDistancePoint) - -class SphereModel: - def __init__(self): - pass - - def make(self, visibilityParam, color, modelName): - scene = slicer.mrmlScene - - sphere = vtk.vtkSphereSource() - sphere.SetRadius(0.5) - sphere.SetPhiResolution(100) - sphere.SetThetaResolution(100) - sphere.Update() - - sphere1 = vtk.vtkSphereSource() - sphere1.SetRadius(2.5) - sphere1.SetPhiResolution(100) - sphere1.SetThetaResolution(100) - sphere1.SetCenter(20,20,20) - sphere1.Update() - - # Create model node - sphereCursor = slicer.vtkMRMLModelNode() - sphereCursor.SetScene(scene) - sphereCursor.SetName(scene.GenerateUniqueName(modelName)) - sphereCursor.SetAndObservePolyData(sphere.GetOutput()) - - # Create display node - cursorModelDisplay = slicer.vtkMRMLModelDisplayNode() - cursorModelDisplay.SetColor(color[0], color[1], color[2]) - cursorModelDisplay.SetOpacity(0.3) - cursorModelDisplay.SetScene(scene) - cursorModelDisplay.SetVisibility(visibilityParam) - cursorModelDisplay.SetSliceIntersectionVisibility(True) # Show in slice view - - scene.AddNode(cursorModelDisplay) - sphereCursor.SetAndObserveDisplayNodeID(cursorModelDisplay.GetID()) - - # Add to scene - if vtk.VTK_MAJOR_VERSION <= 5: - cursorModelDisplay.SetInputPolyData(sphere.GetOutput()) - - scene.AddNode(sphereCursor) - - # Create transform node - transform = slicer.vtkMRMLLinearTransformNode() - transformName = modelName + "Transform" - transform.SetName(scene.GenerateUniqueName(transformName)) - scene.AddNode(transform) - sphereCursor.SetAndObserveTransformNodeID(transform.GetID()) + skinModelNode.GetDisplayNode().SetActiveScalar("Accessibility", vtk.vtkAssignAttribute.POINT_DATA) + skinModelNode.GetDisplayNode().SetAndObserveColorNodeID("vtkMRMLColorTableNodeFileMagma.txt") + skinModelNode.GetDisplayNode().SetScalarVisibility(True) - return (sphereCursor, cursorModelDisplay, transform) + # skinModelNode.SetActivePointScalars("Accessibility", vtk.vtkDataSetAttributes.SCALARS) + # skinModelNode.Modified() + # displayNode = skinModelNode.GetModelDisplayNode() + # displayNode.SetActiveScalarName("Accessibility") + # displayNode.SetScalarRange(0.0,20.0) + # displayNode.SetScalarVisibility(False) - def move(self, p, pointNumber, pointSliderValue, transform): - - r = p[pointNumber*2+1][0] - a = p[pointNumber*2+1][1] - s = p[pointNumber*2+1][2] - - targetR = p[pointNumber*2][0] - targetA = p[pointNumber*2][1] - targetS = p[pointNumber*2][2] - - dirVectorR = r - targetR - dirVectorA = a - targetA - dirVectorS = s - targetS - - movedR = r + pointSliderValue*0.001*dirVectorR - movedA = a + pointSliderValue*0.001*dirVectorA - movedS = s + pointSliderValue*0.001*dirVectorS - - coordinate = vtk.vtkMatrix4x4() - transform.GetMatrixTransformToParent(coordinate) - coordinate.SetElement(0, 3, movedR) - coordinate.SetElement(1, 3, movedA) - coordinate.SetElement(2, 3, movedS) - transform.SetMatrixTransformToParent(coordinate) - - movedRAS = [movedR, movedA, movedS] - - return movedRAS - - def drop(self, r, a, s, transform): - - coordinate = vtk.vtkMatrix4x4() - transform.GetMatrixTransformToParent(coordinate) - coordinate.SetElement(0, 3, r) - coordinate.SetElement(1, 3, a) - coordinate.SetElement(2, 3, s) - transform.SetMatrixTransformToParent(coordinate) - -# NeedlePathModel class is based on EndoscopyPathModel class for Endoscopy module -class NeedlePathModel: - """Create a vtkPolyData for a polyline: - - Add one point per path point. - - Add a single polyline - """ - def __init__(self): - pass - - def createPolyData(self, points): - """make an empty single-polyline polydata""" - - polyData = vtk.vtkPolyData() - polyData.SetPoints(points) - - lines = vtk.vtkCellArray() - polyData.SetLines(lines) - idArray = lines.GetData() - idArray.Reset() - idArray.InsertNextTuple1(0) - - polygons = vtk.vtkCellArray() - polyData.SetPolys(polygons) - idArray = polygons.GetData() - idArray.Reset() - idArray.InsertNextTuple1(0) - - return polyData - - def modify(self, path, approachablePoints, visibilityParam, color, modelName, lineType, model, modelDisplay, points): - import numpy - - # Create an array for all approachable points - p = numpy.zeros([approachablePoints*2,3]) - p1 = [0.0, 0.0, 0.0] - - scene = slicer.mrmlScene - - polyData = vtk.vtkPolyData() - polyData = lineType - - lines = vtk.vtkCellArray() - polyData.SetLines(lines) - linesIDArray = lines.GetData() - linesIDArray.Reset() - linesIDArray.InsertNextTuple1(0) - - polygons = vtk.vtkCellArray() - polyData.SetPolys( polygons ) - idArray = polygons.GetData() - idArray.Reset() - idArray.InsertNextTuple1(0) - - if approachablePoints != 0: - - for point in path: - pointIndex = points.InsertNextPoint(*point) - linesIDArray.InsertNextTuple1(pointIndex) - linesIDArray.SetTuple1( 0, linesIDArray.GetNumberOfTuples() - 1 ) - lines.SetNumberOfCells(1) - - def make(self, path, approachablePoints, visibilityParam, color, modelName, lineType): - - import numpy - - # Create an array for all approachable points - p = numpy.zeros([approachablePoints*2,3]) - p1 = [0.0, 0.0, 0.0] - - scene = slicer.mrmlScene - - self.points = vtk.vtkPoints() - polyData = vtk.vtkPolyData() - polyData = lineType - polyData.SetPoints(self.points) - - lines = vtk.vtkCellArray() - polyData.SetLines(lines) - linesIDArray = lines.GetData() - linesIDArray.Reset() - linesIDArray.InsertNextTuple1(0) - - polygons = vtk.vtkCellArray() - polyData.SetPolys( polygons ) - idArray = polygons.GetData() - idArray.Reset() - idArray.InsertNextTuple1(0) - - if approachablePoints != 0: - - for point in path: - pointIndex = self.points.InsertNextPoint(*point) - linesIDArray.InsertNextTuple1(pointIndex) - linesIDArray.SetTuple1( 0, linesIDArray.GetNumberOfTuples() - 1 ) - lines.SetNumberOfCells(1) - - # Save all approachable points - p1[0] = linesIDArray.GetTuple1(1) - p1[1] = linesIDArray.GetTuple1(2) - p1[2] = linesIDArray.GetTuple1(3) - - coord = [p1[0], p1[1], p1[2]] - p[pointIndex] = coord - - # Create model node - model = slicer.vtkMRMLModelNode() - model.SetScene(scene) - model.SetName(scene.GenerateUniqueName(modelName)) - model.SetAndObservePolyData(polyData) - - # Create display node - modelDisplay = slicer.vtkMRMLModelDisplayNode() - modelDisplay.SetColor(color[0], color[1], color[2]) - modelDisplay.SetScene(scene) - modelDisplay.SetVisibility(visibilityParam) - modelDisplay.SetSliceIntersectionVisibility(True) # Show in slice view - scene.AddNode(modelDisplay) - model.SetAndObserveDisplayNodeID(modelDisplay.GetID()) - - # Add to scene - if vtk.VTK_MAJOR_VERSION <= 5: - modelDisplay.SetInputPolyData(model.GetPolyData()) - scene.AddNode(model) + return (score, minDistance, minDistancePoint) - return (model, p, modelDisplay, self.points) class PercutaneousApproachAnalysisTest(unittest.TestCase): """ @@ -1424,5 +1070,4 @@ def test_PercutaneousApproachAnalysis1(self): volumeNode = slicer.util.getNode(pattern="FA") logic = PercutaneousApproachAnalysisLogic() - self.assertTrue( logic.hasImageData(volumeNode) ) self.delayDisplay('Test passed!') diff --git a/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.pyc b/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.pyc new file mode 100644 index 0000000..fa2beaa Binary files /dev/null and b/PercutaneousApproachAnalysis/PercutaneousApproachAnalysis.pyc differ