|
@@ -133,26 +133,48 @@ 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.QFrame()
|
|
|
+
|
|
|
+ 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.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,16 +208,29 @@ 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())
|
|
|
- self.logic.compileSegmentation()
|
|
|
+ segmentList=self.logic.compileSegmentation()
|
|
|
+ importantSegmentList=['liver','bowel','thyroid','bladder']
|
|
|
+ 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")
|
|
@@ -203,11 +238,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()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -235,6 +319,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)
|
|
@@ -260,7 +350,7 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
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
|
|
|
|
|
@@ -284,6 +374,9 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
slicer.util.setSliceViewerLayers(background=self.volumeNode['CT'],\
|
|
|
foreground=self.volumeNode['PET'],foregroundOpacity=0.5,fit=True)
|
|
|
|
|
|
+
|
|
|
+ #segmentations
|
|
|
+
|
|
|
def compileSegmentation(self):
|
|
|
try:
|
|
|
labelmapVolumeNode = self.volumeNode['Segmentation']
|
|
@@ -291,30 +384,113 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
print("No segmentaion volumeNode available")
|
|
|
return
|
|
|
|
|
|
- seg = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
|
|
|
+ self.segmentationNode = slicer.mrmlScene.AddNewNodeByClass('vtkMRMLSegmentationNode')
|
|
|
slicer.modules.segmentations.logic().\
|
|
|
- ImportLabelmapToSegmentationNode(labelmapVolumeNode, seg)
|
|
|
+ ImportLabelmapToSegmentationNode(labelmapVolumeNode, self.segmentationNode)
|
|
|
|
|
|
- segLabel={'1':'liver','2':'spleen','3':'lung','4':'thyroid',\
|
|
|
- '5':'kidney','6':'pancreas','7':'gallbladder','8':'bladder',\
|
|
|
- '9':'aorta','10':'trachea','11':'sternum','12':'vertebra L1',\
|
|
|
- '13':'adrenal','14':'psoas major','15':'rectus',\
|
|
|
- '16':'bowel','17':'stomach','18':'heart'}
|
|
|
|
|
|
- for i in range(seg.GetSegmentation().GetNumberOfSegments()):
|
|
|
- segment=seg.GetSegmentation().GetNthSegment(i)
|
|
|
- segment.SetName(segLabel[segment.GetName()])
|
|
|
+ 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']
|
|
|
+
|
|
|
+
|
|
|
+ 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
|
|
|
|
|
|
- def submitReview(self,currentRow,idx,comment):
|
|
|
+ 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']
|
|
@@ -337,7 +513,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,\
|