Pārlūkot izejas kodu

Merge branch 'master' of wiscigt.powertheword.com:oil/iraemm

NIX User 4 gadi atpakaļ
vecāks
revīzija
b718bab561
3 mainītis faili ar 256 papildinājumiem un 23 dzēšanām
  1. 12 1
      README.md
  2. 0 0
      Resources/.gitkeep
  3. 244 22
      slicerModule/iraemmBrowser.py

+ 12 - 1
README.md

@@ -7,6 +7,9 @@ A Slicer module was created to assist Radiology and Nuclear Medicine phsicians i
 reviewing the images. The following lists the installation, setup and usage of the module.
 
 ## Installation
+
+Here is the installation [video][iraeMMInstallation] that shows required steps.
+
 Download the [code][iraemm] and [dependencies][SlicerLabkeyExtension]. Unzip. To
 have Slicer know where the files are, open Slicer, and under Edit->Application settings 
 select Modules section. Under Paths, click on Add and navigate to newly unzipped
@@ -31,6 +34,11 @@ Once the data is entered, click on Init to check whether LabKey can be accessed.
 the button turns green, you are OK. Do `Save configuration`. 
 
 ## Usage
+
+
+See [video][iraeMMWorkflow] of module use, illustrating steps below. 
+Old version [here][iraeMMWorkflowOld].
+
 Use Labkey->iraemmBrowser module. The `Patients` section lets you select the patient
 and corresponding visit. On `Load` the data gets loaded from the server. 
 
@@ -85,4 +93,7 @@ in the `anonymization.py` and run it with `runPython.sh anonymization.py`.
 [nibabel]: https://nipy.org/nibabel/gettingstarted.html
 [labkeyInterface]: http://wiscigt.powertheword.com/andrej.studen/labkeyInterface
 [Orthanc]:https://www.orthanc-server.com
-[orthancInterface]: http://wiscigt.powertheword.com/andrej.studen/orthancinterface
+[orthancInterface]: http://wiscigt.powertheword.com/andrej.studen/orthancinterface
+[iraeMMWorkflowOld]: https://med1.fmf.uni-lj.si/owncloud/index.php/s/wrKOo1iUzgePzTi
+[iraeMMWorkflow]: https://med1.fmf.uni-lj.si/owncloud/index.php/s/iEETqswhTjlI2hV
+[iraeMMInstallation]: https://med1.fmf.uni-lj.si/owncloud/index.php/s/pAe4NBONHOGgEYO

+ 0 - 0
Resources/.gitkeep


+ 244 - 22
slicerModule/iraemmBrowser.py

@@ -48,7 +48,7 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
     self.network.parseConfig(fconfig)
     self.network.initRemote()
     self.project="iPNUMMretro/Study"
-    self.dataset="Imaging"
+    self.dataset="Imaging1"
     self.reviewDataset="ImageReview"
 
 
@@ -133,26 +133,52 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
     reviewCollapsibleButton.text = "Review"
     self.layout.addWidget(reviewCollapsibleButton)
     
-    reviewFormLayout = qt.QFormLayout(reviewCollapsibleButton)
+    self.reviewBoxLayout = qt.QVBoxLayout(reviewCollapsibleButton)
 
+    self.reviewFormLayout = qt.QFormLayout()
+
+
+    self.reviewSegment=qt.QComboBox()
+    self.reviewSegment.currentIndexChanged.connect(self.onReviewSegmentChanged)
+    self.reviewFormLayout.addRow("Selected region:",self.reviewSegment)
+    
    
+
     self.reviewResult=qt.QComboBox()
