cardiacSPECT.py 26 KB

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