|
@@ -1,442 +0,0 @@
|
|
|
-import slicer
|
|
|
-import vtk
|
|
|
-import os
|
|
|
-from slicer.ScriptedLoadableModule import *
|
|
|
-import ctk
|
|
|
-import qt
|
|
|
-
|
|
|
-class resample(ScriptedLoadableModule):
|
|
|
- """Uses ScriptedLoadableModule base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
- def __init__(self, parent):
|
|
|
- ScriptedLoadableModule.__init__(self, parent)
|
|
|
- parent.title = "Resample"
|
|
|
- parent.categories = ["Examples"]
|
|
|
- parent.dependencies = []
|
|
|
- parent.contributors = ["Andrej Studen (FMF/JSI)"]
|
|
|
- parent.helpText = """
|
|
|
- Resample to different shapes
|
|
|
- """
|
|
|
- parent.acknowledgementText = """
|
|
|
- This module was developed within the frame of the ARRS sponsored medical
|
|
|
- physics research programe to investigate quantitative measurements of cardiac
|
|
|
- function using sestamibi-like tracers
|
|
|
- """ # replace with organization, grant and thanks.
|
|
|
- self.parent.helpText += self.getDefaultModuleDocumentationLink()
|
|
|
- self.parent = parent
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-class resampleWidget(ScriptedLoadableModuleWidget):
|
|
|
- """Uses ScriptedLoadableModuleWidget base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def setup(self):
|
|
|
- ScriptedLoadableModuleWidget.setup(self)
|
|
|
- self.logic=resampleLogic(self)
|
|
|
-
|
|
|
- datasetCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
- datasetCollapsibleButton.text = "Dataset"
|
|
|
- self.layout.addWidget(datasetCollapsibleButton)
|
|
|
-
|
|
|
- datasetFormLayout = qt.QFormLayout(datasetCollapsibleButton)
|
|
|
-
|
|
|
- self.transformNode=qt.QLineEdit("NodeToTransform")
|
|
|
- datasetFormLayout.addRow("TransformedNode:",self.transformNode)
|
|
|
- self.referenceNode=qt.QLineEdit("ReferenceNode")
|
|
|
- datasetFormLayout.addRow("ReferenceNode:",self.referenceNode)
|
|
|
-
|
|
|
- self.transformButton=qt.QPushButton("Transform")
|
|
|
- self.transformButton.clicked.connect(self.onTransformButtonClicked)
|
|
|
- datasetFormLayout.addRow("Volume:",self.transformButton)
|
|
|
-
|
|
|
- self.transformSegmentationButton=qt.QPushButton("Transform")
|
|
|
- self.transformSegmentationButton.clicked.connect(self.onTransformSegmentationButtonClicked)
|
|
|
- datasetFormLayout.addRow("Segmentation:",self.transformSegmentationButton)
|
|
|
-
|
|
|
-
|
|
|
- def onTransformButtonClicked(self):
|
|
|
- node=slicer.util.getFirstNodeByName(self.transformNode.text)
|
|
|
- if node==None:
|
|
|
- print("Transform node [{}] not found").format(self.transformNode.text)
|
|
|
- return
|
|
|
- refNode=slicer.util.getFirstNodeByName(self.referenceNode.text)
|
|
|
- if refNode==None:
|
|
|
- print("Reference node [{}] not found").format(self.referenceNode.text)
|
|
|
- return
|
|
|
-
|
|
|
- self.logic.rebinNode(node,refNode)
|
|
|
-
|
|
|
- def onTransformSegmentationButtonClicked(self):
|
|
|
-
|
|
|
- segNodes=slicer.util.getNodesByClass("vtkMRMLSegmentationNode")
|
|
|
- segNode=None
|
|
|
- for s in segNodes:
|
|
|
- print ("SegmentationNode: {}").format(s.GetName())
|
|
|
- if s.GetName()==self.transformNode.text:
|
|
|
- segNode=s
|
|
|
- break
|
|
|
-
|
|
|
- if segNode==None:
|
|
|
- print("Segmentation node [{}] not found").format(self.transformNode.text)
|
|
|
- return
|
|
|
-
|
|
|
- refNode=slicer.util.getFirstNodeByName(self.referenceNode.text)
|
|
|
- if refNode==None:
|
|
|
- print("Reference node [{}] not found").format(self.referenceNode.text)
|
|
|
- return
|
|
|
-
|
|
|
- self.logic.rebinSegmentation(segNode,refNode)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-class resampleLogic(ScriptedLoadableModuleLogic):
|
|
|
- """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.
|
|
|
- Uses ScriptedLoadableModuleLogic base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
- def __init__(self,parent):
|
|
|
- ScriptedLoadableModuleLogic.__init__(self, parent)
|
|
|
- try:
|
|
|
- fhome=os.environ["HOME"]
|
|
|
- except:
|
|
|
-
|
|
|
- fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
|
|
|
- self.baseLog=os.path.join(fhome,".resample")
|
|
|
- if not os.path.isdir(self.baseLog):
|
|
|
- os.mkdir(self.baseLog)
|
|
|
-
|
|
|
- def printMe(self):
|
|
|
- print("resampleLogic ready")
|
|
|
- print("Log: {}").format(self.baseLog)
|
|
|
-
|
|
|
-
|
|
|
- def cast(self,newImage,originalImage):
|
|
|
- if newImage.GetPointData().GetScalars().GetDataType()==originalImage.GetPointData().GetScalars().GetDataType():
|
|
|
- return newImage
|
|
|
- outputType=originalImage.GetPointData().GetScalars().__class__.__name__
|
|
|
- shifter=vtk.vtkImageShiftScale()
|
|
|
- shifter.SetInputData(newImage)
|
|
|
- if outputType=="vtkUnsignedShortArray":
|
|
|
- shifter.SetOutputScalarTypeToUnsignedShort()
|
|
|
- if outputType=="vtkShortArray":
|
|
|
- shifter.SetOutputScalarTypeToShort()
|
|
|
- shifter.SetScale(1)
|
|
|
- shifter.SetShift(0)
|
|
|
- shifter.Update()
|
|
|
- return shifter.GetOutput()
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def rebinNode(self,node,refNode):
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- log=open(os.path.join(self.baseLog,"rebinNode.log"),"w")
|
|
|
-
|
|
|
-
|
|
|
- log.write(("rebinNode: volume: {} ref: {}\n").format(node.GetName(),refNode.GetName()))
|
|
|
- refImage=refNode.GetImageData()
|
|
|
- n=refImage.GetNumberOfPoints()
|
|
|
- refIJKtoRAS=vtk.vtkMatrix4x4()
|
|
|
- refNode.GetIJKToRASMatrix(refIJKtoRAS)
|
|
|
-
|
|
|
- nodeRAStoIJK=vtk.vtkMatrix4x4()
|
|
|
- node.GetRASToIJKMatrix(nodeRAStoIJK)
|
|
|
- nodeName=node.GetName()
|
|
|
-
|
|
|
- coeff=vtk.vtkImageBSplineCoefficients()
|
|
|
- coeff.SetInputData(node.GetImageData())
|
|
|
- coeff.SetBorderMode(vtk.VTK_IMAGE_BORDER_CLAMP)
|
|
|
-
|
|
|
- coeff.SetSplineDegree(5)
|
|
|
- coeff.Update()
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- newImage=vtk.vtkImageData()
|
|
|
- newImage.DeepCopy(refNode.GetImageData())
|
|
|
- newImage=self.cast(newImage,node.GetImageData())
|
|
|
- newScalars=newImage.GetPointData().GetScalars()
|
|
|
-
|
|
|
- log.write(("Iterating: {} points\n").format(n))
|
|
|
-
|
|
|
-
|
|
|
- for i in range(0,n):
|
|
|
- refIJK=refImage.GetPoint(i)
|
|
|
- refIJK=list(refIJK)
|
|
|
- refIJK.append(1)
|
|
|
-
|
|
|
- refPos=refIJKtoRAS.MultiplyPoint(refIJK)
|
|
|
- refPos=refPos[0:3]
|
|
|
- fWorld=[0,0,0]
|
|
|
-
|
|
|
- refNode.TransformPointToWorld(refPos,fWorld)
|
|
|
-
|
|
|
-
|
|
|
- nodePos=[0,0,0]
|
|
|
- node.TransformPointFromWorld(fWorld,nodePos)
|
|
|
- nodePos.append(1)
|
|
|
- nodeIJK=nodeRAStoIJK.MultiplyPoint(nodePos)
|
|
|
-
|
|
|
-
|
|
|
- nodeIJK=nodeIJK[0:3]
|
|
|
- v=coeff.Evaluate(nodeIJK)
|
|
|
- v0=newScalars.GetTuple1(i)
|
|
|
- newScalars.SetTuple1(i,v)
|
|
|
- if i%10000==0:
|
|
|
- log.write(("[{}]: {}->{}\n").format(i,v0,v))
|
|
|
-
|
|
|
- node.SetName(nodeName+"_BU")
|
|
|
-
|
|
|
-
|
|
|
- newNode=slicer.vtkMRMLScalarVolumeNode()
|
|
|
- newNode.SetName(nodeName)
|
|
|
- newNode.SetOrigin(refNode.GetOrigin())
|
|
|
- newNode.SetSpacing(refNode.GetSpacing())
|
|
|
- newNode.SetIJKToRASMatrix(refIJKtoRAS)
|
|
|
-
|
|
|
- newNode.SetAndObserveImageData(newImage)
|
|
|
- slicer.mrmlScene.AddNode(newNode)
|
|
|
- log.write(("Adding node {}\n").format(newNode.GetName()))
|
|
|
- log.close()
|
|
|
- return newNode
|
|
|
-
|
|
|
-
|
|
|
- def inMask(self,binaryRep,fpos):
|
|
|
-
|
|
|
- local=[0,0,0]
|
|
|
-
|
|
|
- segNode=binaryRep['node']
|
|
|
- segNode.TransformPointFromWorld(fpos,local)
|
|
|
-
|
|
|
- mask=binaryRep['mask']
|
|
|
- maskWorldToImageMatrix=vtk.vtkMatrix4x4()
|
|
|
- mask.GetWorldToImageMatrix(maskWorldToImageMatrix)
|
|
|
- local.append(1)
|
|
|
- maskIJK=maskWorldToImageMatrix.MultiplyPoint(local)
|
|
|
-
|
|
|
-
|
|
|
- maskIJK=maskIJK[0:3]
|
|
|
- maskIJK=[r*s for r,s in zip(maskIJK,mask.GetSpacing())]
|
|
|
- maskIJK=[r+c for r,c in zip(maskIJK,mask.GetOrigin())]
|
|
|
-
|
|
|
- maskI=mask.FindPoint(maskIJK)
|
|
|
- try:
|
|
|
- return mask.GetPointData().GetScalars().GetTuple1(maskI)
|
|
|
- except:
|
|
|
- return 0
|
|
|
-
|
|
|
- def rebinSegment(self,refNode,binaryRep):
|
|
|
-
|
|
|
- refIJKtoRAS=vtk.vtkMatrix4x4()
|
|
|
- refNode.GetIJKToRASMatrix(refIJKtoRAS)
|
|
|
- refImage=refNode.GetImageData()
|
|
|
-
|
|
|
-
|
|
|
- newImage=vtk.vtkImageData()
|
|
|
- newImage.DeepCopy(refNode.GetImageData())
|
|
|
- n=newImage.GetNumberOfPoints()
|
|
|
- newScalars=newImage.GetPointData().GetScalars()
|
|
|
-
|
|
|
- segNode=binaryRep['node']
|
|
|
- mask=binaryRep['mask']
|
|
|
-
|
|
|
- for j in range(0,n):
|
|
|
- refIJK=refImage.GetPoint(j)
|
|
|
- refIJK=list(refIJK)
|
|
|
- refIJK.append(1)
|
|
|
-
|
|
|
-
|
|
|
- refPos=refIJKtoRAS.MultiplyPoint(refIJK)
|
|
|
- refPos=refPos[0:3]
|
|
|
- fWorld=[0,0,0]
|
|
|
-
|
|
|
-
|
|
|
- refNode.TransformPointToWorld(refPos,fWorld)
|
|
|
-
|
|
|
- v=self.inMask(binaryRep,fWorld)
|
|
|
-
|
|
|
- newScalars.SetTuple1(j,v)
|
|
|
-
|
|
|
- newNode=slicer.vtkMRMLScalarVolumeNode()
|
|
|
- newNode.SetName(segNode.GetName()+'_'+binaryRep['segId'])
|
|
|
- newNode.SetOrigin(refNode.GetOrigin())
|
|
|
- newNode.SetSpacing(refNode.GetSpacing())
|
|
|
- newNode.SetIJKToRASMatrix(refIJKtoRAS)
|
|
|
-
|
|
|
- newNode.SetAndObserveImageData(newImage)
|
|
|
- slicer.mrmlScene.AddNode(newNode)
|
|
|
- return newNode
|
|
|
-
|
|
|
-
|
|
|
- def rebinSegmentation(self,segNode,refNode):
|
|
|
-
|
|
|
- log=open(os.path.join(self.baseLog,"rebinSegmentation.log"),"w")
|
|
|
-
|
|
|
- log.write(("rebinNode: {} {}\n").format(segNode.GetName(),refNode.GetName()))
|
|
|
-
|
|
|
- nSeg=segNode.GetSegmentation().GetNumberOfSegments()
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- for i in range(0,nSeg):
|
|
|
-
|
|
|
- segID=segNode.GetSegmentation().GetNthSegmentID(i)
|
|
|
- log.write(("Parsing segment {}").format(segNode.GetSegmentation.GetNthSegment(i).GetName()))
|
|
|
- binaryRep={'node':segNode,
|
|
|
- 'mask':segNode.GetBinaryLabelmapRepresentation(segID)}
|
|
|
- newNode=self.rebinSegment(refNode,binaryRep)
|
|
|
- log.write(("Adding node: {}").format(newNode.GetName()))
|
|
|
-
|
|
|
- log.close()
|
|
|
-
|
|
|
- def rebinSegmentation1(self,segNode,refNode):
|
|
|
-
|
|
|
-
|
|
|
- logfile="C:\\Users\\studen\\labkeyCache\\log\\resample.log"
|
|
|
- print("rebinNode: {} {}\n").format(segNode.GetName(),refNode.GetName())
|
|
|
- refImage=refNode.GetImageData()
|
|
|
- refIJKtoRAS=vtk.vtkMatrix4x4()
|
|
|
- refNode.GetIJKToRASMatrix(refIJKtoRAS)
|
|
|
- refRAStoIJK=vtk.vtkMatrix4x4()
|
|
|
- refNode.GetRASToIJKMatrix(refRAStoIJK)
|
|
|
-
|
|
|
- nSeg=segNode.GetSegmentation().GetNumberOfSegments()
|
|
|
-
|
|
|
- nSeg=1
|
|
|
-
|
|
|
- for i in range(0,nSeg):
|
|
|
-
|
|
|
- segID=segNode.GetSegmentation().GetNthSegmentID(i)
|
|
|
- binaryRep={'node':segNode,
|
|
|
- 'mask': segNode.GetBinaryLabelmapRepresentation(segID)}
|
|
|
-
|
|
|
- mask=binaryRep['mask']
|
|
|
-
|
|
|
- newImage=vtk.vtkImageData()
|
|
|
- newImage.DeepCopy(refNode.GetImageData())
|
|
|
- newScalars=newImage.GetPointData().GetScalars()
|
|
|
- refN=newImage.GetNumberOfPoints()
|
|
|
- for k in range(0,refN):
|
|
|
- newScalars.SetTuple1(k,0)
|
|
|
-
|
|
|
- maskN=binaryRep['mask'].GetNumberOfPoints()
|
|
|
- maskScalars=mask.GetPointData().GetScalars()
|
|
|
- maskImageToWorldMatrix=vtk.vtkMatrix4x4()
|
|
|
- binaryRep['mask'].GetImageToWorldMatrix(maskImageToWorldMatrix)
|
|
|
-
|
|
|
- for j in range(0,maskN):
|
|
|
-
|
|
|
- if maskScalars.GetTuple1(j)==0:
|
|
|
- continue
|
|
|
-
|
|
|
- maskIJK=mask.GetPoint(j)
|
|
|
- maskIJK=[r-c for r,c in zip(maskIJK,mask.GetOrigin())]
|
|
|
- maskIJK=[r/s for r,s in zip(maskIJK,mask.GetSpacing())]
|
|
|
- maskIJK.append(1)
|
|
|
- maskPos=maskImageToWorldMatrix.MultiplyPoint(maskIJK)
|
|
|
- maskPos=maskPos[0:3]
|
|
|
- fWorld=[0,0,0]
|
|
|
-
|
|
|
- segNode.TransformPointToWorld(maskPos,fWorld)
|
|
|
-
|
|
|
- refPos=[0,0,0]
|
|
|
-
|
|
|
-
|
|
|
- refNode.TransformPointFromWorld(fWorld,refPos)
|
|
|
- refPos.append(1)
|
|
|
- refIJK=refRAStoIJK.MultiplyPoint(refPos)
|
|
|
- refIJK=refIJK[0:3]
|
|
|
- i1=newImage.FindPoint(refIJK)
|
|
|
- if i1<0:
|
|
|
- continue
|
|
|
- if i1<refN:
|
|
|
- newScalars.SetTuple1(i1,1)
|
|
|
-
|
|
|
- newNode=slicer.vtkMRMLScalarVolumeNode()
|
|
|
- newNode.SetName(segNode.GetName()+'_'+segNode.GetSegmentation().GetNthSegmentID(i))
|
|
|
- newNode.SetOrigin(refNode.GetOrigin())
|
|
|
- newNode.SetSpacing(refNode.GetSpacing())
|
|
|
- newNode.SetIJKToRASMatrix(refIJKtoRAS)
|
|
|
-
|
|
|
- newNode.SetAndObserveImageData(newImage)
|
|
|
- slicer.mrmlScene.AddNode(newNode)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-class resampleTest(ScriptedLoadableModuleTest):
|
|
|
- """
|
|
|
- This is the test case for your scripted module.
|
|
|
- Uses ScriptedLoadableModuleTest base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def setUp(self):
|
|
|
- """ Do whatever is needed to reset the state - typically a scene clear will be enough.
|
|
|
- """
|
|
|
- slicer.mrmlScene.Clear(0)
|
|
|
- refNodeName="2SBMIRVolume19"
|
|
|
- nodeName="2SMobrVolume19"
|
|
|
- transformNodeName="2SMobr_DF"
|
|
|
- path="c:\\Users\\studen\\labkeyCache\\dinamic_spect\\Patients\\@files"
|
|
|
-
|
|
|
- refPath=os.path.join(path,"2SBMIR")
|
|
|
- refPath=os.path.join(refPath,refNodeName+".nrrd")
|
|
|
- slicer.util.loadNodeFromFile(refPath,'VolumeFile')
|
|
|
-
|
|
|
- transformPath=os.path.join(path,"2SMobr")
|
|
|
- transformPath=os.path.join(transformPath,transformNodeName+".h5")
|
|
|
- slicer.util.loadNodeFromFile(transformPath,'TransformFile')
|
|
|
-
|
|
|
- nodePath=os.path.join(path,"2SMobr")
|
|
|
- nodePath=os.path.join(nodePath,nodeName+".nrrd")
|
|
|
-
|
|
|
- slicer.util.loadNodeFromFile(nodePath,'VolumeFile')
|
|
|
-
|
|
|
- self.node=slicer.util.getFirstNodeByName(nodeName)
|
|
|
- self.refNode=slicer.util.getFirstNodeByName(refNodeName)
|
|
|
- self.transformNode=slicer.util.getFirstNodeByName(transformNodeName)
|
|
|
- self.node.SetAndObserveTransformNodeID(self.transformNode.GetID())
|
|
|
-
|
|
|
- self.resampler=resampleLogic(None)
|
|
|
-
|
|
|
- def runTest(self):
|
|
|
- """Run as few or as many tests as needed here.
|
|
|
- """
|
|
|
- self.setUp()
|
|
|
- self.test_resample()
|
|
|
-
|
|
|
- def test_resample(self):
|
|
|
- """ Ideally you should have several levels of tests. At the lowest level
|
|
|
- tests should exercise the functionality of the logic with different inputs
|
|
|
- (both valid and invalid). At higher levels your tests should emulate the
|
|
|
- way the user would interact with your code and confirm that it still works
|
|
|
- the way you intended.
|
|
|
- One of the most important features of the tests is that it should alert other
|
|
|
- developers when their changes will have an impact on the behavior of your
|
|
|
- module. For example, if a developer removes a feature that you depend on,
|
|
|
- your test should break so they know that the feature is needed.
|
|
|
- """
|
|
|
-
|
|
|
- self.delayDisplay("Starting the test")
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- self.resampler.rebinNode(self.node,self.refNode)
|
|
|
-
|
|
|
- self.delayDisplay('Test passed!')
|