-    reviewFormLayout.addRow("What do you think about the segmentation:",\
+    self.reviewFormLayout.addRow("What do you think about the segmentation:",\
             self.reviewResult)
-    self.reviewResult.addItem("Excellent")
-    self.reviewResult.addItem("Minor deficiencies")
-    self.reviewResult.addItem("Major deficiencies")
-    self.reviewResult.addItem("Unusable")
+    reviewOptions=['Select','Excellent','Minor deficiencies',\
+            'Major deficiencies','Unusable']
+    for opt in reviewOptions:
+        self.reviewResult.addItem(opt)
     
-    self.reviewComment=qt.QLineEdit("this is a test")
-    reviewFormLayout.addRow("Comments (optional)",\
+    self.updateReview=qt.QPushButton("Save")
+    self.reviewFormLayout.\
+            addRow("Save segmentation decision for current segment",\
+            self.updateReview)
+    self.updateReview.clicked.connect(self.onUpdateReviewButtonClicked)
+
+    self.reviewBoxLayout.addLayout(self.reviewFormLayout)
+
+    submitFrame=qt.QGroupBox("Submit data")
+    
+    self.submitFormLayout=qt.QFormLayout()
+
+    self.reviewComment=qt.QTextEdit("this is a test")
+    self.submitFormLayout.addRow("Comments (optional)",\
             self.reviewComment)
     
     self.submitReviewButton=qt.QPushButton("Submit")
-    reviewFormLayout.addRow("Submit to database",\
+    self.submitFormLayout.addRow("Submit to database",\
             self.submitReviewButton)
     self.submitReviewButton.clicked.connect(self.onSubmitReviewButtonClicked)
-
+    
+    submitFrame.setLayout(self.submitFormLayout)
+    submitFrame.setFlat(1)
+    #submitFrame.setFrameShape(qt.QFrame.StyledPanel)
+    #submitFrame.setFrameShadow(qt.QFrame.Sunken)
+    submitFrame.setStyleSheet("background-color:rgba(220,215,180,45)")
+    self.reviewBoxLayout.addWidget(submitFrame)
 
   def onPatientListChanged(self,i):
       idFilter={'variable':'PatientId','value':self.patientList.currentText,'oper':'eq'}
@@ -186,15 +212,30 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
       self.ctCode.setText(row[self.ctField.text])
       self.segmentationCode.setText(row[self.segmentationField.text])
 
-
   def onPatientLoadButtonClicked(self):
       print("Load")
       #delegate loading to logic
       #try:
       self.logic.loadImage(self.currentRow,self.keepCached.isChecked())
+      segmentList=self.logic.compileSegmentation()
+      importantSegmentList=['liver','bowel','thyroid','lung','spleen']
+      #also bladder,vertebraL1, stomach, heart
+      for seg in segmentList:
+          if not seg in importantSegmentList:
+              continue
+          #filter to most important ones
+          self.reviewSegment.addItem(seg)
+      self.logic.loadReview(self.currentRow)
+      self.onReviewSegmentChanged()
       #except AttributeError:
       #    print("Missing current row")
       #    return
+  
+  def onReviewSegmentChanged(self):
+      self.logic.hideSegments()
+      self.logic.showSegment(self.reviewSegment.currentText)
+      #set reviewFlag to stored value
+      self.reviewResult.setCurrentIndex(self.logic.getReviewResult(self.reviewSegment.currentText)) 
 
   def onSubmitReviewButtonClicked(self):
       print("Submit")
@@ -202,11 +243,60 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
           self.reviewResult.currentText))
       print("Comment:{}".format(self.reviewComment))
       self.logic.submitReview(self.currentRow,\
-              self.reviewResult.currentIndex,\
-              self.reviewComment.text)
+              self.reviewComment.plainText)
+
+  def onUpdateReviewButtonClicked(self):
+      print("Save")
+      
+      self.logic.updateReview(self.reviewSegment.currentText,\
+              self.reviewResult.currentIndex)
+      
+      idx=self.findCompletedSegment(self.reviewSegment.currentText)
+      if idx<0:
+          qReview=qt.QLabel(self.reviewResult.currentText)
+          self.submitFormLayout.insertRow(0,self.reviewSegment.currentText,qReview)
+          try:
+              self.segmentsCompleted.append(self.reviewSegment.currentText)
+          except AttributeError:
+              self.segmentsCompleted=[]
+              self.segmentsCompleted.append(self.reviewSegment.currentText)
+      else:
+          qReview=self.submitFormLayout.itemAt(idx,1).widget()
+          qReview.setText(self.reviewResult.currentText)
+
+      colors=['pink','green','yellow','orange','red']
+      qReview.setStyleSheet("background-color: "+colors[self.reviewResult.currentIndex])
+  
+  def findCompletedSegment(self,segment):
+
+      for i in range(self.submitFormLayout.rowCount()):
+          if self.submitFormLayout.itemAt(i,0).widget().text==segment:
+              return i
+      return -1
+
+  def removeCompletedSegments(self):
+      
+      try:
+          segments=self.segmentsCompleted
+      except AttributeError:
+          return 
+
+      for seg in segments:
+          idx=self.findCompletedSegment(seg)
+          if idx>-1:
+              self.submitFormLayout.removeRow(idx)
+      
+      self.segmentsCompleted=[]
+
 
   def onPatientClearButtonClicked(self):
       self.logic.clearVolumesAndSegmentations()
