cardiacSPECT.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. import os
  2. import sys
  3. import unittest
  4. import vtk, qt, ctk, slicer
  5. from slicer.ScriptedLoadableModule import *
  6. import logging
  7. import parseDicom
  8. import vtkInterface as vi
  9. import fileIO
  10. import slicer
  11. import numpy as np
  12. import slicerNetwork
  13. import resample
  14. #
  15. # cardiacSPECT
  16. #
  17. class cardiacSPECT(ScriptedLoadableModule):
  18. """Uses ScriptedLoadableModule base class, available at:
  19. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  20. """
  21. def __init__(self, parent):
  22. ScriptedLoadableModule.__init__(self, parent)
  23. parent.title = "Cardiac SPECT"
  24. parent.categories = ["Examples"]
  25. parent.dependencies = []
  26. parent.contributors = ["Andrej Studen (FMF/JSI)"] # replace with "Firstname Lastname (Org)"
  27. parent.helpText = """
  28. Load dynamic cardiac SPECT data to Slicer
  29. """
  30. parent.acknowledgementText = """
  31. This module was developed within the frame of the ARRS sponsored medical
  32. physics research programe to investigate quantitative measurements of cardiac
  33. function using sestamibi-like tracers
  34. """ # replace with organization, grant and thanks.
  35. self.parent.helpText += self.getDefaultModuleDocumentationLink()
  36. self.parent = parent
  37. #
  38. # cardiacSPECTWidget
  39. #
  40. class cardiacSPECTWidget(ScriptedLoadableModuleWidget):
  41. """Uses ScriptedLoadableModuleWidget base class, available at:
  42. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  43. """
  44. def setup(self):
  45. ScriptedLoadableModuleWidget.setup(self)
  46. self.selectRemote=fileIO.remoteFileSelector()
  47. try:
  48. self.network=slicer.modules.labkeySlicerPythonExtensionWidget.network
  49. except:
  50. self.network=slicerNetwork.labkeyURIHandler()
  51. self.logic=cardiacSPECTLogic(self)
  52. self.logic.setURIHandler(self.network)
  53. self.selectRemote.setMaster(self)
  54. # Instantiate and connect widgets ...
  55. dataButton = ctk.ctkCollapsibleButton()
  56. dataButton.text = "Data"
  57. self.layout.addWidget(dataButton)
  58. # Layout within the sample collapsible button
  59. dataFormLayout = qt.QFormLayout(dataButton)
  60. #pathGuess="file://"+os.environ['HOME']+"/SPECT"
  61. pathGuess="labkey://" + "dinamic_spect/%40files/Dinamika%20test2/SPECT_Dinamika_Rekonstruirano"
  62. self.dataPath=qt.QLineEdit(pathGuess)
  63. dataFormLayout.addRow("Data location",self.dataPath)
  64. self.remotePath=qt.QLineEdit();
  65. dataFormLayout.addRow('Remote Path', self.remotePath)
  66. self.remotePath.textChanged.connect(self.onRemotePathTextChanged)
  67. browseButton = qt.QPushButton("Browse local")
  68. browseButton.toolTip="Set file location"
  69. dataFormLayout.addRow("Select local",browseButton)
  70. browseButton.connect('clicked(bool)',self.onBrowseButtonClicked)
  71. browseRemoteButton = qt.QPushButton("Browse remote")
  72. browseRemoteButton.toolTip="Set remote location"
  73. dataFormLayout.addRow("Select remote",browseRemoteButton)
  74. browseRemoteButton.connect('clicked(bool)',self.onRemoteBrowseButtonClicked)
  75. dataLoadButton = qt.QPushButton("Load")
  76. dataLoadButton.toolTip="Load data from DICOM"
  77. dataFormLayout.addRow("Data",dataLoadButton)
  78. dataLoadButton.connect('clicked(bool)',self.onDataLoadButtonClicked)
  79. self.dataLoadButton = dataLoadButton
  80. self.patientId=qt.QLineEdit();
  81. dataFormLayout.addRow('Patient ID', self.patientId)
  82. self.refPatientId=qt.QLineEdit();
  83. dataFormLayout.addRow('Reference Patient ID', self.refPatientId)
  84. patientLoadButton = qt.QPushButton("Load")
  85. patientLoadButton.toolTip="Load data from DICOM"
  86. dataFormLayout.addRow("Load Patient",patientLoadButton)
  87. patientLoadButton.clicked.connect(self.onPatientLoadButtonClicked)
  88. loadSegmentationButton = qt.QPushButton("Load")
  89. loadSegmentationButton.toolTip="Load segmentation from server"
  90. dataFormLayout.addRow("Load Segmentation",loadSegmentationButton)
  91. loadSegmentationButton.clicked.connect(self.onLoadSegmentationButtonClicked)
  92. saveVolumeButton = qt.QPushButton("Save")
  93. saveVolumeButton.toolTip="Save volume to NRRD"
  94. dataFormLayout.addRow("Volume",saveVolumeButton)
  95. saveVolumeButton.clicked.connect(self.onSaveVolumeButtonClicked)
  96. saveSegmentationButton = qt.QPushButton("Save")
  97. saveSegmentationButton.toolTip="Save segmentation to NRRD"
  98. dataFormLayout.addRow("Segmentation",saveSegmentationButton)
  99. saveSegmentationButton.clicked.connect(self.onSaveSegmentationButtonClicked)
  100. saveTransformationButton = qt.QPushButton("Save")
  101. saveTransformationButton.toolTip="Save transformation to NRRD"
  102. dataFormLayout.addRow("Transformation",saveTransformationButton)
  103. saveTransformationButton.clicked.connect(self.onSaveTransformationButtonClicked)
  104. transformNodeButton = qt.QPushButton("Transform Node")
  105. transformNodeButton.toolTip="Transform node with patient based transform"
  106. dataFormLayout.addRow("Transform Nodes",transformNodeButton)
  107. transformNodeButton.clicked.connect(self.onTransformNodeButtonClicked)
  108. # Add vertical spacer
  109. self.layout.addStretch(1)
  110. #addFrameButton=qt.QPushButton("Add Frame")
  111. #addFrameButton.toolTip="Add frame to VTK"
  112. #dataFormLayout.addWidget(addFrameButton)
  113. #addFrameButton.connect('clicked(bool)',self.onAddFrameButtonClicked)
  114. #addCTButton=qt.QPushButton("Add CT")
  115. #addCTButton.toolTip="Add CT to VTK"
  116. #dataFormLayout.addWidget(addCTButton)
  117. #addCTButton.connect('clicked(bool)',self.onAddCTButtonClicked)
  118. #
  119. # Parameters Area
  120. #
  121. parametersCollapsibleButton = ctk.ctkCollapsibleButton()
  122. parametersCollapsibleButton.text = "Parameters"
  123. self.layout.addWidget(parametersCollapsibleButton)
  124. # Layout within the dummy collapsible button
  125. parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton)
  126. #
  127. # check box to trigger taking screen shots for later use in tutorials
  128. #
  129. hbox1=qt.QHBoxLayout()
  130. frameLabel = qt.QLabel()
  131. frameLabel.setText("Select frame")
  132. hbox1.addWidget(frameLabel)
  133. self.time_frame_select=qt.QSlider(qt.Qt.Horizontal)
  134. self.time_frame_select.valueChanged.connect(self.onTimeFrameSelect)
  135. #self.time_frame_select.connect('valueChanged()', self.onTimeFrameSelect)
  136. self.time_frame_select.setMinimum(0)
  137. self.time_frame_select.setMaximum(0)
  138. self.time_frame_select.setValue(0)
  139. self.time_frame_select.setTickPosition(qt.QSlider.TicksBelow)
  140. self.time_frame_select.setTickInterval(5)
  141. self.time_frame_select.toolTip = "Select the time frame"
  142. hbox1.addWidget(self.time_frame_select)
  143. parametersFormLayout.addRow(hbox1)
  144. hbox2 = qt.QHBoxLayout()
  145. meanROILabel = qt.QLabel()
  146. meanROILabel.setText("MeanROI")
  147. hbox2.addWidget(meanROILabel)
  148. self.meanROIVolume = qt.QLineEdit()
  149. self.meanROIVolume.setText("testVolume15")
  150. hbox2.addWidget(self.meanROIVolume)
  151. self.meanROISegment = qt.QLineEdit()
  152. self.meanROISegment.setText("Segment_1")
  153. hbox2.addWidget(self.meanROISegment)
  154. computeMeanROI = qt.QPushButton("Compute mean ROI")
  155. computeMeanROI.connect('clicked(bool)',self.onComputeMeanROIClicked)
  156. hbox2.addWidget(computeMeanROI)
  157. self.meanROIResult = qt.QLineEdit()
  158. self.meanROIResult.setText("0")
  159. hbox2.addWidget(self.meanROIResult)
  160. parametersFormLayout.addRow(hbox2)
  161. #row 3
  162. hbox3 = qt.QHBoxLayout()
  163. drawTimePlot=qt.QPushButton("Draw ROI time plot")
  164. drawTimePlot.connect('clicked(bool)',self.onDrawTimePlotClicked)
  165. hbox3.addWidget(drawTimePlot)
  166. parametersFormLayout.addRow(hbox3)
  167. #dataFormLayout.addWidget(hbox)
  168. #row 4
  169. hbox4 = qt.QHBoxLayout()
  170. countSegments=qt.QPushButton("Count segmentation segments")
  171. countSegments.connect('clicked(bool)',self.onCountSegmentsClicked)
  172. hbox4.addWidget(countSegments)
  173. self.countSegmentsDisplay=qt.QLineEdit()
  174. self.countSegmentsDisplay.setText("0")
  175. hbox4.addWidget(self.countSegmentsDisplay)
  176. parametersFormLayout.addRow(hbox4)
  177. #
  178. # Apply Button
  179. #
  180. self.applyButton = qt.QPushButton("Apply")
  181. self.applyButton.toolTip = "Run the algorithm."
  182. self.applyButton.enabled = False
  183. parametersFormLayout.addRow(self.applyButton)
  184. # connections
  185. self.applyButton.connect('clicked(bool)', self.onApplyButton)
  186. #self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
  187. #self.outputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
  188. # Add vertical spacer
  189. self.layout.addStretch(1)
  190. self.resetPosition=1
  191. def cleanup(self):
  192. pass
  193. def onApplyButton(self):
  194. pass
  195. #logic = cardiacSPECTLogic()
  196. #imageThreshold = self.imageThresholdSliderWidget.value
  197. def onBrowseButtonClicked(self):
  198. startDir=self.dataPath.text
  199. inputDir=qt.QFileDialog.getExistingDirectory(None,
  200. 'Select DICOM directory',startDir)
  201. self.dataPath.setText("file://"+inputDir)
  202. def onRemoteBrowseButtonClicked(self):
  203. self.selectRemote.show()
  204. def onDataLoadButtonClicked(self):
  205. self.logic.loadData(self)
  206. def onRemotePathTextChanged(self,str):
  207. self.dataPath.setText('labkey://'+str)
  208. def onTimeFrameSelect(self):
  209. it=self.time_frame_select.value
  210. selectionNode = slicer.app.applicationLogic().GetSelectionNode()
  211. print("Propagating CT volume")
  212. nodeName=self.patientId.text+'CT'
  213. node=slicer.mrmlScene.GetFirstNodeByName(nodeName)
  214. selectionNode.SetReferenceActiveVolumeID(node.GetID())
  215. if self.resetPosition==1:
  216. self.resetPosition=0
  217. slicer.app.applicationLogic().PropagateVolumeSelection(1)
  218. else:
  219. slicer.app.applicationLogic().PropagateVolumeSelection(0)
  220. print("Propagating SPECT volume")
  221. nodeName=self.patientId.text+'Volume'+str(it)
  222. node=slicer.mrmlScene.GetFirstNodeByName(nodeName)
  223. selectionNode.SetSecondaryVolumeID(node.GetID())
  224. slicer.app.applicationLogic().PropagateForegroundVolumeSelection(0)
  225. node.GetDisplayNode().SetAndObserveColorNodeID('vtkMRMLColorTableNodeRed')
  226. lm = slicer.app.layoutManager()
  227. sID=['Red','Yellow','Green']
  228. for s in sID:
  229. sliceLogic = lm.sliceWidget(s).sliceLogic()
  230. compositeNode = sliceLogic.GetSliceCompositeNode()
  231. compositeNode.SetForegroundOpacity(0.5)
  232. #make sure the viewer is matched to the volume
  233. print("Done")
  234. #to access sliceLogic (slice control) use
  235. #lcol=slicer.app.layoutManager().mrmlSliceLogics() (vtkCollection)
  236. #vtkMRMLSliceLogic are named by colors (Red,Green,Blue)
  237. def onComputeMeanROIClicked(self):
  238. s=self.logic.meanROI(self.meanROIVolume.text,self.meanROISegment.text)
  239. self.meanROIResult.setText(str(s))
  240. def onDrawTimePlotClicked(self):
  241. n=self.time_frame_select.maximum+1
  242. ft=self.logic.frame_time
  243. #find number of segments
  244. ns = self.logic.countSegments()
  245. #add the chart node
  246. cn = slicer.mrmlScene.AddNode(slicer.vtkMRMLChartNode())
  247. for j in range(0,ns):
  248. #add node for data
  249. dn = slicer.mrmlScene.AddNode(slicer.vtkMRMLDoubleArrayNode())
  250. dn.SetSize(n)
  251. dn.SetName(self.logic.getSegmentName(j))
  252. dt=0;
  253. t0=0;
  254. for i in range(0,n):
  255. vol=self.patientId.text+"Volume"+str(i)
  256. fx=ft[i]
  257. fy=self.logic.meanROI(vol,j)
  258. dt=2*ft[i]-t0
  259. t0+=dt
  260. dn.SetValue(i, 0, fx)
  261. dn.SetValue(i, 1, fy/dt)
  262. dn.SetValue(i, 2, 0)
  263. print("[{0} at {1:.2f}:{2:.2f}]".format(vol,fx,fy))
  264. #fish the number of the segment
  265. cn.AddArray(self.logic.getSegmentName(j), dn.GetID())
  266. cn.SetProperty('default', 'title', 'ROI time plot')
  267. cn.SetProperty('default', 'xAxisLabel', 'time [ms]')
  268. cn.SetProperty('default', 'yAxisLabel', 'Activity (arb)')
  269. #update the chart node
  270. cvns = slicer.mrmlScene.GetNodesByClass('vtkMRMLChartViewNode')
  271. if cvns.GetNumberOfItems() == 0:
  272. cvn = slicer.mrmlScene.AddNode(slicer.vtkMRMLChartViewNode())
  273. else:
  274. cvn = cvns.GetItemAsObject(0)
  275. cvn.SetChartNodeID(cn.GetID())
  276. def onCountSegmentsClicked(self):
  277. self.countSegmentsDisplay.setText(self.logic.countSegments())
  278. def onPatientLoadButtonClicked(self):
  279. self.logic.loadPatient(self,self.patientId.text)
  280. def onLoadSegmentationButtonClicked(self):
  281. self.logic.loadSegmentation(self.patientId.text)
  282. def onSaveVolumeButtonClicked(self):
  283. self.logic.storeVolumeNodes(self.patientId.text,
  284. self.time_frame_select.minimum,self.time_frame_select.maximum)
  285. def onSaveSegmentationButtonClicked(self):
  286. self.logic.storeSegmentation(self.patientId.text)
  287. def onSaveTransformationButtonClicked(self):
  288. self.logic.storeTransformation(self.patientId.text)
  289. def onTransformNodeButtonClicked(self):
  290. self.logic.applyTransform(self.patientId.text, self.refPatientId.text,
  291. self.time_frame_select.minimum,self.time_frame_select.maximum)
  292. #def onAddFrameButtonClicked(self):
  293. # it=int(self.time_frame_select.text)
  294. # self.logic.addFrame(it)
  295. # def onAddCTButtonClicked(self):
  296. # self.logic.addCT()
  297. #
  298. #
  299. # cardiacSPECTLogic
  300. #
  301. class cardiacSPECTLogic(ScriptedLoadableModuleLogic):
  302. """This class should implement all the actual
  303. computation done by your module. The interface
  304. should be such that other python code can import
  305. this class and make use of the functionality without
  306. requiring an instance of the Widget.
  307. Uses ScriptedLoadableModuleLogic base class, available at:
  308. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  309. """
  310. def __init__(self,parent):
  311. ScriptedLoadableModuleLogic.__init__(self, parent)
  312. self.pd=parseDicom.parseDicomLogic(self)
  313. def setURIHandler(self,net):
  314. self.net=net
  315. self.pd.setURIHandler(net)
  316. def loadData(self,widget):
  317. inputDir=str(widget.dataPath.text)
  318. self.pd.readMasterDirectory(inputDir)
  319. self.frame_data, self.frame_time, self.frame_origin, \
  320. self.frame_pixel_size, self.frame_orientation=self.pd.readNMDirectory(inputDir)
  321. self.ct_data,self.ct_origin,self.ct_pixel_size, \
  322. self.ct_orientation=self.pd.readCTDirectory(inputDir)
  323. self.ct_orientation=vi.completeOrientation(self.ct_orientation)
  324. self.frame_orientation=vi.completeOrientation(self.frame_orientation)
  325. self.addCT('test')
  326. self.addFrames('test')
  327. widget.time_frame_select.setMaximum(self.frame_data.shape[3]-1)
  328. #additional message via qt
  329. qt.QMessageBox.information(
  330. slicer.util.mainWindow(),
  331. 'Slicer Python','Data loaded')
  332. def formatToGetfileSpec(self,path):
  333. #path=self.net.GetRelativePathFromLabkeyPath(path)
  334. path="labkey://"+path
  335. return path
  336. def loadPatient(self,widget,patientId):
  337. print("Loading {}").format(patientId)
  338. ds=self.net.loadDataset("dinamic_spect/Patients","Imaging")
  339. for r in ds['rows']:
  340. if r['aliasID']==patientId:
  341. visit=r
  342. print visit
  343. dicoms=self.net.loadDataset("Test/Transfer","Imaging")
  344. for r in dicoms['rows']:
  345. if not r['PatientId']==visit['aliasID']:
  346. continue
  347. if abs(r['SequenceNum']-float(visit['nmMaster']))<0.1:
  348. path=r['_labkeyurl_Series']
  349. masterPath=path[:path.rfind('/')]
  350. #masterPath="labkey://"+path
  351. if abs(r['SequenceNum']-float(visit['nmCorrData']))<0.1:
  352. path=r['_labkeyurl_Series']
  353. nmPath=path[:path.rfind('/')]
  354. #nmPath="labkey://"+path
  355. if abs(r['SequenceNum']-float(visit['ctData']))<0.1:
  356. path=r['_labkeyurl_Series']
  357. ctPath=path[:path.rfind('/')]
  358. #ctPath="labkey://"+path
  359. self.pd.readMasterDirectory(self.formatToGetfileSpec(masterPath))
  360. self.frame_data, self.frame_time, self.frame_origin, \
  361. self.frame_pixel_size, self.frame_orientation=self.pd.readNMDirectory(self.formatToGetfileSpec(nmPath))
  362. self.ct_data,self.ct_origin,self.ct_pixel_size, \
  363. self.ct_orientation=self.pd.readCTDirectory(self.formatToGetfileSpec(ctPath))
  364. self.ct_orientation=vi.completeOrientation(self.ct_orientation)
  365. self.frame_orientation=vi.completeOrientation(self.frame_orientation)
  366. self.addCT(patientId)
  367. self.addFrames(patientId)
  368. widget.time_frame_select.setMaximum(self.frame_data.shape[3]-1)
  369. def loadSegmentation(self,patientId):
  370. project="dinamic_spect/Patients"
  371. relativePath=project+'/@files/'+patientId
  372. segName="Heart"
  373. labkeyFile=relativePath+'/'+segName+'.nrrd'
  374. print ("Remote: {}").format(labkeyFile)
  375. self.net.loadNode(labkeyFile,'SegmentationFile')
  376. def addNode(self,nodeName,v, lpsOrigin, pixel_size, lpsOrientation, dataType):
  377. # if dataType=0 it is CT data, which gets propagated to background an is
  378. #used to fit the view field dimensions
  379. # if dataType=1, it is SPECT data, which gets propagated to foreground
  380. #and is not fit; keeping window set from CT
  381. #nodeName='testVolume'+str(it)
  382. newNode=slicer.vtkMRMLScalarVolumeNode()
  383. newNode.SetName(nodeName)
  384. #pixel_size=[0,0,0]
  385. #pixel_size=v.GetSpacing()
  386. #print(pixel_size)
  387. #origin=[0,0,0]
  388. #origin=v.GetOrigin()
  389. v.SetOrigin([0,0,0])
  390. v.SetSpacing([1,1,1])
  391. ijkToRAS = vtk.vtkMatrix4x4()
  392. #think how to do this with image orientation
  393. rasOrientation=[-lpsOrientation[i] if (i%3 < 2) else lpsOrientation[i]
  394. for i in range(0,len(lpsOrientation))]
  395. rasOrigin=[-lpsOrigin[i] if (i%3<2) else lpsOrigin[i] for i in range(0,len(lpsOrigin))]
  396. for i in range(0,3):
  397. for j in range(0,3):
  398. ijkToRAS.SetElement(i,j,pixel_size[i]*rasOrientation[3*j+i])
  399. ijkToRAS.SetElement(i,3,rasOrigin[i])
  400. newNode.SetIJKToRASMatrix(ijkToRAS)
  401. newNode.SetAndObserveImageData(v)
  402. slicer.mrmlScene.AddNode(newNode)
  403. def addFrames(self,patientId):
  404. #convert data from numpy.array to vtkImageData
  405. #use time point it
  406. print "NFrames: {}".format(self.frame_data.shape[3])
  407. for it in range(0,self.frame_data.shape[3]):
  408. frame_data=self.frame_data[:,:,:,it];
  409. nodeName=patientId+'Volume'+str(it)
  410. self.addNode(nodeName,
  411. vi.numpyToVTK(frame_data,frame_data.shape),
  412. self.frame_origin,
  413. self.frame_pixel_size,
  414. self.frame_orientation,1)
  415. def addCT(self,patientId):
  416. nodeName=patientId+'CT'
  417. self.addNode(nodeName,
  418. #vi.numpyToVTK3D(self.ct_data,
  419. # self.ct_origin,self.ct_pixel_size),
  420. vi.numpyToVTK(self.ct_data,self.ct_data.shape),
  421. self.ct_origin,self.ct_pixel_size,
  422. self.ct_orientation,0)
  423. def rFromI(i,volumeNode):
  424. ijkToRas = vtk.vtkMatrix4x4()
  425. volumeNode.GetIJKToRASMatrix(ijkToRas)
  426. vImage=volumeNode.GetImageData()
  427. i1=list(vImage.GetPoint(i))
  428. i1=i1.append(1)
  429. #ras are global coordinates (in mm)
  430. position_ras=ijkToRas.MultiplyPoint(i1)
  431. return position_ras[0:3]
  432. def IfromR(pos,volumeNode):
  433. fM=vtk.vtkMatrix4x4()
  434. volumeNode.GetRASToIJKMatrix(fM)
  435. fM.MultiplyPoint(pos)
  436. vImage=volumeNode.GetImageData()
  437. #nearest neighbor
  438. return vImage.FindPoint(pos[0:3])
  439. def meanROI(self, volName1, i):
  440. s=0
  441. #get the segmentation mask
  442. fNode=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode").GetItemAsObject(0)
  443. print "Found segmentation node: {}".format(fNode.GetName())
  444. segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
  445. #no python bindings for vtkSegmentation
  446. #if segNode.GetSegmentation().GetNumberOfSegments()==0 :
  447. # print("No segments available")
  448. # return 0
  449. #edit here to change for more segments
  450. segment=segNode.GetSegmentation().GetNthSegmentID(int(i))
  451. mask = segNode.GetBinaryLabelmapRepresentation(segment)
  452. if mask==None:
  453. print("Segment {} not found".format(segment))
  454. return s
  455. print "Got mask for segment {}".format(segment)
  456. #get mask at (x,y,z)
  457. #mask.GetPointData().GetScalars().GetTuple1(mask.FindPoint([x,y,z]))
  458. #get the image data
  459. dataNode=slicer.mrmlScene.GetFirstNodeByName(volName1)
  460. dataImage=dataNode.GetImageData()
  461. # use IJK2RAS to get global coordinates
  462. ijkToRas = vtk.vtkMatrix4x4()
  463. dataNode.GetIJKToRASMatrix(ijkToRas)
  464. #iterate over volume pixelData
  465. n=dataImage.GetNumberOfPoints()
  466. extent=mask.GetExtent()
  467. fM=vtk.vtkMatrix4x4()
  468. mask.GetWorldToImageMatrix(fM)
  469. ns=0
  470. for i in range(0,n):
  471. #get global coordinates of point i
  472. [ix,iy,iz]=dataImage.GetPoint(i)
  473. position_ijk=[ix, iy, iz, 1]
  474. #ras are global coordinates (in mm)
  475. position_ras=ijkToRas.MultiplyPoint(position_ijk)
  476. fpos=[int(np.round(x)) for x in fM.MultiplyPoint(position_ras)]
  477. outOfRange=False
  478. for k in range(0,3):
  479. if fpos[k]<extent[2*k] or fpos[k]>extent[2*k+1]:
  480. outOfRange=True
  481. break;
  482. if outOfRange:
  483. continue
  484. #find point in mask with the same global coordinates
  485. maskValue=mask.GetPointData().GetScalars().GetTuple1(mask.ComputePointId(fpos[0:3]))
  486. if maskValue == 0:
  487. continue
  488. #use maskValue to project ROI data
  489. s+=maskValue*dataImage.GetPointData().GetScalars().GetTuple1(i)
  490. ns+=1
  491. return s/ns
  492. def countSegments(self):
  493. segNodeList=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode")
  494. if segNodeList.GetNumberOfItems()==0:
  495. return 0
  496. fNode=segNodeList.GetItemAsObject(0)
  497. segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
  498. if fNode==None:
  499. return 0
  500. return segNode.GetSegmentation().GetNumberOfSegments()
  501. def getSegmentName(self,i):
  502. segNodeList=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode")
  503. if segNodeList.GetNumberOfItems()==0:
  504. return "NONE"
  505. fNode=segNodeList.GetItemAsObject(0)
  506. segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
  507. if fNode==None:
  508. return "NONE"
  509. return segNode.GetSegmentation().GetSegment(segNode.GetSegmentation().GetNthSegmentID(i)).GetName()
  510. def storeNodeRemote(self,relativePath,nodeName):
  511. labkeyPath=self.pd.net.GetLabkeyPathFromRelativePath(relativePath)
  512. print ("Remote: {}").format(labkeyPath)
  513. #checks if exists
  514. self.pd.net.mkdir(labkeyPath)
  515. localPath=self.pd.net.GetLocalPathFromRelativePath(relativePath)
  516. localPath.replace('/',os.path.sep)
  517. node=slicer.mrmlScene.GetFirstNodeByName(nodeName)
  518. if node==None:
  519. print("Node {} not found").format(nodeName)
  520. return
  521. suffix=".nrrd"
  522. if node.__class__.__name__=="vtkMRMLDoubleArrayNode":
  523. suffix=".mcsv"
  524. if node.__class__.__name__=="vtkMRMLTransformNode":
  525. suffix=".h5"
  526. fileName=nodeName+suffix
  527. file=os.path.join(localPath,fileName)
  528. slicer.util.saveNode(node,file)
  529. print("Stored to: {}").format(file)
  530. f=open(file,"rb")
  531. remoteFile=labkeyPath+'/'+fileName
  532. self.pd.net.put(remoteFile,f.read())
  533. def storeVolumeNodes(self,patientId,n1,n2):
  534. #n1=self.time_frame.minimum;
  535. #n2=self.time_frame.maximum
  536. project="dinamic_spect/Patients"
  537. relativePath=project+'/@files/'+patientId
  538. print("Store CT")
  539. nodeName=patientId+'CT'
  540. self.storeNodeRemote(relativePath,nodeName)
  541. print("Storing NM from {} to {}").format(n1,n2)
  542. n=n2-n1+1
  543. for i in range(n):
  544. it=i+n1
  545. nodeName=patientId+'Volume'+str(it)
  546. self.storeNodeRemote(relativePath,nodeName)
  547. def storeSegmentation(self,patientId):
  548. project="dinamic_spect/Patients"
  549. relativePath=project+'/@files/'+patientId
  550. segNodeName="Heart"
  551. self.storeNodeRemote(relativePath,segNodeName)
  552. def storeInputFunction(self,patientId):
  553. doubleArrayNodeName=patientId+'_Ventricle'
  554. self.storeNodeRemote(relativePath,doubleArrayNodeName)
  555. def storeTransformation(self,patientId):
  556. transformNodeName=patientId+"_DF"
  557. self.storeNodeRemote(relativePath,transformNodeName)
  558. def applyTransform(self, patientId,refPatientId,n1,n2):
  559. transformNodeName=patientId+"_DF"
  560. transformNode=slicer.util.getFirstNodeByName(transformNodeName)
  561. if transformNode==None:
  562. print("Node {} not found").format(transformNodeName)
  563. return
  564. try:
  565. self.resampler.printMe()
  566. return
  567. except AttributeError:
  568. print("Initializing resampler")
  569. self.resampler=resample.resampleLogic(None)
  570. n=n2-n1+1
  571. for i in range(n):
  572. it=i+n1
  573. nodeName=patientId+'Volume'+str(it)
  574. node=slicer.util.getFirstNodeByName(nodeName)
  575. if node==None:
  576. continue
  577. node.SetAndObserveTransformNodeID(transformNode.GetID())
  578. refNodeName=refPatientId+'Volume'+str(it)
  579. refNode=slicer.util.getFirstNodeByName(refNodeName)
  580. if refNode!=None:
  581. self.resampler.rebinNode(node,refNode)
  582. nodeName=patientId+'CT'
  583. node=slicer.util.getFirstNodeByName(nodeName)
  584. if not node==None:
  585. node.SetAndObserveTransformNodeID(transformNode.GetID())
  586. refNodeName=refPatientId+'CT'
  587. refNode=slicer.util.getFirstNodeByName(refNodeName)
  588. if refNode!=None:
  589. self.resampler.rebinNode(node,refNode)
  590. def calculateInputFunction(self):
  591. n=self.frame_data.shape[3]
  592. dns = slicer.mrmlScene.GetNodesByClassByName('vtkMRMLDoubleArrayNode','Ventricle')
  593. if dns.GetNumberOfItems() == 0:
  594. dn = slicer.mrmlScene.AddNode(slicer.vtkMRMLDoubleArrayNode())
  595. dn.SetName('Ventricle')
  596. else:
  597. dn = dns.GetItemAsObject(0)
  598. dn.SetSize(n)
  599. fNodes=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode")
  600. if fNodes.GetNumberOfItems() == 0:
  601. return
  602. fNode=fNodes.GetItemAsObject(0)
  603. segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
  604. segmentation=segNode.GetSegmentation()
  605. juse=-1
  606. for j in range(0,segmentation.GetNumberOfSegments()):
  607. segment=segNode.GetSegmentation().GetNthSegmentID(j)
  608. if segment.GetName()=='Ventricle':
  609. juse=j
  610. break
  611. if juse<0:
  612. print 'Failed to find Ventricle segment'
  613. return
  614. dt=0;
  615. t0=0;
  616. for i in range(0,n):
  617. vol="testVolume"+str(i)
  618. fx=ft[i]
  619. fy=self.logic.meanROI(vol,juse)
  620. dt=2*ft[i]-t0
  621. t0+=dt
  622. dn.SetValue(i, 0, fx)
  623. dn.SetValue(i, 1, fy/dt)
  624. dn.SetValue(i, 2, 0)
  625. print("[{0} at {1:.2f}:{2:.2f}]".format(vol,fx,fy))
  626. class cardiacSPECTTest(ScriptedLoadableModuleTest):
  627. """
  628. This is the test case for your scripted module.
  629. Uses ScriptedLoadableModuleTest base class, available at:
  630. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  631. """
  632. def setUp(self):
  633. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  634. """
  635. slicer.mrmlScene.Clear(0)
  636. def runTest(self):
  637. """Run as few or as many tests as needed here.
  638. """
  639. self.setUp()
  640. self.test_cardiacSPECT1()
  641. def test_cardiacSPECT1(self):
  642. """ Ideally you should have several levels of tests. At the lowest level
  643. tests should exercise the functionality of the logic with different inputs
  644. (both valid and invalid). At higher levels your tests should emulate the
  645. way the user would interact with your code and confirm that it still works
  646. the way you intended.
  647. One of the most important features of the tests is that it should alert other
  648. developers when their changes will have an impact on the behavior of your
  649. module. For example, if a developer removes a feature that you depend on,
  650. your test should break so they know that the feature is needed.
  651. """
  652. self.delayDisplay("Starting the test")
  653. #
  654. # first, get some data
  655. #
  656. self.delayDisplay('Test passed!')