resample.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import slicer
  2. import vtk
  3. import os
  4. from slicer.ScriptedLoadableModule import *
  5. import ctk
  6. import qt
  7. class resample(ScriptedLoadableModule):
  8. """Uses ScriptedLoadableModule base class, available at:
  9. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  10. """
  11. def __init__(self, parent):
  12. ScriptedLoadableModule.__init__(self, parent)
  13. parent.title = "Resample"
  14. parent.categories = ["LabKey"]
  15. parent.dependencies = []
  16. parent.contributors = ["Andrej Studen (FMF/JSI)"] # replace with "Firstname Lastname (Org)"
  17. parent.helpText = """
  18. Resample to different shapes
  19. """
  20. parent.acknowledgementText = """
  21. This module was developed within the frame of the ARRS sponsored medical
  22. physics research programe to investigate quantitative measurements of cardiac
  23. function using sestamibi-like tracers
  24. """ # replace with organization, grant and thanks.
  25. self.parent.helpText += self.getDefaultModuleDocumentationLink()
  26. self.parent = parent
  27. #
  28. # resampleWidget
  29. #
  30. class resampleWidget(ScriptedLoadableModuleWidget):
  31. """Uses ScriptedLoadableModuleWidget base class, available at:
  32. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  33. """
  34. def setup(self):
  35. ScriptedLoadableModuleWidget.setup(self)
  36. self.logic=resampleLogic(self)
  37. datasetCollapsibleButton = ctk.ctkCollapsibleButton()
  38. datasetCollapsibleButton.text = "Dataset"
  39. self.layout.addWidget(datasetCollapsibleButton)
  40. # Layout within the dummy collapsible button
  41. datasetFormLayout = qt.QFormLayout(datasetCollapsibleButton)
  42. self.transformNode=qt.QLineEdit("NodeToTransform")
  43. datasetFormLayout.addRow("TransformedNode:",self.transformNode)
  44. self.referenceNode=qt.QLineEdit("ReferenceNode")
  45. datasetFormLayout.addRow("ReferenceNode:",self.referenceNode)
  46. self.transformButton=qt.QPushButton("Transform")
  47. self.transformButton.clicked.connect(self.onTransformButtonClicked)
  48. datasetFormLayout.addRow("Volume:",self.transformButton)
  49. self.transformSegmentationButton=qt.QPushButton("Transform")
  50. self.transformSegmentationButton.clicked.connect(self.onTransformSegmentationButtonClicked)
  51. datasetFormLayout.addRow("Segmentation:",self.transformSegmentationButton)
  52. def onTransformButtonClicked(self):
  53. node=slicer.util.getFirstNodeByName(self.transformNode.text)
  54. if node==None:
  55. print("Transform node [{}] not found").format(self.transformNode.text)
  56. return
  57. refNode=slicer.util.getFirstNodeByName(self.referenceNode.text)
  58. if refNode==None:
  59. print("Reference node [{}] not found").format(self.referenceNode.text)
  60. return
  61. self.logic.rebinNode(node,refNode)
  62. def onTransformSegmentationButtonClicked(self):
  63. segNodes=slicer.util.getNodesByClass("vtkMRMLSegmentationNode")
  64. segNode=None
  65. for s in segNodes:
  66. print ("SegmentationNode: {}").format(s.GetName())
  67. if s.GetName()==self.transformNode.text:
  68. segNode=s
  69. break
  70. if segNode==None:
  71. print("Segmentation node [{}] not found").format(self.transformNode.text)
  72. return
  73. refNode=slicer.util.getFirstNodeByName(self.referenceNode.text)
  74. if refNode==None:
  75. print("Reference node [{}] not found").format(self.referenceNode.text)
  76. return
  77. self.logic.rebinSegmentation(segNode,refNode)
  78. class resampleLogic(ScriptedLoadableModuleLogic):
  79. """This class should implement all the actual
  80. computation done by your module. The interface
  81. should be such that other python code can import
  82. this class and make use of the functionality without
  83. requiring an instance of the Widget.
  84. Uses ScriptedLoadableModuleLogic base class, available at:
  85. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  86. """
  87. def __init__(self,parent):
  88. ScriptedLoadableModuleLogic.__init__(self, parent)
  89. try:
  90. fhome=os.environ["HOME"]
  91. except:
  92. #in windows, the variable is called HOMEPATH
  93. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  94. self.baseLog=os.path.join(fhome,".resample")
  95. if not os.path.isdir(self.baseLog):
  96. os.mkdir(self.baseLog)
  97. def printMe(self):
  98. print("resampleLogic ready")
  99. print("Log: {}").format(self.baseLog)
  100. def cast(self,newImage,originalImage):
  101. if newImage.GetPointData().GetScalars().GetDataType()==originalImage.GetPointData().GetScalars().GetDataType():
  102. return newImage
  103. outputType=originalImage.GetPointData().GetScalars().__class__.__name__
  104. shifter=vtk.vtkImageShiftScale()
  105. shifter.SetInputData(newImage)
  106. if outputType=="vtkUnsignedShortArray":
  107. shifter.SetOutputScalarTypeToUnsignedShort()
  108. if outputType=="vtkShortArray":
  109. shifter.SetOutputScalarTypeToShort()
  110. shifter.SetScale(1)
  111. shifter.SetShift(0)
  112. shifter.Update()
  113. return shifter.GetOutput()
  114. def rebinNode(self,node,refNode):
  115. #refNodeName="2SBMIRVolume19"
  116. #nodeName="2SMobrVolume19"
  117. #node=slicer.util.getFirstNodeByName(nodeName)
  118. #refNode=slicer.util.getFirstNodeByName(refNodeName)
  119. #transformNodeName="2SMobr_DF"
  120. #transformNode=slicer.util.getFirstNodeByName(transformNodeName)
  121. #node.SetAndObserveTransformNodeID(transformNode.GetID())
  122. log=open(os.path.join(self.baseLog,"rebinNode.log"),"w")
  123. log.write(("rebinNode: volume: {} ref: {}\n").format(node.GetName(),refNode.GetName()))
  124. refImage=refNode.GetImageData()
  125. n=refImage.GetNumberOfPoints()
  126. refIJKtoRAS=vtk.vtkMatrix4x4()
  127. refNode.GetIJKToRASMatrix(refIJKtoRAS)
  128. nodeRAStoIJK=vtk.vtkMatrix4x4()
  129. node.GetRASToIJKMatrix(nodeRAStoIJK)
  130. nodeName=node.GetName()
  131. coeff=vtk.vtkImageBSplineCoefficients()
  132. coeff.SetInputData(node.GetImageData())
  133. coeff.SetBorderMode(vtk.VTK_IMAGE_BORDER_CLAMP)
  134. #between 3 and 5
  135. coeff.SetSplineDegree(5)
  136. coeff.Update()
  137. #interpolation ready to use
  138. #this is tough. COpy only links (ie. shallow copy)
  139. newImage=vtk.vtkImageData()
  140. newImage.DeepCopy(refNode.GetImageData())
  141. newImage=self.cast(newImage,node.GetImageData())
  142. newScalars=newImage.GetPointData().GetScalars()
  143. #doesn't set the scalars
  144. log.write(("Iterating: {} points\n").format(n))
  145. for i in range(0,n):
  146. refIJK=refImage.GetPoint(i)
  147. refIJK=list(refIJK)
  148. refIJK.append(1)
  149. #shift to world coordinates to match global points
  150. refPos=refIJKtoRAS.MultiplyPoint(refIJK)
  151. refPos=refPos[0:3]
  152. fWorld=[0,0,0]
  153. #apply potential transformations
  154. refNode.TransformPointToWorld(refPos,fWorld)
  155. #now do the opposite on the target node; reuse fPos
  156. nodePos=[0,0,0]
  157. node.TransformPointFromWorld(fWorld,nodePos)
  158. nodePos.append(1)
  159. nodeIJK=nodeRAStoIJK.MultiplyPoint(nodePos)
  160. #here we should apply some sort of interpolation
  161. nodeIJK=nodeIJK[0:3]
  162. v=coeff.Evaluate(nodeIJK)
  163. v0=newScalars.GetTuple1(i)
  164. newScalars.SetTuple1(i,v)
  165. if i%10000==0:
  166. log.write(("[{}]: {}->{}\n").format(i,v0,v))
  167. #node.SetName(nodeName+"_BU")
  168. newNode=slicer.vtkMRMLScalarVolumeNode()
  169. newNode.SetName(nodeName+"_RS")
  170. newNode.SetOrigin(refNode.GetOrigin())
  171. newNode.SetSpacing(refNode.GetSpacing())
  172. newNode.SetIJKToRASMatrix(refIJKtoRAS)
  173. newNode.SetAndObserveImageData(newImage)
  174. slicer.mrmlScene.AddNode(newNode)
  175. log.write(("Adding node {}\n").format(newNode.GetName()))
  176. log.close()
  177. return newNode
  178. def inMask(self,binaryRep,fpos):
  179. local=[0,0,0]
  180. segNode=binaryRep['node']
  181. segNode.TransformPointFromWorld(fpos,local)
  182. mask=binaryRep['mask']
  183. maskWorldToImageMatrix=vtk.vtkMatrix4x4()
  184. mask.GetWorldToImageMatrix(maskWorldToImageMatrix)
  185. local.append(1)
  186. maskIJK=maskWorldToImageMatrix.MultiplyPoint(local)
  187. #mask IJK is in image coordinates. However, binaryLabelMap is a truncated
  188. #version of vtkImageData, so more work is required
  189. maskIJK=maskIJK[0:3]#skip last (dummy) coordinate
  190. maskIJK=[r*s for r,s in zip(maskIJK,mask.GetSpacing())]
  191. maskIJK=[r+c for r,c in zip(maskIJK,mask.GetOrigin())]
  192. maskI=mask.FindPoint(maskIJK)
  193. try:
  194. return mask.GetPointData().GetScalars().GetTuple1(maskI)
  195. except:
  196. return 0
  197. def rebinSegment(self,refNode,binaryRep):
  198. refIJKtoRAS=vtk.vtkMatrix4x4()
  199. refNode.GetIJKToRASMatrix(refIJKtoRAS)
  200. refImage=refNode.GetImageData()
  201. #create new node for each segment
  202. newImage=vtk.vtkImageData()
  203. newImage.DeepCopy(refNode.GetImageData())
  204. n=newImage.GetNumberOfPoints()
  205. newScalars=newImage.GetPointData().GetScalars()
  206. segNode=binaryRep['node']
  207. mask=binaryRep['mask']
  208. for j in range(0,n):
  209. refIJK=refImage.GetPoint(j)
  210. refIJK=list(refIJK)
  211. refIJK.append(1)
  212. #shift to world coordinates to match global points
  213. refPos=refIJKtoRAS.MultiplyPoint(refIJK)
  214. refPos=refPos[0:3]
  215. fWorld=[0,0,0]
  216. #apply potential transformations
  217. refNode.TransformPointToWorld(refPos,fWorld)
  218. v=self.inMask(binaryRep,fWorld)
  219. #print("[{}] Setting ({}) to: {}\n").format(j,fWorld,v)
  220. newScalars.SetTuple1(j,v)
  221. newNode=slicer.vtkMRMLScalarVolumeNode()
  222. newNode.SetName(segNode.GetName()+'_'+binaryRep['segId'])
  223. newNode.SetOrigin(refNode.GetOrigin())
  224. newNode.SetSpacing(refNode.GetSpacing())
  225. newNode.SetIJKToRASMatrix(refIJKtoRAS)
  226. newNode.SetAndObserveImageData(newImage)
  227. slicer.mrmlScene.AddNode(newNode)
  228. return newNode
  229. def rebinSegmentation(self,segNode,refNode):
  230. log=open(os.path.join(self.baseLog,"rebinSegmentation.log"),"w")
  231. log.write(("rebinNode: {} {}\n").format(segNode.GetName(),refNode.GetName()))
  232. nSeg=segNode.GetSegmentation().GetNumberOfSegments()
  233. ## DEBUG:
  234. #nSeg=1
  235. #n=1000
  236. for i in range(0,nSeg):
  237. #segID
  238. segID=segNode.GetSegmentation().GetNthSegmentID(i)
  239. log.write(("Parsing segment {}").format(segNode.GetSegmentation.GetNthSegment(i).GetName()))
  240. binaryRep={'node':segNode,
  241. 'mask':segNode.GetBinaryLabelmapRepresentation(segID)}
  242. newNode=self.rebinSegment(refNode,binaryRep)
  243. log.write(("Adding node: {}").format(newNode.GetName()))
  244. log.close()
  245. def rebinSegmentation1(self,segNode,refNode):
  246. logfile="C:\\Users\\studen\\labkeyCache\\log\\resample.log"
  247. print("rebinNode: {} {}\n").format(segNode.GetName(),refNode.GetName())
  248. refImage=refNode.GetImageData()
  249. refIJKtoRAS=vtk.vtkMatrix4x4()
  250. refNode.GetIJKToRASMatrix(refIJKtoRAS)
  251. refRAStoIJK=vtk.vtkMatrix4x4()
  252. refNode.GetRASToIJKMatrix(refRAStoIJK)
  253. nSeg=segNode.GetSegmentation().GetNumberOfSegments()
  254. ## DEBUG:
  255. nSeg=1
  256. for i in range(0,nSeg):
  257. #segID
  258. segID=segNode.GetSegmentation().GetNthSegmentID(i)
  259. binaryRep={'node':segNode,
  260. 'mask': segNode.GetBinaryLabelmapRepresentation(segID)}
  261. mask=binaryRep['mask']
  262. #create new node for each segment
  263. newImage=vtk.vtkImageData()
  264. newImage.DeepCopy(refNode.GetImageData())
  265. newScalars=newImage.GetPointData().GetScalars()
  266. refN=newImage.GetNumberOfPoints()
  267. for k in range(0,refN):
  268. newScalars.SetTuple1(k,0)
  269. maskN=binaryRep['mask'].GetNumberOfPoints()
  270. maskScalars=mask.GetPointData().GetScalars()
  271. maskImageToWorldMatrix=vtk.vtkMatrix4x4()
  272. binaryRep['mask'].GetImageToWorldMatrix(maskImageToWorldMatrix)
  273. for j in range(0,maskN):
  274. if maskScalars.GetTuple1(j)==0:
  275. continue
  276. maskIJK=mask.GetPoint(j)
  277. maskIJK=[r-c for r,c in zip(maskIJK,mask.GetOrigin())]
  278. maskIJK=[r/s for r,s in zip(maskIJK,mask.GetSpacing())]
  279. maskIJK.append(1)
  280. maskPos=maskImageToWorldMatrix.MultiplyPoint(maskIJK)
  281. maskPos=maskPos[0:3]
  282. fWorld=[0,0,0]
  283. #apply segmentation transformation
  284. segNode.TransformPointToWorld(maskPos,fWorld)
  285. refPos=[0,0,0]
  286. #apply potential reference transformations
  287. refNode.TransformPointFromWorld(fWorld,refPos)
  288. refPos.append(1)
  289. refIJK=refRAStoIJK.MultiplyPoint(refPos)
  290. refIJK=refIJK[0:3]
  291. i1=newImage.FindPoint(refIJK)
  292. if i1<0:
  293. continue
  294. if i1<refN:
  295. newScalars.SetTuple1(i1,1)
  296. newNode=slicer.vtkMRMLScalarVolumeNode()
  297. newNode.SetName(segNode.GetName()+'_'+segNode.GetSegmentation().GetNthSegmentID(i))
  298. newNode.SetOrigin(refNode.GetOrigin())
  299. newNode.SetSpacing(refNode.GetSpacing())
  300. newNode.SetIJKToRASMatrix(refIJKtoRAS)
  301. newNode.SetAndObserveImageData(newImage)
  302. slicer.mrmlScene.AddNode(newNode)
  303. class resampleTest(ScriptedLoadableModuleTest):
  304. """
  305. This is the test case for your scripted module.
  306. Uses ScriptedLoadableModuleTest base class, available at:
  307. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  308. """
  309. def setUp(self):
  310. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  311. """
  312. slicer.mrmlScene.Clear(0)
  313. refNodeName="2SBMIRVolume19"
  314. nodeName="2SMobrVolume19"
  315. transformNodeName="2SMobr_DF"
  316. path="c:\\Users\\studen\\labkeyCache\\dinamic_spect\\Patients\\@files"
  317. refPath=os.path.join(path,"2SBMIR")
  318. refPath=os.path.join(refPath,refNodeName+".nrrd")
  319. slicer.util.loadNodeFromFile(refPath,'VolumeFile')
  320. transformPath=os.path.join(path,"2SMobr")
  321. transformPath=os.path.join(transformPath,transformNodeName+".h5")
  322. slicer.util.loadNodeFromFile(transformPath,'TransformFile')
  323. nodePath=os.path.join(path,"2SMobr")
  324. nodePath=os.path.join(nodePath,nodeName+".nrrd")
  325. slicer.util.loadNodeFromFile(nodePath,'VolumeFile')
  326. self.node=slicer.util.getFirstNodeByName(nodeName)
  327. self.refNode=slicer.util.getFirstNodeByName(refNodeName)
  328. self.transformNode=slicer.util.getFirstNodeByName(transformNodeName)
  329. self.node.SetAndObserveTransformNodeID(self.transformNode.GetID())
  330. self.resampler=resampleLogic(None)
  331. def runTest(self):
  332. """Run as few or as many tests as needed here.
  333. """
  334. self.setUp()
  335. self.test_resample()
  336. def test_resample(self):
  337. """ Ideally you should have several levels of tests. At the lowest level
  338. tests should exercise the functionality of the logic with different inputs
  339. (both valid and invalid). At higher levels your tests should emulate the
  340. way the user would interact with your code and confirm that it still works
  341. the way you intended.
  342. One of the most important features of the tests is that it should alert other
  343. developers when their changes will have an impact on the behavior of your
  344. module. For example, if a developer removes a feature that you depend on,
  345. your test should break so they know that the feature is needed.
  346. """
  347. self.delayDisplay("Starting the test")
  348. #
  349. # first, get some data
  350. #
  351. self.resampler.rebinNode(self.node,self.refNode)
  352. self.delayDisplay('Test passed!')