+      self.reviewSegment.clear()
+      self.removeCompletedSegments()
+
+      
+
+  
 
 
 
@@ -234,6 +324,12 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
           self.net=parent.network
           self.project=parent.project
 
+      self.segLabel={'1':'liver','2':'spleen','3':'lung','4':'thyroid',\
+              '5':'kidney','6':'pancreas','7':'gallbladder','8':'bladder',\
+              '9':'aorta','10':'trachea','11':'sternum','12':'vertebraL1',\
+              '13':'adrenal','14':'psoasMajor','15':'rectus',\
+              '16':'bowel','17':'stomach','18':'heart'}
+
   def setLabkeyInterface(self,net):
       #additional way of setting the labkey network interface 
       #if no parent was provided in logic initialization (stand-alone mode)
@@ -254,12 +350,12 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
              +row['patientCode']+'/'+row['visitCode']+'/'+row[y]\
              for (x,y) in fields.items()}
 
-      volumeNode={}
+      self.volumeNode={}
       for f in relativePaths:
           p=relativePaths[f]
           labkeyPath=self.net.GetLabkeyPathFromRelativePath(p)
           rp=self.net.head(labkeyPath)
-          if not rp.code==200:
+          if not slicerNetwork.labkeyURIHandler.HTTPStatus(rp):
               print("Failed to get {}".format(labkeyPath))
               continue
 
@@ -269,18 +365,137 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
           if f=="Segmentation":
               properties["labelmap"]=1
 
