imageBrowser.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  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. 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("ctResampled")
  60. infoLayout.addRow("Data field (CT):",self.ctField)
  61. self.petField=qt.QLabel("petResampled")
  62. infoLayout.addRow("Data field (PET):",self.petField)
  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.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.addItem("limfomiPET_iBrowser_selected.json")
  119. self.setupList.addItem("iraemm_iBrowserProspective.json")
  120. self.setupList.addItem("iraemm_iBrowserRetrospective.json")
  121. self.setupList.currentIndexChanged.connect(self.onSetupListChanged)
  122. setupFormLayout.addRow("Setup:",self.setupList)
  123. def addReviewSection(self):
  124. #
  125. # Review Area
  126. #
  127. reviewCollapsibleButton = ctk.ctkCollapsibleButton()
  128. reviewCollapsibleButton.text = "Review"
  129. self.layout.addWidget(reviewCollapsibleButton)
  130. self.reviewBoxLayout = qt.QVBoxLayout(reviewCollapsibleButton)
  131. self.reviewFormLayout = qt.QFormLayout()
  132. self.reviewSegment=qt.QComboBox()
  133. self.reviewSegment.currentIndexChanged.connect(\
  134. self.onReviewSegmentChanged)
  135. self.reviewFormLayout.addRow("Selected region:",self.reviewSegment)
  136. self.reviewResult=qt.QComboBox()
  137. sLabel="What do you think about the segmentation:"
  138. self.reviewFormLayout.addRow(sLabel,self.reviewResult)
  139. reviewOptions=['Select','Excellent','Minor deficiencies',\
  140. 'Major deficiencies','Unusable']
  141. for opt in reviewOptions:
  142. self.reviewResult.addItem(opt)
  143. self.aeResult=qt.QComboBox()
  144. aeLabel="Is organ suffering from adverse effect?"
  145. self.reviewFormLayout.addRow(aeLabel,self.aeResult)
  146. aeOptions=['Select','Yes','No']
  147. for opt in aeOptions:
  148. self.aeResult.addItem(opt)
  149. #self.aeResult.setCurrentIndex(0)
  150. self.updateReview=qt.QPushButton("Save")
  151. saLabel="Save segmentation and AE decision for current segment"
  152. self.reviewFormLayout.addRow(saLabel,self.updateReview)
  153. self.updateReview.clicked.connect(self.onUpdateReviewButtonClicked)
  154. self.reviewBoxLayout.addLayout(self.reviewFormLayout)
  155. submitFrame=qt.QGroupBox("Submit data")
  156. self.submitFormLayout=qt.QFormLayout()
  157. self.reviewComment=qt.QTextEdit("this is a test")
  158. self.submitFormLayout.addRow("Comments (optional)",self.reviewComment)
  159. self.submitReviewButton=qt.QPushButton("Submit")
  160. self.submitFormLayout.addRow("Submit to database",\
  161. self.submitReviewButton)
  162. self.submitReviewButton.clicked.connect(\
  163. self.onSubmitReviewButtonClicked)
  164. submitFrame.setLayout(self.submitFormLayout)
  165. submitFrame.setFlat(1)
  166. #submitFrame.setFrameShape(qt.QFrame.StyledPanel)
  167. #submitFrame.setFrameShadow(qt.QFrame.Sunken)
  168. submitFrame.setStyleSheet("background-color:rgba(220,215,180,45)")
  169. self.reviewBoxLayout.addWidget(submitFrame)
  170. def addSegmentEditor(self):
  171. editorCollapsibleButton = ctk.ctkCollapsibleButton()
  172. editorCollapsibleButton.text = "Segment Editor"
  173. self.layout.addWidget(editorCollapsibleButton)
  174. hLayout=qt.QVBoxLayout(editorCollapsibleButton)
  175. self.segmentEditorWidget=slicer.qMRMLSegmentEditorWidget()
  176. hLayout.addWidget(self.segmentEditorWidget)
  177. self.segmentEditorWidget.setMRMLScene(slicer.mrmlScene)
  178. segEditorNode=slicer.vtkMRMLSegmentEditorNode()
  179. slicer.mrmlScene.AddNode(segEditorNode)
  180. self.segmentEditorWidget.setMRMLSegmentEditorNode(segEditorNode)
  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. print("Load")
  279. #delegate loading to logic
  280. self.logic.loadImages(self.currentRow,self.keepCached.isChecked())
  281. self.logic.loadSegmentation(self.currentRow)
  282. self.setSegmentEditor()
  283. #self.logic.loadReview(self.currentRow)
  284. #self.logic.loadAE(self.currentRow)
  285. #self.onReviewSegmentChanged()
  286. def setSegmentEditor(self):
  287. #use current row to set segment in segment editor
  288. self.segmentEditorWidget.setSegmentationNode(
  289. self.logic.volumeNode['Segmentation'])
  290. self.segmentEditorWidget.setMasterVolumeNode(
  291. self.logic.volumeNode['PET'])
  292. def onReviewSegmentChanged(self):
  293. pass
  294. def onPatientClearButtonClicked(self):
  295. self.logic.clearVolumesAndSegmentations()
  296. self.patientSave.setStyleSheet('background-color:gray')
  297. def onPatientSaveButtonClicked(self):
  298. status=self.logic.saveSegmentation()
  299. if status:
  300. self.patientSave.setStyleSheet('background-color:green')
  301. else:
  302. self.patientSave.setStyleSheet('background-color:red')
  303. def onCtBoneButtonClicked(self):
  304. self.logic.setWindow('CT:bone')
  305. def onCtAirButtonClicked(self):
  306. self.logic.setWindow('CT:air')
  307. def onCtAbdomenButtonClicked(self):
  308. self.logic.setWindow('CT:abdomen')
  309. def onCtBrainButtonClicked(self):
  310. self.logic.setWindow('CT:brain')
  311. def onCtLungButtonClicked(self):
  312. self.logic.setWindow('CT:lung')
  313. def cleanup(self):
  314. pass
  315. def loadLibrary(name):
  316. #utility function to load nix library from git
  317. fwrapper=nixModule.getWrapper('nixWrapper.py')
  318. p=pathlib.Path(fwrapper)
  319. sys.path.append(str(p.parent))
  320. import nixWrapper
  321. return nixWrapper.loadLibrary(name)
  322. #
  323. # imageBrowserLogic
  324. #
  325. class imageBrowserLogic(ScriptedLoadableModuleLogic):
  326. """This class should implement all the actual
  327. computation done by your module. The interface
  328. should be such that other python code can import
  329. this class and make use of the functionality without
  330. requiring an instance of the Widget.
  331. Uses ScriptedLoadableModuleLogic base class, available at:
  332. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  333. """
  334. def __init__(self,parent=None):
  335. ScriptedLoadableModuleLogic.__init__(self, parent)
  336. print('imageBrowserLogic loading')
  337. if not parent==None:
  338. #use layout and data from parent widget
  339. self.parent=parent
  340. fhome=os.path.expanduser('~')
  341. fsetup=os.path.join(fhome,'.labkey','setup.json')
  342. try:
  343. with open(fsetup) as f:
  344. self.setup=json.load(f)
  345. except FileNotFoundError:
  346. self.setup={}
  347. try:
  348. pt=self.setup['paths']
  349. except KeyError:
  350. self.setup['paths']={}
  351. lName='labkeyInterface'
  352. loadLibrary(lName)
  353. import labkeyInterface
  354. import labkeyDatabaseBrowser
  355. import labkeyFileBrowser
  356. self.network=labkeyInterface.labkeyInterface()
  357. self.dbBrowser=labkeyDatabaseBrowser
  358. self.fBrowser=labkeyFileBrowser
  359. self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
  360. if not os.path.isdir(self.tempDir):
  361. os.mkdir(self.tempDir)
  362. self.lookups={}
  363. self.segmentList=["Liver","Blood","Marrow","Disease","Deauville"]
  364. print('imageBrowserLogic setup complete')
  365. def setServer(self,serverName):
  366. #additional way of setting the labkey network interface
  367. #if no parent was provided in logic initialization (stand-alone mode)
  368. status={}
  369. fileName="NONE"
  370. if serverName=="astuden":
  371. fileName="astuden.json"
  372. if serverName=="adoma":
  373. fileName="adoma.json"
  374. if serverName=="kzevnik":
  375. fileName="kzevnik.json"
  376. if fileName=="NONE":
  377. print("No path was associated with server {}".format(serverName))
  378. status['error']='KEY ERROR'
  379. return status
  380. fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
  381. self.network.init(fconfig)
  382. self.remoteId=self.network.getUserId()
  383. if self.remoteId==None:
  384. status['error']='ID ERROR'
  385. return status
  386. status['displayName']=self.remoteId['displayName']
  387. status['id']=self.remoteId['id']
  388. #reset db and fb (they are thin classes anyhow)
  389. self.db=self.dbBrowser.labkeyDB(self.network)
  390. self.fb=self.fBrowser.labkeyFileBrowser(self.network)
  391. return status
  392. def setConfig(self,configName):
  393. status={}
  394. fileName=os.path.join(os.path.expanduser('~'),'.labkey',configName)
  395. if not os.path.isfile(fileName):
  396. status['error']='FILE NOT FOUND'
  397. return status
  398. with open(fileName,'r') as f:
  399. self.isetup=json.load(f)
  400. #self.project=self.isetup['project']
  401. #"iPNUMMretro/Study"
  402. #self.schema='study'
  403. #self.dataset=self.isetup['query']
  404. #include filters...
  405. ds=self.getDataset()
  406. try:
  407. filterValue=self.isetup['filterEntries']
  408. except KeyError:
  409. #this is default
  410. ids=[row[self.isetup['participantField']] for row in ds['rows']]
  411. status['ids']=list(set(ids))
  412. return status
  413. #look for entries where segmentation was already done
  414. dsSet=self.getDataset('SegmentationsMaster')
  415. segMap={'{}:{}'.format(r['ParticipantId'],r['visitCode']):r['comments']
  416. for r in dsSet['rows']}
  417. ids=[]
  418. for r in ds['rows']:
  419. code='{}:{}'.format(r['ParticipantId'],r['visitCode'])
  420. try:
  421. comment=segMap[code]
  422. except KeyError:
  423. ids.append(r['ParticipantId'])
  424. continue
  425. if comment==filterValue:
  426. ids.append(r['ParticipantId'])
  427. status['ids']=list(set(ids))
  428. return status
  429. def getVarName(self,name="Imaging",var="visitField"):
  430. dset=self.isetup['datasets'][name]
  431. defaults={"visitField":"imagingVisitId"}
  432. try:
  433. return dset[var]
  434. except KeyError:
  435. return defaults[var]
  436. def getDataset(self,name="Imaging",dbFilter={}):
  437. dset=self.isetup['datasets'][name]
  438. project=dset['project']
  439. schema=dset['schema']
  440. query=dset['query']
  441. #add default filters
  442. qFilter=[]
  443. try:
  444. for qf in dset['filter']:
  445. v=dset['filter'][qf]
  446. qFilter.append({'variable':qf,'value':v,'oper':'eq'})
  447. except KeyError:
  448. pass
  449. for f in dbFilter:
  450. if f=='participant':
  451. qFilter.append({'variable':self.isetup['participantField'],
  452. 'value':dbFilter[f],'oper':'eq'})
  453. continue
  454. if f=='seqNum':
  455. qFilter.append({'variable':'SequenceNum',
  456. 'value':'{}'.format(dbFilter[f]),
  457. 'oper':'eq'})
  458. continue
  459. qFilter.append({'variable':f,'value':dbFilter[f],'oper':'eq'})
  460. try:
  461. ds=self.db.selectRows(project,schema,query, \
  462. qFilter,dset['view'])
  463. except KeyError:
  464. ds=self.db.selectRows(project,schema,query,qFilter)
  465. #get lookups as well
  466. lookups={}
  467. for f in ds['metaData']['fields']:
  468. try:
  469. lookup=f['lookup']
  470. except KeyError:
  471. continue
  472. var=f['name']
  473. lookupCode='{}:{}'.format(lookup['schema'],lookup['queryName'])
  474. try:
  475. lookups[var]=self.lookups[lookupCode]
  476. except KeyError:
  477. self.lookups[lookupCode]=self.loadLookup(project,lookup)
  478. lookups[var]=self.lookups[lookupCode]
  479. return {'rows':ds['rows'],'lookups':lookups}
  480. def loadLookup(self,project,lookup):
  481. qData={}
  482. key=lookup['keyColumn']
  483. value=lookup['displayColumn']
  484. fSet=self.db.selectRows(project,lookup['schema'],lookup['queryName'],[])
  485. for q in fSet['rows']:
  486. qData[q[key]]=q[value]
  487. return qData
  488. def loadImage(self,iData):
  489. #unpack iData
  490. idx=iData['idx']
  491. path=iData['path']
  492. keepCached=iData['keepCached']
  493. dset=self.isetup['datasets'][iData['dataset']]
  494. localPath=os.path.join(self.tempDir,path[-1])
  495. if not os.path.isfile(localPath):
  496. #download from server
  497. remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
  498. if not self.fb.entryExists(remotePath):
  499. print("Failed to get {}".format(remotePath))
  500. return
  501. self.fb.readFileToFile(remotePath,localPath)
  502. properties={}
  503. filetype='VolumeFile'
  504. #make sure segmentation gets loaded as a labelmap
  505. if idx=="Segmentation":
  506. filetype='SegmentationFile'
  507. #properties["labelmap"]=1
  508. self.volumeNode[idx]=slicer.util.loadNodeFromFile(localPath,
  509. filetype=filetype,properties=properties)
  510. if not keepCached:
  511. pass
  512. #os.remove(localPath)
  513. def loadImages(self,row,keepCached):
  514. #fields={'ctResampled':True,'petResampled':False}
  515. fields={"CT":self.parent.ctField.text,\
  516. "PET":self.parent.petField.text}
  517. path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
  518. relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
  519. self.volumeNode={}
  520. for f in relativePaths:
  521. iData={'idx':f,'path':relativePaths[f],
  522. 'keepCached':keepCached,'dataset':'Imaging'}
  523. self.loadImage(iData)
  524. #mimic abdominalCT standardized window setting
  525. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  526. SetAutoWindowLevel(False)
  527. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  528. SetWindowLevel(1400, -500)
  529. #set colormap for PET to PET-Heat (this is a verbatim setting from
  530. #the Volumes->Display->Lookup Table colormap identifier)
  531. self.volumeNode['PET'].GetScalarVolumeDisplayNode().\
  532. SetAndObserveColorNodeID(\
  533. slicer.util.getNode('Inferno').GetID())
  534. slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
  535. foreground=self.volumeNode['PET'],foregroundOpacity=0.5,fit=True)
  536. def loadSegmentation(self,row, loadFile=1):
  537. dbFilter={'User':'{}'.format(self.remoteId['id']),
  538. 'participant':row[self.isetup['participantField']],
  539. 'visitCode':row['visitCode']}
  540. ds=self.getDataset(name='SegmentationsMaster',
  541. dbFilter=dbFilter)
  542. if len(ds['rows'])>1:
  543. print('Multiple segmentations found!')
  544. return
  545. if len(ds['rows'])==1:
  546. #update self.segmentationEntry
  547. self.segmentationEntry=ds['rows'][0]
  548. self.segmentationEntry['origin']='database'
  549. if loadFile:
  550. self.loadSegmentationFromEntry()
  551. return
  552. #create new segmentation
  553. self.createSegmentation(row)
  554. def getSegmentationPath(self):
  555. path=[self.isetup['imageDir'],
  556. self.segmentationEntry['patientCode'],
  557. self.segmentationEntry['visitCode']]
  558. path.append('Segmentations')
  559. return path
  560. def loadSegmentationFromEntry(self):
  561. #compile path
  562. entry=self.segmentationEntry
  563. path=self.getSegmentationPath()
  564. path.append(entry['latestFile'])
  565. iData={'idx':'Segmentation','path':path,
  566. 'keepCached':0,'dataset':'SegmentationsMaster'}
  567. self.loadImage(iData)
  568. #look for missing segments
  569. segNode=self.volumeNode['Segmentation']
  570. seg=segNode.GetSegmentation()
  571. segNames=[seg.GetNthSegmentID(i) for i in range(seg.GetNumberOfSegments())]
  572. print('Segments')
  573. try:
  574. segmentList=self.isetup['segmentList']
  575. except KeyError:
  576. segmentList=self.segmentList
  577. for s in segmentList:
  578. if s not in segNames:
  579. seg.AddEmptySegment(s,s)
  580. print(s)
  581. print('Done')
  582. def saveSegmentation(self):
  583. #get the latest key by adding an entry to SegmentationList
  584. copyFields=[self.isetup['participantField'],'patientCode','visitCode','User']
  585. outRow={x:self.segmentationEntry[x] for x in copyFields}
  586. sList=self.isetup['datasets']['SegmentationsList']
  587. resp=self.db.modifyRows('insert',sList['project'],
  588. sList['schema'],sList['query'],[outRow])
  589. encoding=chardet.detect(resp)['encoding']
  590. respJSON=json.loads(resp.decode(encoding))
  591. outRow=respJSON['rows'][0]
  592. #print(outRow)
  593. #construct file name with known key
  594. uName=re.sub(' ','_',self.remoteId['displayName'])
  595. fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
  596. self.segmentationEntry['patientCode'],
  597. self.segmentationEntry['visitCode'],
  598. uName,outRow['Key'])
  599. path=self.getSegmentationPath()
  600. path.append(fName)
  601. status=self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
  602. #update SegmentationList with know file name
  603. outRow['Segmentation']=fName
  604. self.db.modifyRows('update',sList['project'],
  605. sList['schema'],sList['query'],[outRow])
  606. #update SegmentationsMaster
  607. self.segmentationEntry['latestFile']=fName
  608. self.segmentationEntry['version']=outRow['Key']
  609. des=self.isetup['datasets']['SegmentationsMaster']
  610. op='insert'
  611. if self.segmentationEntry['origin']=='database':
  612. op='update'
  613. print('Saving: mode={}'.format(op))
  614. resp=self.db.modifyRows(op,des['project'],
  615. des['schema'],des['query'],[self.segmentationEntry])
  616. print(resp)
  617. #since we loaded a version, origin should be set to database
  618. self.loadSegmentation(self.segmentationEntry,0)
  619. return status
  620. #self.segmentationEntry['origin']='database'
  621. def saveNode(self,node,dataset,path):
  622. fName=path[-1]
  623. localPath=os.path.join(self.tempDir,fName)
  624. slicer.util.saveNode(node,localPath)
  625. dset=self.isetup['datasets'][dataset]
  626. #exclude file name when building directory structure
  627. remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
  628. remotePath+='/'+fName
  629. self.fb.writeFileToFile(localPath,remotePath)
  630. return self.fb.entryExists(remotePath)
  631. #add entry to segmentation list
  632. def createSegmentation(self,entry):
  633. #create segmentation entry for database update
  634. #note that origin is not set to database
  635. copyFields=[self.isetup['participantField'],'patientCode','visitCode','SequenceNum']
  636. #copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
  637. self.segmentationEntry={x:entry[x] for x in copyFields}
  638. self.segmentationEntry['User']=self.remoteId['id']
  639. self.segmentationEntry['origin']='created'
  640. self.segmentationEntry['version']=-1111
  641. #create a segmentation node
  642. uName=re.sub(' ','_',self.remoteId['displayName'])
  643. segNode=slicer.vtkMRMLSegmentationNode()
  644. self.volumeNode['Segmentation']=segNode
  645. segNode.SetName('Segmentation_{}_{}_{}'.
  646. format(entry['patientCode'],entry['visitCode'],uName))
  647. slicer.mrmlScene.AddNode(segNode)
  648. segNode.CreateDefaultDisplayNodes()
  649. segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['PET'])
  650. try:
  651. segmentList=self.isetup['segmentList']
  652. except KeyError:
  653. segmentList=self.segmentList
  654. for s in segmentList:
  655. segNode.GetSegmentation().AddEmptySegment(s,s)
  656. def clearVolumesAndSegmentations(self):
  657. nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
  658. nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
  659. res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
  660. #self.segmentationNode=None
  661. #self.reviewResult={}
  662. #self.aeList={}
  663. def setWindow(self,windowName):
  664. #default
  665. w=1400
  666. l=-500
  667. if windowName=='CT:bone':
  668. w=1000
  669. l=400
  670. if windowName=='CT:air':
  671. w=1000
  672. l=-426
  673. if windowName=='CT:abdomen':
  674. w=350
  675. l=40
  676. if windowName=='CT:brain':
  677. w=100
  678. l=50
  679. if windowName=='CT:lung':
  680. w=1400
  681. l=-500
  682. self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
  683. SetWindowLevel(w,l)
  684. print('setWindow: {} {}/{}'.format(windowName,w,l))
  685. class imageBrowserTest(ScriptedLoadableModuleTest):
  686. """
  687. This is the test case for your scripted module.
  688. Uses ScriptedLoadableModuleTest base class, available at:
  689. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  690. """
  691. def setup(self):
  692. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  693. """
  694. slicer.mrmlScene.Clear(0)
  695. def runTest(self):
  696. """Run as few or as many tests as needed here.
  697. """
  698. self.setUp()
  699. self.test_irAEMMBrowser()
  700. def test_irAEMMBrowser(self):
  701. """ Ideally you should have several levels of tests. At the lowest level
  702. tests sould exercise the functionality of the logic with different inputs
  703. (both valid and invalid). At higher levels your tests should emulate the
  704. way the user would interact with your code and confirm that it still works
  705. the way you intended.
  706. One of the most important features of the tests is that it should alert other
  707. developers when their changes will have an impact on the behavior of your
  708. module. For example, if a developer removes a feature that you depend on,
  709. your test should break so they know that the feature is needed.
  710. """
  711. self.delayDisplay("Starting the test")
  712. #
  713. # first, get some data
  714. #