cardiacSPECT.py 33 KB

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