segmentationBrowser.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  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. import functools
  13. #
  14. # labkeySlicerPythonExtension
  15. #
  16. class segmentationBrowser(ScriptedLoadableModule):
  17. """Uses ScriptedLoadableModule base class, available at:
  18. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  19. """
  20. def __init__(self, parent):
  21. ScriptedLoadableModule.__init__(self, parent)
  22. self.parent.title = "segmentation Browser"
  23. # TODO make this more human readable by adding spaces
  24. self.parent.categories = ["LabKey"]
  25. self.parent.dependencies = []
  26. self.parent.contributors = ["Andrej Studen (UL/FMF)"]
  27. # replace with "Firstname Lastname (Organization)"
  28. self.parent.helpText = """
  29. Interface to stored segmentation files in LabKey
  30. """
  31. self.parent.acknowledgementText = """
  32. Developed within the medical physics research programme
  33. of the Slovenian research agency.
  34. """ # replace with organization, grant and thanks.
  35. #
  36. # labkeySlicerPythonExtensionWidget
  37. #
  38. class segmentationBrowserWidget(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. print("Setting up segmentationBrowserWidget")
  44. ScriptedLoadableModuleWidget.setup(self)
  45. # Instantiate and connect widgets ...
  46. self.logic=segmentationBrowserLogic(self)
  47. self.addInfoSection()
  48. self.addSetupSection()
  49. self.addPatientsSelector()
  50. self.addSegmentViewer()
  51. self.addWindowManipulator()
  52. def addInfoSection(self):
  53. #a python overview of json settings
  54. infoCollapsibleButton = ctk.ctkCollapsibleButton()
  55. infoCollapsibleButton.text = "Info"
  56. self.layout.addWidget(infoCollapsibleButton)
  57. infoLayout = qt.QFormLayout(infoCollapsibleButton)
  58. self.participantField=qt.QLabel("PatientId")
  59. infoLayout.addRow("Participant field:",self.participantField)
  60. self.ctField=qt.QLabel("ctResampled")
  61. infoLayout.addRow("Data field (CT):",self.ctField)
  62. self.petField=qt.QLabel("petResampled")
  63. infoLayout.addRow("Data field (PET):",self.petField)
  64. self.userField=qt.QLabel("Loading")
  65. infoLayout.addRow("User",self.userField)
  66. self.idField=qt.QLabel("Loading")
  67. infoLayout.addRow("ID",self.idField)
  68. #Add logic at some point
  69. #self.logic=segmentationBrowserLogic(self)
  70. def addPatientsSelector(self):
  71. #
  72. # Patients Area
  73. #
  74. patientsCollapsibleButton = ctk.ctkCollapsibleButton()
  75. patientsCollapsibleButton.text = "Patients"
  76. #don't add it yet
  77. self.layout.addWidget(patientsCollapsibleButton)
  78. patientsFormLayout = qt.QFormLayout(patientsCollapsibleButton)
  79. self.patientList=qt.QComboBox()
  80. self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
  81. self.patientList.setEditable(True)
  82. self.patientList.setInsertPolicy(qt.QComboBox.NoInsert)
  83. patientsFormLayout.addRow("Patient:",self.patientList)
  84. self.visitList=qt.QComboBox()
  85. self.visitList.currentIndexChanged.connect(self.onVisitListChanged)
  86. patientsFormLayout.addRow("Visit:",self.visitList)
  87. self.ctCode=qt.QLabel("ctCode")
  88. patientsFormLayout.addRow("CT:",self.ctCode)
  89. self.petCode=qt.QLabel("petCode")
  90. patientsFormLayout.addRow("PET:",self.petCode)
  91. self.patientLoad=qt.QPushButton("Load")
  92. self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
  93. patientsFormLayout.addRow("Load patient",self.patientLoad)
  94. #no Save button
  95. #self.patientSave=qt.QPushButton("Save")
  96. #self.patientSave.clicked.connect(self.onPatientSaveButtonClicked)
  97. #patientsFormLayout.addRow("Save segmentation",self.patientSave)
  98. self.patientClear=qt.QPushButton("Clear")
  99. self.patientClear.clicked.connect(self.onPatientClearButtonClicked)
  100. patientsFormLayout.addRow("Clear patient",self.patientClear)
  101. self.keepCached=qt.QCheckBox("keep Cached")
  102. self.keepCached.setChecked(1)
  103. patientsFormLayout.addRow("Keep cached",self.keepCached)
  104. def addSetupSection(self):
  105. setupCollapsibleButton = ctk.ctkCollapsibleButton()
  106. setupCollapsibleButton.text = "Setup"
  107. self.layout.addWidget(setupCollapsibleButton)
  108. #Form layout (maybe one can think of more intuitive layouts)
  109. setupFormLayout = qt.QFormLayout(setupCollapsibleButton)
  110. self.serverList=qt.QComboBox()
  111. self.serverList.addItem('<Select>')
  112. self.serverList.addItem("astuden")
  113. self.serverList.addItem("adoma")
  114. self.serverList.addItem("kzevnik")
  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("iraemm_iBrowserProspective.json")
  121. self.setupList.addItem("iraemm_iBrowserRetrospective.json")
  122. self.setupList.currentIndexChanged.connect(self.onSetupListChanged)
  123. setupFormLayout.addRow("Setup:",self.setupList)
  124. def addReviewSection(self):
  125. #
  126. # Review Area (not used, relic)
  127. #
  128. reviewCollapsibleButton = ctk.ctkCollapsibleButton()
  129. reviewCollapsibleButton.text = "Review"
  130. self.layout.addWidget(reviewCollapsibleButton)
  131. self.reviewBoxLayout = qt.QVBoxLayout(reviewCollapsibleButton)
  132. self.reviewFormLayout = qt.QFormLayout()
  133. self.reviewSegment=qt.QComboBox()
  134. self.reviewSegment.currentIndexChanged.connect(\
  135. self.onReviewSegmentChanged)
  136. self.reviewFormLayout.addRow("Selected region:",self.reviewSegment)
  137. self.reviewResult=qt.QComboBox()
  138. sLabel="What do you think about the segmentation:"
  139. self.reviewFormLayout.addRow(sLabel,self.reviewResult)
  140. reviewOptions=['Select','Excellent','Minor deficiencies',\
  141. 'Major deficiencies','Unusable']
  142. for opt in reviewOptions:
  143. self.reviewResult.addItem(opt)
  144. self.aeResult=qt.QComboBox()
  145. aeLabel="Is organ suffering from adverse effect?"
  146. self.reviewFormLayout.addRow(aeLabel,self.aeResult)
  147. aeOptions=['Select','Yes','No']
  148. for opt in aeOptions:
  149. self.aeResult.addItem(opt)
  150. #self.aeResult.setCurrentIndex(0)
  151. self.updateReview=qt.QPushButton("Save")
  152. saLabel="Save segmentation and AE decision for current segment"
  153. self.reviewFormLayout.addRow(saLabel,self.updateReview)
  154. self.updateReview.clicked.connect(self.onUpdateReviewButtonClicked)
  155. self.reviewBoxLayout.addLayout(self.reviewFormLayout)
  156. submitFrame=qt.QGroupBox("Submit data")
  157. self.submitFormLayout=qt.QFormLayout()
  158. self.reviewComment=qt.QTextEdit("this is a test")
  159. self.submitFormLayout.addRow("Comments (optional)",self.reviewComment)
  160. self.submitReviewButton=qt.QPushButton("Submit")
  161. self.submitFormLayout.addRow("Submit to database",\
  162. self.submitReviewButton)
  163. self.submitReviewButton.clicked.connect(\
  164. self.onSubmitReviewButtonClicked)
  165. submitFrame.setLayout(self.submitFormLayout)
  166. submitFrame.setFlat(1)
  167. #submitFrame.setFrameShape(qt.QFrame.StyledPanel)
  168. #submitFrame.setFrameShadow(qt.QFrame.Sunken)
  169. submitFrame.setStyleSheet("background-color:rgba(220,215,180,45)")
  170. self.reviewBoxLayout.addWidget(submitFrame)
  171. def addSegmentViewer(self):
  172. #not used (for review)
  173. editorCollapsibleButton = ctk.ctkCollapsibleButton()
  174. editorCollapsibleButton.text = "Segment Viewer"
  175. self.layout.addWidget(editorCollapsibleButton)
  176. self.vSegViewLayout=qt.QVBoxLayout(editorCollapsibleButton)
  177. segList=' '.join(self.logic.segmentList)
  178. label=qt.QLabel(" {}".format(segList))
  179. self.vSegViewLayout.addWidget(label)
  180. self.segList=[]
  181. def addWindowManipulator(self):
  182. windowManipulatorCollapsibleButton=ctk.ctkCollapsibleButton()
  183. windowManipulatorCollapsibleButton.text="CT Window Manipulator"
  184. self.layout.addWidget(windowManipulatorCollapsibleButton)
  185. hLayout=qt.QHBoxLayout(windowManipulatorCollapsibleButton)
  186. ctWins={'CT:bone':self.onCtBoneButtonClicked,
  187. 'CT:air':self.onCtAirButtonClicked,
  188. 'CT:abdomen':self.onCtAbdomenButtonClicked,
  189. 'CT:brain':self.onCtBrainButtonClicked,
  190. 'CT:lung':self.onCtLungButtonClicked}
  191. for ctWin in ctWins:
  192. ctButton=qt.QPushButton(ctWin)
  193. ctButton.clicked.connect(ctWins[ctWin])
  194. hLayout.addWidget(ctButton)
  195. def onSetupListChanged(self,i):
  196. status=self.logic.setConfig(self.setupList.currentText)
  197. try:
  198. if status['error']=='FILE NOT FOUND':
  199. print('File {} not found.'.format(self.setupList.currentText))
  200. return
  201. except KeyError:
  202. pass
  203. #sort ids
  204. ids=status['ids']
  205. ids.sort()
  206. self.updatePatientList(ids)
  207. self.onPatientListChanged(0)
  208. def onServerListChanged(self,i):
  209. status=self.logic.setServer(self.serverList.currentText)
  210. try:
  211. if status['error']=='KEY ERROR':
  212. self.serverList.setStyleSheet('background-color: violet')
  213. if status['error']=='ID ERROR':
  214. self.serverList.setStyleSheet('background-color: red')
  215. return
  216. except KeyError:
  217. pass
  218. self.idField.setText(status['id'])
  219. self.userField.setText(status['displayName'])
  220. self.serverList.setStyleSheet('background-color: green')
  221. def onPatientListChanged(self,i):
  222. self.visitList.clear()
  223. self.petCode.setText("")
  224. self.ctCode.setText("")
  225. #add potential filters from setup to dbFilter
  226. ds=self.logic.getDataset(dbFilter={'participant':self.patientList.currentText})
  227. visitVar=self.logic.getVarName(var='visitField')
  228. dt=datetime.datetime
  229. #label is a combination of sequence number and date of imaging
  230. try:
  231. seq={row['SequenceNum']:
  232. {'label':row[visitVar],
  233. 'date': dt.strptime(row['studyDate'],'%Y/%m/%d %H:%M:%S')}
  234. for row in ds['rows']}
  235. except TypeError:
  236. #if studyDate is empty, this will return no possible visits
  237. return
  238. #apply lookup to visitVar if available
  239. try:
  240. seq={x:
  241. {'label':ds['lookups'][visitVar][seq[x]['label']],
  242. 'date':seq[x]['date']}
  243. for x in seq}
  244. except KeyError:
  245. pass
  246. #format label
  247. seq={x:'{} ({})'.format(seq[x]['label'],dt.strftime(seq[x]['date'],'%d-%b-%Y'))
  248. for x in seq}
  249. for s in seq:
  250. #onVisitListChanged is called for every addItem
  251. self.visitList.addItem(seq[s],s)
  252. #self.onVisitListChanged(0)
  253. def onVisitListChanged(self,i):
  254. #ignore calls on empty list
  255. if self.visitList.count==0:
  256. return
  257. #get sequence num
  258. s=self.visitList.itemData(i)
  259. print("Visit: SequenceNum:{}, label{}".format(s,self.visitList.currentText))
  260. dbFilter={'participant':self.patientList.currentText,
  261. 'seqNum':s}
  262. ds=self.logic.getDataset(dbFilter=dbFilter)
  263. if not len(ds['rows'])==1:
  264. print("Found incorrect number {} of matches for [{}]/[{}]".\
  265. format(len(ds['rows']),\
  266. self.patientList.currentText,s))
  267. row=ds['rows'][0]
  268. #copy row properties for data access
  269. self.currentRow=row
  270. self.petCode.setText(row[self.petField.text])
  271. self.ctCode.setText(row[self.ctField.text])
  272. #self.segmentationCode.setText(row[self.segmentationField.text])
  273. def updatePatientList(self,ids):
  274. self.patientList.clear()
  275. for id in ids:
  276. self.patientList.addItem(id)
  277. def onPatientLoadButtonClicked(self):
  278. #clear first
  279. self.logic.clearVolumesAndSegmentations()
  280. print("Load")
  281. #delegate loading to logic
  282. self.logic.loadImages(self.currentRow,self.keepCached.isChecked())
  283. self.logic.loadSegmentations(self.currentRow)
  284. self.updateSegmentationViewer(self.logic.segmentationRows)
  285. #self.logic.loadReview(self.currentRow)
  286. #self.logic.loadAE(self.currentRow)
  287. #self.onReviewSegmentChanged()
  288. def generateSegmentationView(self):
  289. s=qt.QHBoxLayout()
  290. nm=qt.QLabel("name")
  291. s.addWidget(nm)
  292. for g in self.logic.segmentList:
  293. t=qt.QCheckBox()
  294. s.addWidget(t)
  295. return s
  296. def setSegmentationViewName(self,s,name):
  297. label=s.itemAt(0).widget()
  298. label.setText(name)
  299. def getWidgetsFromLayout(self,s):
  300. return [s.itemAt(i).widget() for i in range(s.count())]
  301. def hideLayout(self,s):
  302. for w in self.getWidgetsFromLayout(s):
  303. w.hide()
  304. def disconnect(self,s):
  305. for w in self.getWidgetsFromLayout(s)[1:]:
  306. try:
  307. w.stateChanged.disconnect()
  308. except TypeError:
  309. continue
  310. def showLayout(self,s):
  311. for w in self.getWidgetsFromLayout(s):
  312. w.show()
  313. def refreshCallbacks(self,s,entry):
  314. w=self.getWidgetsFromLayout(s)
  315. for i in range(len(self.logic.segmentList)):
  316. w[i+1].stateChanged.connect(functools.partial(self.onSegmentationViewChanged,
  317. latestFile=entry['latestFile'],
  318. segment=self.logic.segmentList[i]))
  319. def getUserMap(self):
  320. users=self.logic.lookups['core:Users']
  321. #print('{}'.format(users))
  322. return users
  323. def updateSegmentationViewer(self,rows):
  324. users=self.getUserMap()
  325. for s in self.segList:
  326. self.hideLayout(s)
  327. self.disconnect(s)
  328. #can't hide a layout
  329. #s.hide()
  330. i=0
  331. for r in rows:
  332. try:
  333. s=self.segList[i]
  334. except IndexError:
  335. #assume we only have to add one (since we iterate sequentially)
  336. self.segList.append(self.generateSegmentationView())
  337. s=self.segList[i]
  338. self.vSegViewLayout.addLayout(s)
  339. self.showLayout(s)
  340. self.setSegmentationViewName(s,users[r['User']])
  341. self.refreshCallbacks(s,r)
  342. i+=1
  343. def onReviewSegmentChanged(self):
  344. pass
  345. def onPatientClearButtonClicked(self):
  346. self.logic.clearVolumesAndSegmentations()
  347. def onPatientSaveButtonClicked(self):
  348. #not used
  349. self.logic.saveSegmentation()
  350. def onCtBoneButtonClicked(self):
  351. self.logic.setWindow('CT:bone')
  352. def onCtAirButtonClicked(self):
  353. self.logic.setWindow('CT:air')
  354. def onCtAbdomenButtonClicked(self):
  355. self.logic.setWindow('CT:abdomen')
  356. def onCtBrainButtonClicked(self):
  357. self.logic.setWindow('CT:brain')
  358. def onCtLungButtonClicked(self):
  359. self.logic.setWindow('CT:lung')
  360. def onSegmentationViewChanged(self,state,latestFile,segment):
  361. print('onSegmentationViewChanged {} {} {} '.format(latestFile,segment,state))
  362. self.logic.toggleSegmentVisibility(latestFile,segment,state>0)
  363. def cleanup(self):
  364. pass
  365. def loadLibrary(name):
  366. #utility function to load nix library from git
  367. fwrapper=nixModule.getWrapper('nixWrapper.py')
  368. p=pathlib.Path(fwrapper)
  369. sys.path.append(str(p.parent))
  370. import nixWrapper
  371. return nixWrapper.loadLibrary(name)
  372. #
  373. # segmentationBrowserLogic
  374. #
  375. class segmentationBrowserLogic(ScriptedLoadableModuleLogic):
  376. """This class should implement all the actual
  377. computation done by your module. The interface
  378. should be such that other python code can import
  379. this class and make use of the functionality without
  380. requiring an instance of the Widget.
  381. Uses ScriptedLoadableModuleLogic base class, available at:
  382. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  383. """
  384. def __init__(self,parent=None):
  385. ScriptedLoadableModuleLogic.__init__(self, parent)
  386. print('segmentationBrowserLogic loading')
  387. if not parent==None:
  388. #use layout and data from parent widget
  389. self.parent=parent
  390. fhome=os.path.expanduser('~')
  391. fsetup=os.path.join(fhome,'.labkey','setup.json')
  392. try:
  393. with open(fsetup) as f:
  394. self.setup=json.load(f)
  395. except FileNotFoundError:
  396. self.setup={}
  397. try:
  398. pt=self.setup['paths']
  399. except KeyError:
  400. self.setup['paths']={}
  401. lName='labkeyInterface'
  402. loadLibrary(lName)
  403. import labkeyInterface
  404. import labkeyDatabaseBrowser
  405. import labkeyFileBrowser
  406. self.network=labkeyInterface.labkeyInterface()
  407. self.dbBrowser=labkeyDatabaseBrowser
  408. self.fBrowser=labkeyFileBrowser
  409. self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
  410. if not os.path.isdir(self.tempDir):
  411. os.mkdir(self.tempDir)
  412. self.lookups={}
  413. self.segmentList=["Liver","Blood","Marrow","Disease","Deauville"]
  414. print('segmentationBrowserLogic setup complete')
  415. def setServer(self,serverName):
  416. #additional way of setting the labkey network interface
  417. #if no parent was provided in logic initialization (stand-alone mode)
  418. status={}
  419. fileName="NONE"
  420. if serverName=="astuden":
  421. fileName="astuden.json"
  422. if serverName=="adoma":
  423. fileName="adoma.json"
  424. if serverName=="kzevnik":
  425. fileName="kzevnik.json"
  426. if fileName=="NONE":
  427. print("No path was associated with server {}".format(serverName))
  428. status['error']='KEY ERROR'
  429. return status
  430. fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
  431. self.network.init(fconfig)
  432. self.remoteId=self.network.getUserId()
  433. if self.remoteId==None:
  434. status['error']='ID ERROR'
  435. return status
  436. status['displayName']=self.remoteId['displayName']
  437. status['id']=self.remoteId['id']
  438. #reset db and fb (they are thin classes anyhow)
  439. self.db=self.dbBrowser.labkeyDB(self.network)
  440. self.fb=self.fBrowser.labkeyFileBrowser(self.network)
  441. return status
  442. def setConfig(self,configName):
  443. status={}
  444. fileName=os.path.join(os.path.expanduser('~'),'.labkey',configName)
  445. if not os.path.isfile(fileName):
  446. status['error']='FILE NOT FOUND'
  447. return status
  448. with open(fileName,'r') as f:
  449. self.isetup=json.load(f)
  450. #self.project=self.isetup['project']
  451. #"iPNUMMretro/Study"
  452. #self.schema='study'
  453. #self.dataset=self.isetup['query']
  454. #include filters...
  455. ds=self.getDataset()
  456. ids=[row[self.isetup['participantField']] for row in ds['rows']]
  457. status['ids']=list(set(ids))
  458. return status
  459. def getVarName(self,name="Imaging",var="visitField"):
  460. dset=self.isetup['datasets'][name]
  461. defaults={"visitField":"imagingVisitId"}
  462. try:
  463. return dset[var]
  464. except KeyError:
  465. return defaults[var]
  466. def getDataset(self,name="Imaging",dbFilter={}):
  467. dset=self.isetup['datasets'][name]
  468. project=dset['project']
  469. schema=dset['schema']
  470. query=dset['query']
  471. #add default filters
  472. qFilter=[]
  473. try:
  474. for qf in dset['filter']:
  475. v=dset['filter'][qf]
  476. qFilter.append({'variable':qf,'value':v,'oper':'eq'})
  477. except KeyError:
  478. pass
  479. for f in dbFilter:
  480. if f=='participant':
  481. qFilter.append({'variable':self.isetup['participantField'],
  482. 'value':dbFilter[f],'oper':'eq'})
  483. continue
  484. if f=='seqNum':
  485. qFilter.append({'variable':'SequenceNum',
  486. 'value':'{}'.format(dbFilter[f]),
  487. 'oper':'eq'})
  488. continue
  489. qFilter.append({'variable':f,'value':dbFilter[f],'oper':'eq'})
  490. try:
  491. ds=self.db.selectRows(project,schema,query, \
  492. qFilter,dset['view'])
  493. except KeyError:
  494. ds=self.db.selectRows(project,schema,query,qFilter)
  495. #get lookups as well
  496. lookups={}
  497. for f in ds['metaData']['fields']:
  498. try:
  499. lookup=f['lookup']
  500. except KeyError:
  501. continue
  502. var=f['name']
  503. lookupCode='{}:{}'.format(lookup['schema'],lookup['queryName'])
  504. try:
  505. lookups[var]=self.lookups[lookupCode]
  506. except KeyError:
  507. self.lookups[lookupCode]=self.loadLookup(project,lookup)
  508. lookups[var]=self.lookups[lookupCode]
  509. return {'rows':ds['rows'],'lookups':lookups}
  510. def loadLookup(self,project,lookup):
  511. qData={}
  512. key=lookup['keyColumn']
  513. value=lookup['displayColumn']
  514. fSet=self.db.selectRows(project,lookup['schema'],lookup['queryName'],[])
  515. for q in fSet['rows']:
  516. qData[q[key]]=q[value]
  517. return qData
  518. def loadImage(self,iData):
  519. #unpack iData
  520. idx=iData['idx']
  521. path=iData['path']
  522. keepCached=iData['keepCached']
  523. dset=self.isetup['datasets'][iData['dataset']]
  524. localPath=os.path.join(self.tempDir,path[-1])
  525. if not os.path.isfile(localPath):
  526. #download from server
  527. remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
  528. if not self.fb.entryExists(remotePath):
  529. print("Failed to get {}".format(remotePath))
  530. return
  531. self.fb.readFileToFile(remotePath,localPath)
  532. properties={}
  533. filetype='VolumeFile'
  534. #make sure segmentation gets loaded as a labelmap
  535. if idx=="Segmentation":
  536. filetype='SegmentationFile'
  537. #properties["labelmap"]=1
  538. try:
  539. volumeIdx=iData['volumeIdx']
  540. except KeyError:
  541. volumeIdx=idx
  542. self.volumeNode[volumeIdx]=slicer.util.loadNodeFromFile(localPath,
  543. filetype=filetype,properties=properties)
  544. if not keepCached:
  545. os.remove(localPath)
  546. def loadImages(self,row,keepCached):
  547. #fields={'ctResampled':True,'petResampled':False}
  548. fields={"CT":self.parent.ctField.text,\
  549. "PET":self.parent.petField.text}
  550. path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
  551. relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
  552. self.volumeNode={}
  553. for f in relativePaths:
  554. iData={'idx':f,'path':relativePaths[f],
  555. 'keepCached':keepCached,'dataset':'Imaging'}
  556. self.loadImage(iData)
  557. #mimic abdominalCT standardized window setting
  558. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  559. SetAutoWindowLevel(False)
  560. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  561. SetWindowLevel(1400, -500)
  562. #set colormap for PET to PET-Heat (this is a verbatim setting from
  563. #the Volumes->Display->Lookup Table colormap identifier)
  564. self.volumeNode['PET'].GetScalarVolumeDisplayNode().\
  565. SetAndObserveColorNodeID(\
  566. slicer.util.getNode('Inferno').GetID())
  567. slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
  568. foreground=self.volumeNode['PET'],foregroundOpacity=0.5,fit=True)
  569. def loadSegmentations(self,row, loadFile=1):
  570. dbFilter={
  571. #'User':'{}'.format(self.remoteId['id']),
  572. 'participant':row[self.isetup['participantField']],
  573. 'visitCode':row['visitCode']}
  574. ds=self.getDataset(name='SegmentationsMaster',
  575. dbFilter=dbFilter)
  576. self.segmentationRows=ds['rows']
  577. if len(self.segmentationRows)<0:
  578. print('No segmentations for {}'.format(dbFilter))
  579. return
  580. for r in self.segmentationRows:
  581. #update self.segmentationEntry
  582. #self.segmentationEntry=ds['rows'][0]
  583. #self.segmentationEntry['origin']='database'
  584. #if loadFile:
  585. self.loadSegmentationFromEntry(r)
  586. #make invisile by default
  587. for s in self.segmentList:
  588. self.toggleSegmentVisibility(r['latestFile'],s,False)
  589. print('load Segmentation: done')
  590. #create new segmentation
  591. #self.createSegmentation(row)
  592. def getSegmentationPath(self,entry):
  593. path=[self.isetup['imageDir'],
  594. entry['patientCode'],
  595. entry['visitCode']]
  596. path.append('Segmentations')
  597. return path
  598. def loadSegmentationFromEntry(self,r):
  599. #compile path
  600. entry=r
  601. path=self.getSegmentationPath(entry)
  602. path.append(entry['latestFile'])
  603. iData={'idx':'Segmentation','path':path,
  604. 'keepCached':0,'dataset':'SegmentationsMaster',
  605. 'volumeIdx':entry['latestFile']}
  606. self.loadImage(iData)
  607. #look for missing segments
  608. return
  609. #skip this
  610. segNode=self.volumeNode['Segmentation']
  611. seg=segNode.GetSegmentation()
  612. segNames=[seg.GetNthSegmentID(i) for i in range(seg.GetNumberOfSegments())]
  613. print('Segments')
  614. try:
  615. segmentList=self.isetup['segmentList']
  616. except KeyError:
  617. segmentList=self.segmentList
  618. for s in segmentList:
  619. if s not in segNames:
  620. seg.AddEmptySegment(s,s)
  621. print(s)
  622. print('Done')
  623. def toggleSegmentVisibility(self,latestFile,segmentId,visible):
  624. segNode=self.volumeNode[latestFile]
  625. if segNode==None:
  626. print('Could not find segNode')
  627. return
  628. seg=segNode.GetSegmentation()
  629. segment=seg.GetSegment(segmentId)
  630. if segment==None:
  631. print('Could not find segment {}'.format(segmentId))
  632. return
  633. segNode.GetDisplayNode().SetSegmentVisibility(segmentId, visible)
  634. def saveSegmentation(self):
  635. #not used
  636. #get the latest key by adding an entry to SegmentationList
  637. copyFields=[self.isetup['participantField'],'patientCode','visitCode','User']
  638. outRow={x:self.segmentationEntry[x] for x in copyFields}
  639. sList=self.isetup['datasets']['SegmentationsList']
  640. resp=self.db.modifyRows('insert',sList['project'],
  641. sList['schema'],sList['query'],[outRow])
  642. encoding=chardet.detect(resp)['encoding']
  643. respJSON=json.loads(resp.decode(encoding))
  644. outRow=respJSON['rows'][0]
  645. #print(outRow)
  646. #construct file name with known key
  647. uName=re.sub(' ','_',self.remoteId['displayName'])
  648. fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
  649. self.segmentationEntry['patientCode'],
  650. self.segmentationEntry['visitCode'],
  651. uName,outRow['Key'])
  652. path=self.getSegmentationPath()
  653. path.append(fName)
  654. self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
  655. #update SegmentationList with know file name
  656. outRow['Segmentation']=fName
  657. self.db.modifyRows('update',sList['project'],
  658. sList['schema'],sList['query'],[outRow])
  659. #update SegmentationsMaster
  660. self.segmentationEntry['latestFile']=fName
  661. self.segmentationEntry['version']=outRow['Key']
  662. des=self.isetup['datasets']['SegmentationsMaster']
  663. op='insert'
  664. if self.segmentationEntry['origin']=='database':
  665. op='update'
  666. print('Saving: mode={}'.format(op))
  667. resp=self.db.modifyRows(op,des['project'],
  668. des['schema'],des['query'],[self.segmentationEntry])
  669. print(resp)
  670. #since we loaded a version, origin should be set to database
  671. self.loadSegmentation(self.segmentationEntry,0)
  672. #self.segmentationEntry['origin']='database'
  673. def saveNode(self,node,dataset,path):
  674. #not used
  675. fName=path[-1]
  676. localPath=os.path.join(self.tempDir,fName)
  677. slicer.util.saveNode(node,localPath)
  678. dset=self.isetup['datasets'][dataset]
  679. #exclude file name when building directory structure
  680. remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
  681. remotePath+='/'+fName
  682. self.fb.writeFileToFile(localPath,remotePath)
  683. #add entry to segmentation list
  684. def createSegmentation(self,entry):
  685. #not used
  686. #create segmentation entry for database update
  687. #note that origin is not set to database
  688. copyFields=[self.isetup['participantField'],'patientCode','visitCode','SequenceNum']
  689. #copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
  690. self.segmentationEntry={x:entry[x] for x in copyFields}
  691. self.segmentationEntry['User']=self.remoteId['id']
  692. self.segmentationEntry['origin']='created'
  693. self.segmentationEntry['version']=-1111
  694. #create a segmentation node
  695. uName=re.sub(' ','_',self.remoteId['displayName'])
  696. segNode=slicer.vtkMRMLSegmentationNode()
  697. self.volumeNode['Segmentation']=segNode
  698. segNode.SetName('Segmentation_{}_{}_{}'.
  699. format(entry['patientCode'],entry['visitCode'],uName))
  700. slicer.mrmlScene.AddNode(segNode)
  701. segNode.CreateDefaultDisplayNodes()
  702. segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['PET'])
  703. try:
  704. segmentList=self.isetup['segmentList']
  705. except KeyError:
  706. segmentList=self.segmentList
  707. for s in segmentList:
  708. segNode.GetSegmentation().AddEmptySegment(s,s)
  709. def clearVolumesAndSegmentations(self):
  710. nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
  711. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  712. res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
  713. try:
  714. self.volumeNode.clear()
  715. except AttributeError:
  716. pass
  717. #self.segmentationNode=None
  718. #self.reviewResult={}
  719. #self.aeList={}
  720. def setWindow(self,windowName):
  721. #default
  722. w=1400
  723. l=-500
  724. if windowName=='CT:bone':
  725. w=1000
  726. l=400
  727. if windowName=='CT:air':
  728. w=1000
  729. l=-426
  730. if windowName=='CT:abdomen':
  731. w=350
  732. l=40
  733. if windowName=='CT:brain':
  734. w=100
  735. l=50
  736. if windowName=='CT:lung':
  737. w=1400
  738. l=-500
  739. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  740. SetWindowLevel(w,l)
  741. print('setWindow: {} {}/{}'.format(windowName,w,l))
  742. class segmentationBrowserTest(ScriptedLoadableModuleTest):
  743. """
  744. This is the test case for your scripted module.
  745. Uses ScriptedLoadableModuleTest base class, available at:
  746. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  747. """
  748. def setup(self):
  749. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  750. """
  751. slicer.mrmlScene.Clear(0)
  752. def runTest(self):
  753. """Run as few or as many tests as needed here.
  754. """
  755. self.setUp()
  756. self.test_irAEMMBrowser()
  757. def test_irAEMMBrowser(self):
  758. """ Ideally you should have several levels of tests. At the lowest level
  759. tests sould exercise the functionality of the logic with different inputs
  760. (both valid and invalid). At higher levels your tests should emulate the
  761. way the user would interact with your code and confirm that it still works
  762. the way you intended.
  763. One of the most important features of the tests is that it should alert other
  764. developers when their changes will have an impact on the behavior of your
  765. module. For example, if a developer removes a feature that you depend on,
  766. your test should break so they know that the feature is needed.
  767. """
  768. self.delayDisplay("Starting the test")
  769. #
  770. # first, get some data
  771. #