|
@@ -7,7 +7,8 @@ import datetime
|
|
|
import sys
|
|
|
import nixModule
|
|
|
import pathlib
|
|
|
-
|
|
|
+import chardet
|
|
|
+import re
|
|
|
#
|
|
|
# labkeySlicerPythonExtension
|
|
|
#
|
|
@@ -69,6 +70,9 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
self.petField=qt.QLabel("petResampled")
|
|
|
infoLayout.addRow("Data field (PET):",self.petField)
|
|
|
|
|
|
+ self.userField=qt.QLabel("Loading")
|
|
|
+ infoLayout.addRow("User",self.userField)
|
|
|
+
|
|
|
self.idField=qt.QLabel("Loading")
|
|
|
infoLayout.addRow("ID",self.idField)
|
|
|
|
|
@@ -115,6 +119,10 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
|
|
|
patientsFormLayout.addRow("Load patient",self.patientLoad)
|
|
|
|
|
|
+ self.patientSave=qt.QPushButton("Save")
|
|
|
+ self.patientSave.clicked.connect(self.onPatientSaveButtonClicked)
|
|
|
+ patientsFormLayout.addRow("Save segmentation",self.patientSave)
|
|
|
+
|
|
|
self.patientClear=qt.QPushButton("Clear")
|
|
|
self.patientClear.clicked.connect(self.onPatientClearButtonClicked)
|
|
|
patientsFormLayout.addRow("Clear patient",self.patientClear)
|
|
@@ -235,13 +243,14 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
pass
|
|
|
|
|
|
self.idField.setText(status['id'])
|
|
|
+ self.userField.setText(status['displayName'])
|
|
|
self.serverList.setStyleSheet('background-color: green')
|
|
|
|
|
|
def onPatientListChanged(self,i):
|
|
|
idFilter={'variable':'PatientId',
|
|
|
'value':self.patientList.currentText,
|
|
|
'oper':'eq'}
|
|
|
- ds=self.logic.getDataset([idFilter])
|
|
|
+ ds=self.logic.getDataset(dbFilter=[idFilter])
|
|
|
|
|
|
seq=[int(row['SequenceNum']) for row in ds['rows']]
|
|
|
self.visitList.clear()
|
|
@@ -260,7 +269,7 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
idFilter={'variable':'PatientId',\
|
|
|
'value':self.patientList.currentText,'oper':'eq'}
|
|
|
sFilter={'variable':'SequenceNum','value':s,'oper':'eq'}
|
|
|
- ds=self.logic.getDataset([idFilter,sFilter])
|
|
|
+ ds=self.logic.getDataset(dbFilter=[idFilter,sFilter])
|
|
|
if not len(ds['rows'])==1:
|
|
|
print("Found incorrect number {} of matches for [{}]/[{}]".\
|
|
|
format(len(ds['rows']),\
|
|
@@ -277,6 +286,7 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
print("Load")
|
|
|
#delegate loading to logic
|
|
|
self.logic.loadImages(self.currentRow,self.keepCached.isChecked())
|
|
|
+ self.logic.loadSegmentation(self.currentRow)
|
|
|
#self.logic.loadReview(self.currentRow)
|
|
|
#self.logic.loadAE(self.currentRow)
|
|
|
|
|
@@ -287,9 +297,9 @@ class imageBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
|
|
|
def onPatientClearButtonClicked(self):
|
|
|
self.logic.clearVolumesAndSegmentations()
|
|
|
- #self.reviewSegment.clear()
|
|
|
- #self.removeCompletedSegments()
|
|
|
- #self.reviewComment.clear()
|
|
|
+
|
|
|
+ def onPatientSaveButtonClicked(self):
|
|
|
+ self.logic.saveSegmentation()
|
|
|
|
|
|
def cleanup(self):
|
|
|
pass
|
|
@@ -348,6 +358,9 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
self.network=labkeyInterface.labkeyInterface()
|
|
|
self.dbBrowser=labkeyDatabaseBrowser
|
|
|
self.fBrowser=labkeyFileBrowser
|
|
|
+ self.tempDir=os.path.join(os.path.expanduser('~'),'temp')
|
|
|
+ if not os.path.isdir(self.tempDir):
|
|
|
+ os.mkdir(self.tempDir)
|
|
|
print('imageBrowserLogic setup complete')
|
|
|
|
|
|
|
|
@@ -365,13 +378,16 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
return status
|
|
|
fconfig=os.path.join(os.path.expanduser('~'),'.labkey',fileName)
|
|
|
self.network.init(fconfig)
|
|
|
- remoteId=self.network.getUserId()
|
|
|
+ self.remoteId=self.network.getUserId()
|
|
|
|
|
|
- if remoteId==None:
|
|
|
+ if self.remoteId==None:
|
|
|
status['error']='ID ERROR'
|
|
|
return status
|
|
|
|
|
|
- status['id']=remoteId['displayName']
|
|
|
+
|
|
|
+ status['displayName']=self.remoteId['displayName']
|
|
|
+ status['id']=self.remoteId['id']
|
|
|
+
|
|
|
#reset db and fb (they are thin classes anyhow)
|
|
|
self.db=self.dbBrowser.labkeyDB(self.network)
|
|
|
self.fb=self.fBrowser.labkeyFileBrowser(self.network)
|
|
@@ -388,34 +404,41 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
with open(fileName,'r') as f:
|
|
|
self.isetup=json.load(f)
|
|
|
|
|
|
- self.project=self.isetup['project']
|
|
|
+ #self.project=self.isetup['project']
|
|
|
#"iPNUMMretro/Study"
|
|
|
- self.schema='study'
|
|
|
- self.dataset=self.isetup['query']
|
|
|
+ #self.schema='study'
|
|
|
+ #self.dataset=self.isetup['query']
|
|
|
|
|
|
- ds=self.getDataset([])
|
|
|
+ ds=self.getDataset()
|
|
|
|
|
|
ids=[row[self.isetup['participantField']] for row in ds['rows']]
|
|
|
status['ids']=list(set(ids))
|
|
|
return status
|
|
|
|
|
|
- def getDataset(self,dbFilter):
|
|
|
- project=self.project
|
|
|
- schema=self.schema
|
|
|
- query=self.dataset
|
|
|
+ def getDataset(self,name="Imaging",dbFilter=[]):
|
|
|
+ dset=self.isetup['datasets'][name]
|
|
|
+ project=dset['project']
|
|
|
+ schema=dset['schema']
|
|
|
+ query=dset['query']
|
|
|
try:
|
|
|
return self.db.selectRows(project,schema,query, \
|
|
|
- dbFilter,self.isetup['view'])
|
|
|
+ dbFilter,dset['view'])
|
|
|
except KeyError:
|
|
|
return self.db.selectRows(project,schema,query,dbFilter)
|
|
|
|
|
|
- def loadImage(self,idx,path,tempDir,keepCached):
|
|
|
+ def loadImage(self,iData):
|
|
|
|
|
|
- localPath=os.path.join(tempDir,path[-1])
|
|
|
+ #unpack iData
|
|
|
+ idx=iData['idx']
|
|
|
+ path=iData['path']
|
|
|
+ keepCached=iData['keepCached']
|
|
|
+ dset=self.isetup['datasets'][iData['dataset']]
|
|
|
+
|
|
|
+ localPath=os.path.join(self.tempDir,path[-1])
|
|
|
|
|
|
if not os.path.isfile(localPath):
|
|
|
#download from server
|
|
|
- remotePath=self.fb.formatPathURL(self.project,'/'.join(path))
|
|
|
+ remotePath=self.fb.formatPathURL(dset['project'],'/'.join(path))
|
|
|
if not self.fb.entryExists(remotePath):
|
|
|
print("Failed to get {}".format(remotePath))
|
|
|
return
|
|
@@ -423,13 +446,16 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
self.fb.readFileToFile(remotePath,localPath)
|
|
|
|
|
|
properties={}
|
|
|
+ filetype='VolumeFile'
|
|
|
+
|
|
|
#make sure segmentation gets loaded as a labelmap
|
|
|
if idx=="Segmentation":
|
|
|
- properties["labelmap"]=1
|
|
|
+ filetype='SegmentationFile'
|
|
|
+ #properties["labelmap"]=1
|
|
|
|
|
|
|
|
|
self.volumeNode[idx]=slicer.util.loadNodeFromFile(localPath,
|
|
|
- filetype='VolumeFile',properties=properties)
|
|
|
+ filetype=filetype,properties=properties)
|
|
|
|
|
|
if not keepCached:
|
|
|
os.remove(localPath)
|
|
@@ -437,21 +463,19 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
|
|
|
def loadImages(self,row,keepCached):
|
|
|
|
|
|
- tempDir=os.path.join(os.path.expanduser('~'),'temp')
|
|
|
- if not os.path.isdir(tempDir):
|
|
|
- os.mkdir(tempDir)
|
|
|
|
|
|
#fields={'ctResampled':True,'petResampled':False}
|
|
|
fields={"CT":self.parent.ctField.text,\
|
|
|
"PET":self.parent.petField.text}
|
|
|
|
|
|
- path=['preprocessedImages',row['patientCode'],row['visitCode']]
|
|
|
+ path=[self.isetup['imageDir'],row['patientCode'],row['visitCode']]
|
|
|
relativePaths={x:path+[row[y]] for (x,y) in fields.items()}
|
|
|
|
|
|
self.volumeNode={}
|
|
|
for f in relativePaths:
|
|
|
- p=relativePaths[f]
|
|
|
- self.loadImage(f,p,tempDir,keepCached)
|
|
|
+ iData={'idx':f,'path':relativePaths[f],
|
|
|
+ 'keepCached':keepCached,'dataset':'Imaging'}
|
|
|
+ self.loadImage(iData)
|
|
|
|
|
|
#mimic abdominalCT standardized window setting
|
|
|
self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
|
|
@@ -462,12 +486,124 @@ class imageBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
SetAndObserveColorNodeID(\
|
|
|
slicer.util.getNode('PET-Heat').GetID())
|
|
|
slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
|
|
|
- foreground=self.volumeNode['PET'],foregroundOpacity=0.1,fit=True)
|
|
|
+ foreground=self.volumeNode['PET'],foregroundOpacity=0.5,fit=True)
|
|
|
+
|
|
|
+ def loadSegmentation(self,row):
|
|
|
+ userFilter={'variable':'User','value':'{}'.format(self.remoteId['id']),
|
|
|
+ 'oper':'eq'}
|
|
|
+ pField=self.isetup['participantField']
|
|
|
+ idFilter={'variable':pField,'value':row[pField],'oper':'eq'}
|
|
|
+ visitFilter={'variable':'visitCode','value':row['visitCode'],'oper':'eq'}
|
|
|
+ ds=self.getDataset(name='SegmentationsMaster',
|
|
|
+ dbFilter=[idFilter,visitFilter,userFilter])
|
|
|
+ if len(ds['rows'])>1:
|
|
|
+ print('Multiple segmentations found!')
|
|
|
+ return
|
|
|
+ if len(ds['rows'])==1:
|
|
|
+ #update self.segmentationEntry
|
|
|
+ self.segmentationEntry=ds['rows'][0]
|
|
|
+ self.segmentationEntry['origin']='database'
|
|
|
+ self.loadSegmentationFromEntry()
|
|
|
+ return
|
|
|
+ #create new segmentation
|
|
|
+ self.createSegmentation(row)
|
|
|
+
|
|
|
+ def getSegmentationPath(self):
|
|
|
+ path=[self.isetup['imageDir'],
|
|
|
+ self.segmentationEntry['patientCode'],
|
|
|
+ self.segmentationEntry['visitCode']]
|
|
|
+ path.append('Segmentations')
|
|
|
+ return path
|
|
|
+
|
|
|
+ def loadSegmentationFromEntry(self):
|
|
|
+ #compile path
|
|
|
+ entry=self.segmentationEntry
|
|
|
+ path=self.getSegmentationPath()
|
|
|
+ path.append(entry['latestFile'])
|
|
|
+ iData={'idx':'Segmentation','path':path,
|
|
|
+ 'keepCached':0,'dataset':'SegmentationsMaster'}
|
|
|
+ self.loadImage(iData)
|
|
|
+
|
|
|
+ def saveSegmentation(self):
|
|
|
+ #get the latest key by adding an entry to SegmentationList
|
|
|
+ copyFields=['ParticipantId','patientCode','visitCode','User']
|
|
|
+ outRow={x:self.segmentationEntry[x] for x in copyFields}
|
|
|
+ sList=self.isetup['datasets']['SegmentationsList']
|
|
|
+ resp=self.db.modifyRows('insert',sList['project'],
|
|
|
+ sList['schema'],sList['query'],[outRow])
|
|
|
+ encoding=chardet.detect(resp)['encoding']
|
|
|
+ respJSON=json.loads(resp.decode(encoding))
|
|
|
+ outRow=respJSON['rows'][0]
|
|
|
+ #print(outRow)
|
|
|
+
|
|
|
+ #construct file name with known key
|
|
|
+ uName=re.sub(' ','_',self.remoteId['displayName'])
|
|
|
+ fName='Segmentation_{}-{}_{}_{}.nrrd'.format(
|
|
|
+ self.segmentationEntry['patientCode'],
|
|
|
+ self.segmentationEntry['visitCode'],
|
|
|
+ uName,outRow['Key'])
|
|
|
+ path=self.getSegmentationPath()
|
|
|
+ path.append(fName)
|
|
|
+ self.saveNode(self.volumeNode['Segmentation'],'SegmentationsMaster',path)
|
|
|
+
|
|
|
+ #update SegmentationList with know file name
|
|
|
+ outRow['Segmentation']=fName
|
|
|
+ self.db.modifyRows('update',sList['project'],
|
|
|
+ sList['schema'],sList['query'],[outRow])
|
|
|
+
|
|
|
+ #update SegmentationsMaster
|
|
|
+ self.segmentationEntry['latestFile']=fName
|
|
|
+ self.segmentationEntry['version']=outRow['Key']
|
|
|
+ des=self.isetup['datasets']['SegmentationsMaster']
|
|
|
+ op='insert'
|
|
|
+ if self.segmentationEntry['origin']=='database':
|
|
|
+ op='update'
|
|
|
+ self.db.modifyRows(op,des['project'],
|
|
|
+ des['schema'],des['query'],[self.segmentationEntry])
|
|
|
+ #since we loaded a version, origin should be set to dataset
|
|
|
+ self.segmentationEntry['origin']='dataset'
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ def saveNode(self,node,dataset,path):
|
|
|
+ fName=path[-1]
|
|
|
+ localPath=os.path.join(self.tempDir,fName)
|
|
|
+ slicer.util.saveNode(node,localPath)
|
|
|
+
|
|
|
+ dset=self.isetup['datasets'][dataset]
|
|
|
+ #exclude file name when building directory structure
|
|
|
+ remotePath=self.fb.buildPathURL(dset['project'],path[:-1])
|
|
|
+ remotePath+='/'+fName
|
|
|
+ self.fb.writeFileToFile(localPath,remotePath)
|
|
|
+
|
|
|
+ #add entry to segmentation list
|
|
|
+
|
|
|
+ def createSegmentation(self,entry):
|
|
|
+
|
|
|
+ #create segmentation entry for database update
|
|
|
+ #note that origin is not set to database
|
|
|
+ copyFields=['ParticipantId','patientCode','visitCode','SequenceNum']
|
|
|
+ self.segmentationEntry={x:entry[x] for x in copyFields}
|
|
|
+ self.segmentationEntry['User']=self.remoteId['id']
|
|
|
+ self.segmentationEntry['origin']='created'
|
|
|
+ self.segmentationEntry['version']=-1111
|
|
|
+
|
|
|
+ #create a segmentation node
|
|
|
+ uName=re.sub(' ','_',self.remoteId['displayName'])
|
|
|
+ segNode=slicer.vtkMRMLSegmentationNode()
|
|
|
+ self.volumeNode['Segmentation']=segNode
|
|
|
+ segNode.SetName('Segmentation_{}_{}_{}'.
|
|
|
+ format(entry['patientCode'],entry['visitCode'],uName))
|
|
|
+ slicer.mrmlScene.AddNode(segNode)
|
|
|
+ segNode.CreateDefaultDisplayNodes()
|
|
|
+ segNode.SetReferenceImageGeometryParameterFromVolumeNode(self.volumeNode['PET'])
|
|
|
+ segNode.GetSegmentation().AddEmptySegment("Lesion","Lesion")
|
|
|
+
|
|
|
|
|
|
|
|
|
def clearVolumesAndSegmentations(self):
|
|
|
nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
|
|
|
- #nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
|
|
|
+ nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
|
|
|
res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
|
|
|
#self.segmentationNode=None
|
|
|
#self.reviewResult={}
|