imageBrowser.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  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 = ["dynamicSPECT"]
  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. self.addSegmentEditor()
  50. self.addWindowManipulator()
  51. def addInfoSection(self):
  52. #a python overview of json settings
  53. infoCollapsibleButton = ctk.ctkCollapsibleButton()
  54. infoCollapsibleButton.text = "Info"
  55. self.layout.addWidget(infoCollapsibleButton)
  56. infoLayout = qt.QFormLayout(infoCollapsibleButton)
  57. self.participantField=qt.QLabel("PatientId")
  58. infoLayout.addRow("Participant field:",self.participantField)
  59. self.ctField=qt.QLabel("ct")
  60. infoLayout.addRow("Data field (CT):",self.ctField)
  61. self.spectField=qt.QLabel("spect")
  62. infoLayout.addRow("Data field (SPECT):",self.spectField)
  63. self.userField=qt.QLabel("Loading")
  64. infoLayout.addRow("User",self.userField)
  65. self.idField=qt.QLabel("Loading")
  66. infoLayout.addRow("ID",self.idField)
  67. #Add logic at some point
  68. #self.logic=imageBrowserLogic(self)
  69. def addPatientsSelector(self):
  70. #
  71. # Patients Area
  72. #
  73. patientsCollapsibleButton = ctk.ctkCollapsibleButton()
  74. patientsCollapsibleButton.text = "Patients"
  75. #don't add it yet
  76. self.layout.addWidget(patientsCollapsibleButton)
  77. patientsFormLayout = qt.QFormLayout(patientsCollapsibleButton)
  78. self.patientList=qt.QComboBox()
  79. self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
  80. self.patientList.setEditable(True)
  81. self.patientList.setInsertPolicy(qt.QComboBox.NoInsert)
  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.spectCode=qt.QLabel("spectCode")
  89. patientsFormLayout.addRow("SPECT:",self.spectCode)
  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. self.forceReload=qt.QCheckBox("Force reload")
  103. self.forceReload.setChecked(0)
  104. patientsFormLayout.addRow("Force reload",self.forceReload)
  105. def addSetupSection(self):
  106. setupCollapsibleButton = ctk.ctkCollapsibleButton()
  107. setupCollapsibleButton.text = "Setup"
  108. self.layout.addWidget(setupCollapsibleButton)
  109. #Form layout (maybe one can think of more intuitive layouts)
  110. setupFormLayout = qt.QFormLayout(setupCollapsibleButton)
  111. self.serverList=qt.QComboBox()
  112. self.serverList.addItem('<Select>')
  113. self.serverList.addItem("astuden")
  114. self.serverList.addItem("llezaic")
  115. self.serverList.currentIndexChanged.connect(self.onServerListChanged)
  116. setupFormLayout.addRow("Select user:",self.serverList)
  117. self.setupList=qt.QComboBox()
  118. self.setupList.addItem('<Select>')
  119. #self.setupList.addItem("limfomiPET_iBrowser.json")
  120. #self.setupList.addItem("limfomiPET_iBrowser_selected.json")
  121. #self.setupList.addItem("iraemm_iBrowserProspective.json")
  122. #self.setupList.addItem("iraemm_iBrowserRetrospective.json")
  123. self.setupList.addItem("cardiacSPECT_iBrowser.json")
  124. self.setupList.currentIndexChanged.connect(self.onSetupListChanged)
  125. setupFormLayout.addRow("Setup:",self.setupList)
  126. def addSegmentEditor(self):
  127. editorCollapsibleButton = ctk.ctkCollapsibleButton()
  128. editorCollapsibleButton.text = "Segment Editor"
  129. self.layout.addWidget(editorCollapsibleButton)
  130. hLayout=qt.QVBoxLayout(editorCollapsibleButton)
  131. self.segmentEditorWidget=slicer.qMRMLSegmentEditorWidget()
  132. hLayout.addWidget(self.segmentEditorWidget)
  133. self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
  134. segEditorNode=slicer.vtkMRMLSegmentEditorNode()
  135. slicer.mrmlScene.AddNode(segEditorNode)
  136. self.segmentEditorWidget.setMRMLSegmentEditorNode(segEditorNode)
  137. def addWindowManipulator(self):
  138. windowManipulatorCollapsibleButton=ctk.ctkCollapsibleButton()
  139. windowManipulatorCollapsibleButton.text="CT Window Manipulator"
  140. self.layout.addWidget(windowManipulatorCollapsibleButton)
  141. hLayout=qt.QHBoxLayout(windowManipulatorCollapsibleButton)
  142. ctWins={'CT:bone':self.onCtBoneButtonClicked,
  143. 'CT:air':self.onCtAirButtonClicked,
  144. 'CT:abdomen':self.onCtAbdomenButtonClicked,
  145. 'CT:brain':self.onCtBrainButtonClicked,
  146. 'CT:lung':self.onCtLungButtonClicked}
  147. for ctWin in ctWins:
  148. ctButton=qt.QPushButton(ctWin)
  149. ctButton.clicked.connect(ctWins[ctWin])
  150. hLayout.addWidget(ctButton)
  151. def onSetupListChanged(self,i):
  152. status=self.logic.setConfig(self.setupList.currentText)
  153. try:
  154. if status['error']=='FILE NOT FOUND':
  155. print('File {} not found.'.format(self.setupList.currentText))
  156. return
  157. except KeyError:
  158. pass
  159. #sort ids
  160. ids=status['ids']
  161. ids.sort()
  162. self.updatePatientList(ids)
  163. self.onPatientListChanged(0)
  164. def onServerListChanged(self,i):
  165. status=self.logic.setServer(self.serverList.currentText)
  166. try:
  167. if status['error']=='KEY ERROR':
  168. self.serverList.setStyleSheet('background-color: violet')
  169. if status['error']=='ID ERROR':
  170. self.serverList.setStyleSheet('background-color: red')
  171. return
  172. except KeyError:
  173. pass
  174. self.idField.setText(status['id'])
  175. self.userField.setText(status['displayName'])
  176. self.serverList.setStyleSheet('background-color: green')
  177. def onPatientListChanged(self,i):
  178. dt=datetime.datetime
  179. self.visitList.clear()
  180. self.spectCode.setText("")
  181. self.ctCode.setText("")
  182. #add potential filters from setup to dbFilter
  183. ds=self.logic.getDataset(dbFilter={'participant':self.patientList.currentText})
  184. visitVar=self.logic.getVarName(var='visitField')
  185. studyDateVar=self.logic.getVarName(var='studyDateField')
  186. #label is a combination of sequence number and date of imaging
  187. try:
  188. seq={row['SequenceNum']:
  189. {'label':row[visitVar],'date': parseDate(row[studyDateVar])} for row in ds['rows']}
  190. except TypeError:
  191. #if studyDate is empty, this will return no possible visits
  192. return
  193. #apply lookup to visitVar if available
  194. try:
  195. seq={x:
  196. {'label':ds['lookups'][visitVar][seq[x]['label']],
  197. 'date':seq[x]['date']}
  198. for x in seq}
  199. except KeyError:
  200. pass
  201. #format label
  202. seq={x:'{} ({})'.format(seq[x]['label'],dt.strftime(seq[x]['date'],'%d-%b-%Y'))
  203. for x in seq}
  204. for s in seq:
  205. #onVisitListChanged is called for every addItem
  206. self.visitList.addItem(seq[s],s)
  207. #self.onVisitListChanged(0)
  208. def onVisitListChanged(self,i):
  209. #ignore calls on empty list
  210. if self.visitList.count==0:
  211. return
  212. #get sequence num
  213. s=self.visitList.itemData(i)
  214. print("Visit: SequenceNum:{}, label{}".format(s,self.visitList.currentText))
  215. dbFilter={'participant':self.patientList.currentText,
  216. 'seqNum':s}
  217. ds=self.logic.getDataset(dbFilter=dbFilter)
  218. if not len(ds['rows'])==1:
  219. print("Found incorrect number {} of matches for [{}]/[{}]".\
  220. format(len(ds['rows']),\
  221. self.patientList.currentText,s))
  222. row=ds['rows'][0]
  223. #copy row properties for data access
  224. self.currentRow=row
  225. self.spectCode.setText(row[self.spectField.text])
  226. self.ctCode.setText(row[self.ctField.text])
  227. #self.segmentationCode.setText(row[self.segmentationField.text])
  228. def updatePatientList(self,ids):
  229. self.patientList.clear()
  230. for id in ids:
  231. self.patientList.addItem(id)
  232. def onPatientLoadButtonClicked(self):
  233. print("Load")
  234. #delegate loading to logic
  235. self.logic.loadImages(self.currentRow,self.keepCached.isChecked(),
  236. self.forceReload.isChecked())
  237. self.logic.loadSegmentation(self.currentRow)
  238. self.setSegmentEditor()
  239. #self.logic.loadReview(self.currentRow)
  240. #self.logic.loadAE(self.currentRow)
  241. #self.onReviewSegmentChanged()
  242. def setSegmentEditor(self):
  243. #use current row to set segment in segment editor
  244. self.segmentEditorWidget.setSegmentationNode(
  245. self.logic.volumeNode['Segmentation'])
  246. self.segmentEditorWidget.setMasterVolumeNode(
  247. self.logic.volumeNode['SPECT'])
  248. def onReviewSegmentChanged(self):
  249. pass
  250. def onPatientClearButtonClicked(self):
  251. self.logic.clearVolumesAndSegmentations()
  252. self.patientSave.setStyleSheet('background-color:gray')
  253. def onPatientSaveButtonClicked(self):
  254. status=self.logic.saveSegmentation()
  255. if status:
  256. self.patientSave.setStyleSheet('background-color:green')
  257. else:
  258. self.patientSave.setStyleSheet('background-color:red')
  259. def onCtBoneButtonClicked(self):
  260. self.logic.setWindow('CT:bone')
  261. def onCtAirButtonClicked(self):
  262. self.logic.setWindow('CT:air')
  263. def onCtAbdomenButtonClicked(self):
  264. self.logic.setWindow('CT:abdomen')
  265. def onCtBrainButtonClicked(self):
  266. self.logic.setWindow('CT:brain')
  267. def onCtLungButtonClicked(self):
  268. self.logic.setWindow('CT:lung')
  269. def cleanup(self):
  270. pass
  271. def loadLibrary(name):
  272. #utility function to load nix library from git
  273. fwrapper=nixModule.getWrapper('nixWrapper.py')
  274. p=pathlib.Path(fwrapper)
  275. sys.path.append(str(p.parent))
  276. import nixWrapper
  277. return nixWrapper.loadLibrary(name)
  278. def parseDate(x):
  279. print(f'Parsing date from {x}')
  280. dt=datetime.datetime
  281. try:
  282. return dt.strptime(x,'%Y/%m/%d %H:%M:%S.%f')
  283. except ValueError:
  284. pass
  285. try:
  286. return dt.strptime(x,'%Y-%m-%d %H:%M:%S.%f')
  287. except ValueError:
  288. pass
  289. #raise TypeError(f'Could not convert date {x}')
  290. return dt.fromtimestamp(0)
  291. #
  292. # imageBrowserLogic
  293. #
  294. class imageBrowserLogic(ScriptedLoadableModuleLogic):
  295. """This class should implement all the actual
  296. computation done by your module. The interface
  297. should be such that other python code can import
  298. this class and make use of the functionality without
  299. requiring an instance of the Widget.
  300. Uses ScriptedLoadableModuleLogic base class, available at:
  301. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  302. """
  303. def __init__(self,parent=None):
  304. ScriptedLoadableModuleLogic.__init__(self, parent)
  305. print('imageBrowserLogic loading')
  306. if not parent==None:
  307. #use layout and data from parent widget
  308. self.parent=parent
  309. fhome=os.path.expanduser('~')
  310. fsetup=os.path.join(fhome,'.labkey','setup.json')
  311. try:
  312. with open(fsetup) as f:
  313. self.setup=json.load(f)
  314. except FileNotFoundError:
  315. self.setup={}
  316. try:
  317. pt=self.setup['paths']
  318. except KeyError:
  319. self.setup['paths']={}
  320. lName='labkeyInterface'
  321. loadLibrary(lName)
  322. import labkeyInterface
  323. import labkeyDatabaseBrowser
  324. import labkeyFileBrowser
  325. self.network=labkeyInterface.labkeyInterface()
  326. self.dbBrowser=labkeyDatabaseBrowser
  327. self.fBrowser=labkeyFileBrowser
  328. self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
  329. if not os.path.isdir(self.tempDir):
  330. os.mkdir(self.tempDir)
  331. self.lookups={}
  332. #self.segmentList=["Liver","Blood","Marrow","Disease","Deauville"]
  333. self.segmentList=["One","Two","Three","Four","Five","Six","Seven","Eight"]
  334. print('imageBrowserLogic setup complete')
  335. def setServer(self,serverName):
  336. #additional way of setting the labkey network interface
  337. #if no parent was provided in logic initialization (stand-alone mode)
  338. status={}
  339. fileName="NONE"
  340. if serverName=="astuden":
  341. fileName="astuden.json"
  342. if serverName=="llezaic":
  343. fileName="llezaic.json"
  344. if fileName=="NONE":
  345. print("No path was associated with server {}".format(serverName))
  346. status['error']='KEY ERROR'
  347. return status
  348. fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
  349. self.network.init(fconfig)
  350. self.remoteId=self.network.getUserId()
  351. if self.remoteId==None:
  352. status['error']='ID ERROR'
  353. return status
  354. status['displayName']=self.remoteId['displayName']
  355. status['id']=self.remoteId['id']
  356. #reset db and fb (they are thin classes anyhow)
  357. self.db=self.dbBrowser.labkeyDB(self.network)
  358. self.fb=self.fBrowser.labkeyFileBrowser(self.network)
  359. return status
  360. def setConfig(self,configName):
  361. status={}
  362. fileName=os.path.join(os.path.expanduser('~'),'.labkey',configName)
  363. if not os.path.isfile(fileName):
  364. status['error']='FILE NOT FOUND'
  365. return status
  366. with open(fileName,'r') as f:
  367. self.isetup=json.load(f)
  368. #self.project=self.isetup['project']
  369. #"iPNUMMretro/Study"
  370. #self.schema='study'
  371. #self.dataset=self.isetup['query']
  372. #include filters...
  373. ds=self.getDataset()
  374. try:
  375. filterValue=self.isetup['filterEntries']
  376. except KeyError:
  377. #this is default
  378. ids=[row[self.isetup['participantField']] for row in ds['rows']]
  379. status['ids']=list(set(ids))
  380. return status
  381. #look for entries where segmentation was already done
  382. dsSet=self.getDataset('SegmentationsMaster')
  383. segMap={'{}:{}'.format(r['patientCode'],r['visitCode']):r['comments']
  384. for r in dsSet['rows']}
  385. ids=[]
  386. for r in ds['rows']:
  387. code='{}:{}'.format(r['patientCode'],r['visitCode'])
  388. try:
  389. comment=segMap[code]
  390. except KeyError:
  391. ids.append(r['patientCode'])
  392. continue
  393. if comment==filterValue:
  394. ids.append(r['patientCode'])
  395. status['ids']=list(set(ids))
  396. return status
  397. def getVarName(self,name="Imaging",var="visitField"):
  398. dset=self.isetup['datasets'][name]
  399. defaults={"visitField":"imagingVisitId","studyDateField":"studyDate"}
  400. try:
  401. return dset[var]
  402. except KeyError:
  403. return defaults[var]
  404. def getDataset(self,name="Imaging",dbFilter={}):
  405. dset=self.isetup['datasets'][name]
  406. project=dset['project']
  407. schema=dset['schema']
  408. query=dset['query']
  409. #add default filters
  410. qFilter=[]
  411. try:
  412. for qf in dset['filter']:
  413. v=dset['filter'][qf]
  414. qFilter.append({'variable':qf,'value':v,'oper':'eq'})
  415. except KeyError:
  416. pass
  417. for f in dbFilter:
  418. if f=='participant':
  419. qFilter.append({'variable':self.isetup['participantField'],
  420. 'value':dbFilter[f],'oper':'eq'})
  421. continue
  422. if f=='seqNum':
  423. qFilter.append({'variable':'SequenceNum',
  424. 'value':'{}'.format(dbFilter[f]),
  425. 'oper':'eq'})
  426. continue
  427. qFilter.append({'variable':f,'value':dbFilter[f],'oper':'eq'})
  428. try:
  429. ds=self.db.selectRows(project,schema,query, \
  430. qFilter,dset['view'])
  431. except KeyError:
  432. ds=self.db.selectRows(project,schema,query,qFilter)
  433. #get lookups as well
  434. lookups={}
  435. for f in ds['metaData']['fields']:
  436. try:
  437. lookup=f['lookup']
  438. except KeyError:
  439. continue
  440. var=f['name']
  441. lookupCode='{}:{}'.format(lookup['schema'],lookup['queryName'])
  442. try:
  443. lookups[var]=self.lookups[lookupCode]
  444. except KeyError:
  445. self.lookups[lookupCode]=self.loadLookup(project,lookup)
  446. lookups[var]=self.lookups[lookupCode]
  447. return {'rows':ds['rows'],'lookups':lookups}
  448. def loadLookup(self,project,lookup):
  449. qData={}
  450. key=lookup['keyColumn']
  451. value=lookup['displayColumn']
  452. fSet=self.db.selectRows(project,lookup['schema'],lookup['queryName'],[])
  453. for q in fSet['rows']:
  454. qData[q[key]]=q[value]
  455. return qData
  456. def loadImage(self,iData):
  457. #unpack iData
  458. idx=iData['idx']
  459. path=iData['path']
  460. keepCached=iData['keepCached']
  461. try:
  462. forceReload=iData['forceReload']
  463. except KeyError:
  464. forceReload=False
  465. dset=self.isetup['datasets'][iData['dataset']]
  466. localPath=os.path.join(self.tempDir,path[-1])
  467. if not os.path.isfile(localPath) or forceReload:
  468. #download from server
  469. remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
  470. if not self.fb.entryExists(remotePath):
  471. print("Failed to get {}".format(remotePath))
  472. return
  473. #overwrites existing file from remote
  474. self.fb.readFileToFile(remotePath,localPath)
  475. properties={}
  476. filetype='VolumeFile'
  477. #make sure segmentation gets loaded as a labelmap
  478. if idx=="Segmentation":
  479. filetype='SegmentationFile'
  480. #properties["labelmap"]=1
  481. self.volumeNode[idx]=slicer.util.loadNodeFromFile(localPath,
  482. filetype=filetype,properties=properties)
  483. if not keepCached:
  484. pass
  485. #os.remove(localPath)
  486. def loadImages(self,row,keepCached, forceReload=False):
  487. #fields={'ctResampled':True,'petResampled':False}
  488. fields={"CT":self.parent.ctField.text,\
  489. "SPECT":self.parent.spectField.text}
  490. path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
  491. relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
  492. self.volumeNode={}
  493. for f in relativePaths:
  494. iData={'idx':f,'path':relativePaths[f],
  495. 'keepCached':keepCached,'dataset':'Imaging',
  496. 'forceReload':forceReload}
  497. self.loadImage(iData)
  498. #mimic abdominalCT standardized window setting
  499. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  500. SetAutoWindowLevel(False)
  501. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  502. SetWindowLevel(1400, -500)
  503. #set colormap for PET to PET-Heat (this is a verbatim setting from
  504. #the Volumes->Display->Lookup Table colormap identifier)
  505. self.volumeNode['SPECT'].GetScalarVolumeDisplayNode().\
  506. SetAndObserveColorNodeID(\
  507. slicer.util.getNode('Inferno').GetID())
  508. slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
  509. foreground=self.volumeNode['SPECT'],foregroundOpacity=0.5,fit=True)
  510. def loadSegmentation(self,row, loadFile=1):
  511. dbFilter={'User':'{}'.format(self.remoteId['id']),
  512. 'participant':row[self.isetup['participantField']],
  513. 'visitCode':row['visitCode']}
  514. ds=self.getDataset(name='SegmentationsMaster',
  515. dbFilter=dbFilter)
  516. if len(ds['rows'])>1:
  517. print('Multiple segmentations found!')
  518. return
  519. if len(ds['rows'])==1:
  520. #update self.segmentationEntry
  521. self.segmentationEntry=ds['rows'][0]
  522. self.segmentationEntry['origin']='database'
  523. if loadFile:
  524. self.loadSegmentationFromEntry()
  525. return
  526. #create new segmentation
  527. self.createSegmentation(row)
  528. def getSegmentationPath(self):
  529. path=[self.isetup['imageDir'],
  530. self.segmentationEntry['patientCode'],
  531. self.segmentationEntry['visitCode']]
  532. path.append('Segmentations')
  533. return path
  534. def loadSegmentationFromEntry(self):
  535. #compile path
  536. entry=self.segmentationEntry
  537. path=self.getSegmentationPath()
  538. path.append(entry['latestFile'])
  539. iData={'idx':'Segmentation','path':path,
  540. 'keepCached':1,'dataset':'SegmentationsMaster'}
  541. self.loadImage(iData)
  542. #look for missing segments
  543. segNode=self.volumeNode['Segmentation']
  544. seg=segNode.GetSegmentation()
  545. segNames=[seg.GetNthSegmentID(i) for i in range(seg.GetNumberOfSegments())]
  546. print('Segments')
  547. try:
  548. segmentList=self.isetup['segmentList']
  549. except KeyError:
  550. segmentList=self.segmentList
  551. for s in segmentList:
  552. if s not in segNames:
  553. seg.AddEmptySegment(s,s)
  554. print(s)
  555. print('Done')
  556. def saveSegmentation(self):
  557. #get the latest key by adding an entry to SegmentationList
  558. copyFields=[self.isetup['participantField'],'patientCode','visitCode','User']
  559. outRow={x:self.segmentationEntry[x] for x in copyFields}
  560. sList=self.isetup['datasets']['SegmentationsList']
  561. resp=self.db.modifyRows('insert',sList['project'],
  562. sList['schema'],sList['query'],[outRow])
  563. encoding=chardet.detect(resp)['encoding']
  564. respJSON=json.loads(resp.decode(encoding))
  565. outRow=respJSON['rows'][0]
  566. #print(outRow)
  567. #construct file name with known key
  568. uName=re.sub(' ','_',self.remoteId['displayName'])
  569. fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
  570. self.segmentationEntry['patientCode'],
  571. self.segmentationEntry['visitCode'],
  572. uName,outRow['Key'])
  573. path=self.getSegmentationPath()
  574. path.append(fName)
  575. status=self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
  576. #update SegmentationList with know file name
  577. outRow['Segmentation']=fName
  578. self.db.modifyRows('update',sList['project'],
  579. sList['schema'],sList['query'],[outRow])
  580. #update SegmentationsMaster
  581. self.segmentationEntry['latestFile']=fName
  582. self.segmentationEntry['version']=outRow['Key']
  583. des=self.isetup['datasets']['SegmentationsMaster']
  584. op='insert'
  585. if self.segmentationEntry['origin']=='database':
  586. op='update'
  587. print('Saving: mode={}'.format(op))
  588. resp=self.db.modifyRows(op,des['project'],
  589. des['schema'],des['query'],[self.segmentationEntry])
  590. print(resp)
  591. #since we loaded a version, origin should be set to database
  592. self.loadSegmentation(self.segmentationEntry,0)
  593. return status
  594. #self.segmentationEntry['origin']='database'
  595. def saveNode(self,node,dataset,path):
  596. fName=path[-1]
  597. localPath=os.path.join(self.tempDir,fName)
  598. slicer.util.saveNode(node,localPath)
  599. dset=self.isetup['datasets'][dataset]
  600. #exclude file name when building directory structure
  601. remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
  602. remotePath+='/'+fName
  603. self.fb.writeFileToFile(localPath,remotePath)
  604. return self.fb.entryExists(remotePath)
  605. #add entry to segmentation list
  606. def createSegmentation(self,entry):
  607. #create segmentation entry for database update
  608. #note that origin is not set to database
  609. copyFields=[self.isetup['participantField'],'patientCode','visitCode','SequenceNum']
  610. #copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
  611. self.segmentationEntry={x:entry[x] for x in copyFields}
  612. self.segmentationEntry['User']=self.remoteId['id']
  613. self.segmentationEntry['origin']='created'
  614. self.segmentationEntry['version']=-1111
  615. #create a segmentation node
  616. uName=re.sub(' ','_',self.remoteId['displayName'])
  617. segNode=slicer.vtkMRMLSegmentationNode()
  618. self.volumeNode['Segmentation']=segNode
  619. segNode.SetName('Segmentation_{}_{}_{}'.
  620. format(entry['patientCode'],entry['visitCode'],uName))
  621. slicer.mrmlScene.AddNode(segNode)
  622. segNode.CreateDefaultDisplayNodes()
  623. segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['SPECT'])
  624. try:
  625. segmentList=self.isetup['segmentList']
  626. except KeyError:
  627. segmentList=self.segmentList
  628. for s in segmentList:
  629. segNode.GetSegmentation().AddEmptySegment(s,s)
  630. def clearVolumesAndSegmentations(self):
  631. nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
  632. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  633. res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
  634. #self.segmentationNode=None
  635. #self.reviewResult={}
  636. #self.aeList={}
  637. def setWindow(self,windowName):
  638. #default
  639. w=1400
  640. l=-500
  641. if windowName=='CT:bone':
  642. w=1000
  643. l=400
  644. if windowName=='CT:air':
  645. w=1000
  646. l=-426
  647. if windowName=='CT:abdomen':
  648. w=350
  649. l=40
  650. if windowName=='CT:brain':
  651. w=100
  652. l=50
  653. if windowName=='CT:lung':
  654. w=1400
  655. l=-500
  656. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  657. SetWindowLevel(w,l)
  658. print('setWindow: {} {}/{}'.format(windowName,w,l))
  659. class imageBrowserTest(ScriptedLoadableModuleTest):
  660. """
  661. This is the test case for your scripted module.
  662. Uses ScriptedLoadableModuleTest base class, available at:
  663. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  664. """
  665. def setup(self):
  666. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  667. """
  668. slicer.mrmlScene.Clear(0)
  669. def runTest(self):
  670. """Run as few or as many tests as needed here.
  671. """
  672. self.setUp()
  673. self.test_irAEMMBrowser()
  674. def test_irAEMMBrowser(self):
  675. """ Ideally you should have several levels of tests. At the lowest level
  676. tests sould exercise the functionality of the logic with different inputs
  677. (both valid and invalid). At higher levels your tests should emulate the
  678. way the user would interact with your code and confirm that it still works
  679. the way you intended.
  680. One of the most important features of the tests is that it should alert other
  681. developers when their changes will have an impact on the behavior of your
  682. module. For example, if a developer removes a feature that you depend on,
  683. your test should break so they know that the feature is needed.
  684. """
  685. self.delayDisplay("Starting the test")
  686. #
  687. # first, get some data
  688. #