imageBrowser.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. import os
  2. import unittest
  3. from __main__ import vtk, qt, ctk, slicer
  4. from slicer.ScriptedLoadableModule import *
  5. import json
  6. import datetime
  7. import sys
  8. import nixModule
  9. import pathlib
  10. import chardet
  11. import re
  12. #
  13. # labkeySlicerPythonExtension
  14. #
  15. class imageBrowser(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. self.parent.title = "image Browser"
  22. # TODO make this more human readable by adding spaces
  23. self.parent.categories = ["LabKey"]
  24. self.parent.dependencies = []
  25. self.parent.contributors = ["Andrej Studen (UL/FMF)"]
  26. # replace with "Firstname Lastname (Organization)"
  27. self.parent.helpText = """
  28. Interface to irAEMM files in LabKey
  29. """
  30. self.parent.acknowledgementText = """
  31. Developed within the medical physics research programme
  32. of the Slovenian research agency.
  33. """ # replace with organization, grant and thanks.
  34. #
  35. # labkeySlicerPythonExtensionWidget
  36. #
  37. class imageBrowserWidget(ScriptedLoadableModuleWidget):
  38. """Uses ScriptedLoadableModuleWidget base class, available at:
  39. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  40. """
  41. def setup(self):
  42. print("Setting up imageBrowserWidget")
  43. ScriptedLoadableModuleWidget.setup(self)
  44. # Instantiate and connect widgets ...
  45. self.logic=imageBrowserLogic(self)
  46. self.addInfoSection()
  47. self.addSetupSection()
  48. self.addPatientsSelector()
  49. def addInfoSection(self):
  50. #a python overview of json settings
  51. infoCollapsibleButton = ctk.ctkCollapsibleButton()
  52. infoCollapsibleButton.text = "Info"
  53. self.layout.addWidget(infoCollapsibleButton)
  54. infoLayout = qt.QFormLayout(infoCollapsibleButton)
  55. self.participantField=qt.QLabel("PatientId")
  56. infoLayout.addRow("Participant field:",self.participantField)
  57. self.ctField=qt.QLabel("ctResampled")
  58. infoLayout.addRow("Data field (CT):",self.ctField)
  59. self.petField=qt.QLabel("petResampled")
  60. infoLayout.addRow("Data field (PET):",self.petField)
  61. self.userField=qt.QLabel("Loading")
  62. infoLayout.addRow("User",self.userField)
  63. self.idField=qt.QLabel("Loading")
  64. infoLayout.addRow("ID",self.idField)
  65. #Add logic at some point
  66. #self.logic=imageBrowserLogic(self)
  67. def updatePatientList(self,ids):
  68. self.patientList.clear()
  69. for id in ids:
  70. self.patientList.addItem(id)
  71. def addPatientsSelector(self):
  72. #
  73. # Patients Area
  74. #
  75. patientsCollapsibleButton = ctk.ctkCollapsibleButton()
  76. patientsCollapsibleButton.text = "Patients"
  77. #don't add it yet
  78. self.layout.addWidget(patientsCollapsibleButton)
  79. patientsFormLayout = qt.QFormLayout(patientsCollapsibleButton)
  80. self.patientList=qt.QComboBox()
  81. self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
  82. patientsFormLayout.addRow("Patient:",self.patientList)
  83. self.visitList=qt.QComboBox()
  84. self.visitList.currentIndexChanged.connect(self.onVisitListChanged)
  85. patientsFormLayout.addRow("Visit:",self.visitList)
  86. self.ctCode=qt.QLabel("ctCode")
  87. patientsFormLayout.addRow("CT:",self.ctCode)
  88. self.petCode=qt.QLabel("petCode")
  89. patientsFormLayout.addRow("PET:",self.petCode)
  90. self.patientLoad=qt.QPushButton("Load")
  91. self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
  92. patientsFormLayout.addRow("Load patient",self.patientLoad)
  93. self.patientSave=qt.QPushButton("Save")
  94. self.patientSave.clicked.connect(self.onPatientSaveButtonClicked)
  95. patientsFormLayout.addRow("Save segmentation",self.patientSave)
  96. self.patientClear=qt.QPushButton("Clear")
  97. self.patientClear.clicked.connect(self.onPatientClearButtonClicked)
  98. patientsFormLayout.addRow("Clear patient",self.patientClear)
  99. self.keepCached=qt.QCheckBox("keep Cached")
  100. self.keepCached.setChecked(1)
  101. patientsFormLayout.addRow("Keep cached",self.keepCached)
  102. def addSetupSection(self):
  103. setupCollapsibleButton = ctk.ctkCollapsibleButton()
  104. setupCollapsibleButton.text = "Setup"
  105. self.layout.addWidget(setupCollapsibleButton)
  106. #Form layout (maybe one can think of more intuitive layouts)
  107. setupFormLayout = qt.QFormLayout(setupCollapsibleButton)
  108. self.serverList=qt.QComboBox()
  109. self.serverList.addItem('<Select>')
  110. self.serverList.addItem("astuden")
  111. self.serverList.addItem("adoma")
  112. self.serverList.addItem("kzevnik")
  113. self.serverList.currentIndexChanged.connect(self.onServerListChanged)
  114. setupFormLayout.addRow("Select user:",self.serverList)
  115. self.setupList=qt.QComboBox()
  116. self.setupList.addItem('<Select>')
  117. self.setupList.addItem("limfomiPET_iBrowser.json")
  118. self.setupList.currentIndexChanged.connect(self.onSetupListChanged)
  119. setupFormLayout.addRow("Setup:",self.setupList)
  120. def addReviewSection(self):
  121. #
  122. # Review Area
  123. #
  124. reviewCollapsibleButton = ctk.ctkCollapsibleButton()
  125. reviewCollapsibleButton.text = "Review"
  126. self.layout.addWidget(reviewCollapsibleButton)
  127. self.reviewBoxLayout = qt.QVBoxLayout(reviewCollapsibleButton)
  128. self.reviewFormLayout = qt.QFormLayout()
  129. self.reviewSegment=qt.QComboBox()
  130. self.reviewSegment.currentIndexChanged.connect(\
  131. self.onReviewSegmentChanged)
  132. self.reviewFormLayout.addRow("Selected region:",self.reviewSegment)
  133. self.reviewResult=qt.QComboBox()
  134. sLabel="What do you think about the segmentation:"
  135. self.reviewFormLayout.addRow(sLabel,self.reviewResult)
  136. reviewOptions=['Select','Excellent','Minor deficiencies',\
  137. 'Major deficiencies','Unusable']
  138. for opt in reviewOptions:
  139. self.reviewResult.addItem(opt)
  140. self.aeResult=qt.QComboBox()
  141. aeLabel="Is organ suffering from adverse effect?"
  142. self.reviewFormLayout.addRow(aeLabel,self.aeResult)
  143. aeOptions=['Select','Yes','No']
  144. for opt in aeOptions:
  145. self.aeResult.addItem(opt)
  146. #self.aeResult.setCurrentIndex(0)
  147. self.updateReview=qt.QPushButton("Save")
  148. saLabel="Save segmentation and AE decision for current segment"
  149. self.reviewFormLayout.addRow(saLabel,self.updateReview)
  150. self.updateReview.clicked.connect(self.onUpdateReviewButtonClicked)
  151. self.reviewBoxLayout.addLayout(self.reviewFormLayout)
  152. submitFrame=qt.QGroupBox("Submit data")
  153. self.submitFormLayout=qt.QFormLayout()
  154. self.reviewComment=qt.QTextEdit("this is a test")
  155. self.submitFormLayout.addRow("Comments (optional)",self.reviewComment)
  156. self.submitReviewButton=qt.QPushButton("Submit")
  157. self.submitFormLayout.addRow("Submit to database",\
  158. self.submitReviewButton)
  159. self.submitReviewButton.clicked.connect(\
  160. self.onSubmitReviewButtonClicked)
  161. submitFrame.setLayout(self.submitFormLayout)
  162. submitFrame.setFlat(1)
  163. #submitFrame.setFrameShape(qt.QFrame.StyledPanel)
  164. #submitFrame.setFrameShadow(qt.QFrame.Sunken)
  165. submitFrame.setStyleSheet("background-color:rgba(220,215,180,45)")
  166. self.reviewBoxLayout.addWidget(submitFrame)
  167. def onSetupListChanged(self,i):
  168. status=self.logic.setConfig(self.setupList.currentText)
  169. try:
  170. if status['error']=='FILE NOT FOUND':
  171. print('File {} not found.'.format(self.setupList.currentText))
  172. return
  173. except KeyError:
  174. pass
  175. self.updatePatientList(status['ids'])
  176. self.onPatientListChanged(0)
  177. def onServerListChanged(self,i):
  178. status=self.logic.setServer(self.serverList.currentText)
  179. try:
  180. if status['error']=='KEY ERROR':
  181. self.serverList.setStyleSheet('background-color: violet')
  182. if status['error']=='ID ERROR':
  183. self.serverList.setStyleSheet('background-color: red')
  184. return
  185. except KeyError:
  186. pass
  187. self.idField.setText(status['id'])
  188. self.userField.setText(status['displayName'])
  189. self.serverList.setStyleSheet('background-color: green')
  190. def onPatientListChanged(self,i):
  191. idFilter={'variable':'PatientId',
  192. 'value':self.patientList.currentText,
  193. 'oper':'eq'}
  194. ds=self.logic.getDataset(dbFilter=[idFilter])
  195. seq=[int(row['SequenceNum']) for row in ds['rows']]
  196. self.visitList.clear()
  197. for s in seq:
  198. self.visitList.addItem("Visit "+str(s))
  199. self.onVisitListChanged(0)
  200. def onVisitListChanged(self,i):
  201. try:
  202. s=self.visitList.currentText.split(' ')[1]
  203. except IndexError:
  204. return
  205. print("Visit: Selected item: {}->{}".format(i,s))
  206. idFilter={'variable':'PatientId',\
  207. 'value':self.patientList.currentText,'oper':'eq'}
  208. sFilter={'variable':'SequenceNum','value':s,'oper':'eq'}
  209. ds=self.logic.getDataset(dbFilter=[idFilter,sFilter])
  210. if not len(ds['rows'])==1:
  211. print("Found incorrect number {} of matches for [{}]/[{}]".\
  212. format(len(ds['rows']),\
  213. self.patientList.currentText,s))
  214. row=ds['rows'][0]
  215. #copy row properties for data access
  216. self.currentRow=row
  217. self.petCode.setText(row[self.petField.text])
  218. self.ctCode.setText(row[self.ctField.text])
  219. #self.segmentationCode.setText(row[self.segmentationField.text])
  220. def onPatientLoadButtonClicked(self):
  221. print("Load")
  222. #delegate loading to logic
  223. self.logic.loadImages(self.currentRow,self.keepCached.isChecked())
  224. self.logic.loadSegmentation(self.currentRow)
  225. #self.logic.loadReview(self.currentRow)
  226. #self.logic.loadAE(self.currentRow)
  227. #self.onReviewSegmentChanged()
  228. def onReviewSegmentChanged(self):
  229. pass
  230. def onPatientClearButtonClicked(self):
  231. self.logic.clearVolumesAndSegmentations()
  232. def onPatientSaveButtonClicked(self):
  233. self.logic.saveSegmentation()
  234. def cleanup(self):
  235. pass
  236. def loadLibrary(name):
  237. #utility function to load nix library from git
  238. fwrapper=nixModule.getWrapper('nixWrapper.py')
  239. p=pathlib.Path(fwrapper)
  240. sys.path.append(str(p.parent))
  241. import nixWrapper
  242. return nixWrapper.loadLibrary(name)
  243. #
  244. # imageBrowserLogic
  245. #
  246. class imageBrowserLogic(ScriptedLoadableModuleLogic):
  247. """This class should implement all the actual
  248. computation done by your module. The interface
  249. should be such that other python code can import
  250. this class and make use of the functionality without
  251. requiring an instance of the Widget.
  252. Uses ScriptedLoadableModuleLogic base class, available at:
  253. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  254. """
  255. def __init__(self,parent=None):
  256. ScriptedLoadableModuleLogic.__init__(self, parent)
  257. print('imageBrowserLogic loading')
  258. if not parent==None:
  259. #use layout and data from parent widget
  260. self.parent=parent
  261. fhome=os.path.expanduser('~')
  262. fsetup=os.path.join(fhome,'.labkey','setup.json')
  263. try:
  264. with open(fsetup) as f:
  265. self.setup=json.load(f)
  266. except FileNotFoundError:
  267. self.setup={}
  268. try:
  269. pt=self.setup['paths']
  270. except KeyError:
  271. self.setup['paths']={}
  272. lName='labkeyInterface'
  273. loadLibrary(lName)
  274. import labkeyInterface
  275. import labkeyDatabaseBrowser
  276. import labkeyFileBrowser
  277. self.network=labkeyInterface.labkeyInterface()
  278. self.dbBrowser=labkeyDatabaseBrowser
  279. self.fBrowser=labkeyFileBrowser
  280. self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
  281. if not os.path.isdir(self.tempDir):
  282. os.mkdir(self.tempDir)
  283. print('imageBrowserLogic setup complete')
  284. def setServer(self,serverName):
  285. #additional way of setting the labkey network interface
  286. #if no parent was provided in logic initialization (stand-alone mode)
  287. status={}
  288. fileName="NONE"
  289. if serverName=="astuden":
  290. fileName="astuden.json"
  291. if serverName=="adoma":
  292. fileName="adoma.json"
  293. if serverName=="kzevnik":
  294. fileName="kzevnik.json"
  295. if fileName=="NONE":
  296. print("No path was associated with server {}".format(serverName))
  297. status['error']='KEY ERROR'
  298. return status
  299. fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
  300. self.network.init(fconfig)
  301. self.remoteId=self.network.getUserId()
  302. if self.remoteId==None:
  303. status['error']='ID ERROR'
  304. return status
  305. status['displayName']=self.remoteId['displayName']
  306. status['id']=self.remoteId['id']
  307. #reset db and fb (they are thin classes anyhow)
  308. self.db=self.dbBrowser.labkeyDB(self.network)
  309. self.fb=self.fBrowser.labkeyFileBrowser(self.network)
  310. return status
  311. def setConfig(self,configName):
  312. status={}
  313. fileName=os.path.join(os.path.expanduser('~'),'.labkey',configName)
  314. if not os.path.isfile(fileName):
  315. status['error']='FILE NOT FOUND'
  316. return status
  317. with open(fileName,'r') as f:
  318. self.isetup=json.load(f)
  319. #self.project=self.isetup['project']
  320. #"iPNUMMretro/Study"
  321. #self.schema='study'
  322. #self.dataset=self.isetup['query']
  323. ds=self.getDataset()
  324. ids=[row[self.isetup['participantField']] for row in ds['rows']]
  325. status['ids']=list(set(ids))
  326. return status
  327. def getDataset(self,name="Imaging",dbFilter=[]):
  328. dset=self.isetup['datasets'][name]
  329. project=dset['project']
  330. schema=dset['schema']
  331. query=dset['query']
  332. try:
  333. return self.db.selectRows(project,schema,query, \
  334. dbFilter,dset['view'])
  335. except KeyError:
  336. return self.db.selectRows(project,schema,query,dbFilter)
  337. def loadImage(self,iData):
  338. #unpack iData
  339. idx=iData['idx']
  340. path=iData['path']
  341. keepCached=iData['keepCached']
  342. dset=self.isetup['datasets'][iData['dataset']]
  343. localPath=os.path.join(self.tempDir,path[-1])
  344. if not os.path.isfile(localPath):
  345. #download from server
  346. remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
  347. if not self.fb.entryExists(remotePath):
  348. print("Failed to get {}".format(remotePath))
  349. return
  350. self.fb.readFileToFile(remotePath,localPath)
  351. properties={}
  352. filetype='VolumeFile'
  353. #make sure segmentation gets loaded as a labelmap
  354. if idx=="Segmentation":
  355. filetype='SegmentationFile'
  356. #properties["labelmap"]=1
  357. self.volumeNode[idx]=slicer.util.loadNodeFromFile(localPath,
  358. filetype=filetype,properties=properties)
  359. if not keepCached:
  360. os.remove(localPath)
  361. def loadImages(self,row,keepCached):
  362. #fields={'ctResampled':True,'petResampled':False}
  363. fields={"CT":self.parent.ctField.text,\
  364. "PET":self.parent.petField.text}
  365. path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
  366. relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
  367. self.volumeNode={}
  368. for f in relativePaths:
  369. iData={'idx':f,'path':relativePaths[f],
  370. 'keepCached':keepCached,'dataset':'Imaging'}
  371. self.loadImage(iData)
  372. #mimic abdominalCT standardized window setting
  373. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  374. SetWindowLevel(1400, -500)
  375. #set colormap for PET to PET-Heat (this is a verbatim setting from
  376. #the Volumes->Display->Lookup Table colormap identifier)
  377. self.volumeNode['PET'].GetScalarVolumeDisplayNode().\
  378. SetAndObserveColorNodeID(\
  379. slicer.util.getNode('PET-Heat').GetID())
  380. slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
  381. foreground=self.volumeNode['PET'],foregroundOpacity=0.5,fit=True)
  382. def loadSegmentation(self,row):
  383. userFilter={'variable':'User','value':'{}'.format(self.remoteId['id']),
  384. 'oper':'eq'}
  385. pField=self.isetup['participantField']
  386. idFilter={'variable':pField,'value':row[pField],'oper':'eq'}
  387. visitFilter={'variable':'visitCode','value':row['visitCode'],'oper':'eq'}
  388. ds=self.getDataset(name='SegmentationsMaster',
  389. dbFilter=[idFilter,visitFilter,userFilter])
  390. if len(ds['rows'])>1:
  391. print('Multiple segmentations found!')
  392. return
  393. if len(ds['rows'])==1:
  394. #update self.segmentationEntry
  395. self.segmentationEntry=ds['rows'][0]
  396. self.segmentationEntry['origin']='database'
  397. self.loadSegmentationFromEntry()
  398. return
  399. #create new segmentation
  400. self.createSegmentation(row)
  401. def getSegmentationPath(self):
  402. path=[self.isetup['imageDir'],
  403. self.segmentationEntry['patientCode'],
  404. self.segmentationEntry['visitCode']]
  405. path.append('Segmentations')
  406. return path
  407. def loadSegmentationFromEntry(self):
  408. #compile path
  409. entry=self.segmentationEntry
  410. path=self.getSegmentationPath()
  411. path.append(entry['latestFile'])
  412. iData={'idx':'Segmentation','path':path,
  413. 'keepCached':0,'dataset':'SegmentationsMaster'}
  414. self.loadImage(iData)
  415. def saveSegmentation(self):
  416. #get the latest key by adding an entry to SegmentationList
  417. copyFields=['ParticipantId','patientCode','visitCode','User']
  418. outRow={x:self.segmentationEntry[x] for x in copyFields}
  419. sList=self.isetup['datasets']['SegmentationsList']
  420. resp=self.db.modifyRows('insert',sList['project'],
  421. sList['schema'],sList['query'],[outRow])
  422. encoding=chardet.detect(resp)['encoding']
  423. respJSON=json.loads(resp.decode(encoding))
  424. outRow=respJSON['rows'][0]
  425. #print(outRow)
  426. #construct file name with known key
  427. uName=re.sub(' ','_',self.remoteId['displayName'])
  428. fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
  429. self.segmentationEntry['patientCode'],
  430. self.segmentationEntry['visitCode'],
  431. uName,outRow['Key'])
  432. path=self.getSegmentationPath()
  433. path.append(fName)
  434. self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
  435. #update SegmentationList with know file name
  436. outRow['Segmentation']=fName
  437. self.db.modifyRows('update',sList['project'],
  438. sList['schema'],sList['query'],[outRow])
  439. #update SegmentationsMaster
  440. self.segmentationEntry['latestFile']=fName
  441. self.segmentationEntry['version']=outRow['Key']
  442. des=self.isetup['datasets']['SegmentationsMaster']
  443. op='insert'
  444. if self.segmentationEntry['origin']=='database':
  445. op='update'
  446. self.db.modifyRows(op,des['project'],
  447. des['schema'],des['query'],[self.segmentationEntry])
  448. #since we loaded a version, origin should be set to dataset
  449. self.segmentationEntry['origin']='dataset'
  450. def saveNode(self,node,dataset,path):
  451. fName=path[-1]
  452. localPath=os.path.join(self.tempDir,fName)
  453. slicer.util.saveNode(node,localPath)
  454. dset=self.isetup['datasets'][dataset]
  455. #exclude file name when building directory structure
  456. remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
  457. remotePath+='/'+fName
  458. self.fb.writeFileToFile(localPath,remotePath)
  459. #add entry to segmentation list
  460. def createSegmentation(self,entry):
  461. #create segmentation entry for database update
  462. #note that origin is not set to database
  463. copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
  464. self.segmentationEntry={x:entry[x] for x in copyFields}
  465. self.segmentationEntry['User']=self.remoteId['id']
  466. self.segmentationEntry['origin']='created'
  467. self.segmentationEntry['version']=-1111
  468. #create a segmentation node
  469. uName=re.sub(' ','_',self.remoteId['displayName'])
  470. segNode=slicer.vtkMRMLSegmentationNode()
  471. self.volumeNode['Segmentation']=segNode
  472. segNode.SetName('Segmentation_{}_{}_{}'.
  473. format(entry['patientCode'],entry['visitCode'],uName))
  474. slicer.mrmlScene.AddNode(segNode)
  475. segNode.CreateDefaultDisplayNodes()
  476. segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['PET'])
  477. segNode.GetSegmentation().AddEmptySegment("Lesion","Lesion")
  478. def clearVolumesAndSegmentations(self):
  479. nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
  480. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  481. res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
  482. #self.segmentationNode=None
  483. #self.reviewResult={}
  484. #self.aeList={}
  485. class imageBrowserTest(ScriptedLoadableModuleTest):
  486. """
  487. This is the test case for your scripted module.
  488. Uses ScriptedLoadableModuleTest base class, available at:
  489. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  490. """
  491. def setup(self):
  492. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  493. """
  494. slicer.mrmlScene.Clear(0)
  495. def runTest(self):
  496. """Run as few or as many tests as needed here.
  497. """
  498. self.setUp()
  499. self.test_irAEMMBrowser()
  500. def test_irAEMMBrowser(self):
  501. """ Ideally you should have several levels of tests. At the lowest level
  502. tests sould exercise the functionality of the logic with different inputs
  503. (both valid and invalid). At higher levels your tests should emulate the
  504. way the user would interact with your code and confirm that it still works
  505. the way you intended.
  506. One of the most important features of the tests is that it should alert other
  507. developers when their changes will have an impact on the behavior of your
  508. module. For example, if a developer removes a feature that you depend on,
  509. your test should break so they know that the feature is needed.
  510. """
  511. self.delayDisplay("Starting the test")
  512. #
  513. # first, get some data
  514. #