segmentationBrowser.py 30 KB

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