-          volumeNode[f]=self.net.loadNode(p,'VolumeFile',\
+          self.volumeNode[f]=self.net.loadNode(p,'VolumeFile',\
                   properties=properties,returnNode=True,keepCached=keepCached)
-          
-      slicer.util.setSliceViewerLayers(background=volumeNode['CT'],\
-          foreground=volumeNode['PET'],foregroundOpacity=0.5,fit=True)
+
+      #mimic abdominalCT standardized window setting
+      self.volumeNode['CT'].GetScalarVolumeDisplayNode().\
+              SetWindowLevel(1400, -500)
+      #set colormap for PET to PET-Heat (this is a verbatim setting from
+      #the Volumes->Display->Lookup Table colormap identifier)
+      self.volumeNode['PET'].GetScalarVolumeDisplayNode().\
+              SetAndObserveColorNodeID(\
+              slicer.util.getNode('PET-Heat').GetID())
+      slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
+          foreground=self.volumeNode['PET'],foregroundOpacity=0.1,fit=True)
+
+
+  #segmentations
+
+  def compileSegmentation(self):
+      try:
+          labelmapVolumeNode = self.volumeNode['Segmentation']
+      except KeyError:
+          print("No segmentaion volumeNode available")
+          return
+     
+      self.segmentationNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
+      slicer.modules.segmentations.logic().\
+              ImportLabelmapToSegmentationNode(labelmapVolumeNode, self.segmentationNode)
+      
+      
+      segmentList=[]
+      
+      seg=self.segmentationNode.GetSegmentation()
+
+      for i in range(seg.GetNumberOfSegments()):
+          segment=seg.GetNthSegment(i)
+          segment.SetName(self.segLabel[segment.GetName()])
+          segmentList.append(segment.GetName())
+
+      #seg.CreateClosedSurfaceRepresentation()
+      slicer.mrmlScene.RemoveNode(labelmapVolumeNode)
+      self.volumeNode.pop('Segmentation',None)
+
+      #return list of segment names
+      return segmentList
+  
+  def hideSegments(self):
+
+      try:
+          displayNode=self.segmentationNode.GetDisplayNode()
+      except AttributeError:
+          return
+
+      seg=self.segmentationNode.GetSegmentation()
+      for i in range(seg.GetNumberOfSegments()):
+          #segment=self.segmentationNode.GetSegmentation().GetNthSegment(i)
+          segmentID=seg.GetNthSegmentID(i)
+          displayNode.SetSegmentVisibility(segmentID, False)
+      #print("Done")
+  
+  def showSegment(self,name):
+      try:
+          displayNode=self.segmentationNode.GetDisplayNode()
+      except AttributeError:
+          return
+      
+      seg=self.segmentationNode.GetSegmentation()
+      for i in range(seg.GetNumberOfSegments()):
+          segment=seg.GetNthSegment(i)
+          if not segment.GetName()==name:
+              continue
+          segmentID=seg.GetNthSegmentID(i)
+          displayNode.SetSegmentVisibility(segmentID, True)
+          break
+      #print("Done")
+  
+
+  #clear 
 
   def clearVolumesAndSegmentations(self):
       nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
       nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
       res=[slicer.mrmlScene.RemoveNode(f) for f in nodes] 
+      self.segmentationNode=None
+      self.reviewResult={}
+
+  #reviews by segment
+
+  def updateReview(self,segment,value):
+      try:
+          self.reviewResult[segment]=value
+      except AttributeError:
+          self.reviewResult={}
+          self.updateReview(segment,value)
+  
+  def getReviewResult(self,segment):
+      try:
+          return self.reviewResult[segment]
+      except AttributeError:
+          #review result not initialized
+          return 0
+      except KeyError:
+          #segment not done yet
+          return 0
+  
+  #load review from labkey
+  def loadReview(self,currentRow):
+
+      #see if we have already done a review
+      filters=[]
+      fields=['PatientId','SequenceNum']
 
-  def submitReview(self,currentRow,idx,comment):
+
+      for f in fields:
+          filters.append({'variable':f,'value':str(currentRow[f]),'oper':'eq'})
+      
+      ds=self.net.filterDataset(self.parent.project,\
+              self.parent.reviewDataset,filters)
+
+      if len(ds['rows'])==0:
+          return
+
+      row=ds['rows'][0]
+      for label in self.segLabel:
+          name=self.segLabel[label]+'Review'
+          try:
+              self.updateReview(self.segLabel[label],row[name])
+          except KeyError:
+              continue 
+   
+  #submit review to labkey
+  def submitReview(self,currentRow,comment):
       row={}
 
       fields=['PatientId','SequenceNum']
@@ -303,7 +518,14 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
           for f in fields:
               row[f]=currentRow[f]
       
-      row['reviewResult']=idx+1 #labkey has 1-based arrays
+      seg=self.segmentationNode.GetSegmentation()
+ 
+      for i in range(seg.GetNumberOfSegments()):
+          segment=seg.GetNthSegment(i)
+          fieldName=segment.GetName()+'Review'
+          value=self.getReviewResult(segment.GetName())
+          row[fieldName]=value
+
       row['reviewComment']=comment
       row['Date']=datetime.datetime.now().ctime()
       ds=self.net.modifyDataset(mode,self.parent.project,\