Browse Source

First working version

Andrej Studen 2 years ago
parent
commit
5c54b514ed
1 changed files with 167 additions and 31 deletions
  1. 167 31
      slicerModules/imageBrowser.py

+ 167 - 31
slicerModules/imageBrowser.py

@@ -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={}