cardiacSPECT.py 32 KB

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