|
@@ -5,6 +5,7 @@ from slicer.ScriptedLoadableModule import *
|
|
|
import slicerNetwork
|
|
|
import loadDicom
|
|
|
import json
|
|
|
+import datetime
|
|
|
|
|
|
#
|
|
|
# labkeySlicerPythonExtension
|
|
@@ -43,11 +44,12 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
# Instantiate and connect widgets ...
|
|
|
self.network=slicerNetwork.labkeyURIHandler()
|
|
|
|
|
|
- fconfig=os.path.join(os.path.expanduser('~'),'.labkey','onko-nix.json')
|
|
|
+ fconfig=os.path.join(os.path.expanduser('~'),'.labkey','network.json')
|
|
|
self.network.parseConfig(fconfig)
|
|
|
self.network.initRemote()
|
|
|
self.project="iPNUMMretro/Study"
|
|
|
- self.dataset="Imaging1"
|
|
|
+ self.dataset="Imaging"
|
|
|
+ self.reviewDataset="ImageReview"
|
|
|
|
|
|
|
|
|
self.logic=iraemmBrowserLogic(self)
|
|
@@ -59,42 +61,99 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
|
|
|
|
|
|
#
|
|
|
- # Parameters Area
|
|
|
+ # Setup Area
|
|
|
#
|
|
|
- connectionCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
- connectionCollapsibleButton.text = "Patients"
|
|
|
- self.layout.addWidget(connectionCollapsibleButton)
|
|
|
+ setupCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
+ setupCollapsibleButton.text = "Setup"
|
|
|
+ self.layout.addWidget(setupCollapsibleButton)
|
|
|
+
|
|
|
+ setupFormLayout = qt.QFormLayout(setupCollapsibleButton)
|
|
|
+
|
|
|
+
|
|
|
+ self.ctField=qt.QLabel("ctResampled")
|
|
|
+ setupFormLayout.addRow("Data field (CT):",self.ctField)
|
|
|
+
|
|
|
+ self.petField=qt.QLabel("petResampled")
|
|
|
+ setupFormLayout.addRow("Data field (PET):",self.petField)
|
|
|
+
|
|
|
+ self.segmentationField=qt.QLabel("Segmentation")
|
|
|
+ setupFormLayout.addRow("Data field (Segmentation):",self.segmentationField)
|
|
|
+
|
|
|
|
|
|
- connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
|
|
|
+ #
|
|
|
+ # Patienrs Area
|
|
|
+ #
|
|
|
+ patientsCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
+ patientsCollapsibleButton.text = "Patients"
|
|
|
+ self.layout.addWidget(patientsCollapsibleButton)
|
|
|
+
|
|
|
+ patientsFormLayout = qt.QFormLayout(patientsCollapsibleButton)
|
|
|
|
|
|
|
|
|
self.patientList=qt.QComboBox()
|
|
|
for id in ids:
|
|
|
self.patientList.addItem(id)
|
|
|
self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
|
|
|
- connectionFormLayout.addRow("Patient:",self.patientList)
|
|
|
+ patientsFormLayout.addRow("Patient:",self.patientList)
|
|
|
|
|
|
self.visitList=qt.QComboBox()
|
|
|
self.visitList.currentIndexChanged.connect(self.onVisitListChanged)
|
|
|
- connectionFormLayout.addRow("Visit:",self.visitList)
|
|
|
+ patientsFormLayout.addRow("Visit:",self.visitList)
|
|
|
|
|
|
- self.ctCode=qt.QLabel("ctCode")
|
|
|
- connectionFormLayout.addRow("CT:",self.ctCode)
|
|
|
|
|
|
+ self.ctCode=qt.QLabel("ctCode")
|
|
|
+ patientsFormLayout.addRow("CT:",self.ctCode)
|
|
|
+
|
|
|
self.petCode=qt.QLabel("petCode")
|
|
|
- connectionFormLayout.addRow("PET:",self.petCode)
|
|
|
+ patientsFormLayout.addRow("PET:",self.petCode)
|
|
|
+
|
|
|
+ self.segmentationCode=qt.QLabel("segmentationCode")
|
|
|
+ patientsFormLayout.addRow("Segmentation",self.segmentationCode)
|
|
|
|
|
|
self.patientLoad=qt.QPushButton("Load")
|
|
|
self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
|
|
|
- connectionFormLayout.addRow("Load patient",self.patientLoad)
|
|
|
+ patientsFormLayout.addRow("Load patient",self.patientLoad)
|
|
|
|
|
|
+ self.patientClear=qt.QPushButton("Clear")
|
|
|
+ self.patientClear.clicked.connect(self.onPatientClearButtonClicked)
|
|
|
+ patientsFormLayout.addRow("Clear patient",self.patientClear)
|
|
|
+
|
|
|
self.keepCached=qt.QCheckBox("keep Cached")
|
|
|
self.keepCached.setChecked(1)
|
|
|
- connectionFormLayout.addRow("Keep cached",self.keepCached)
|
|
|
+ patientsFormLayout.addRow("Keep cached",self.keepCached)
|
|
|
|
|
|
#set to a defined state
|
|
|
self.onPatientListChanged(0)
|
|
|
|
|
|
+
|
|
|
+ #
|
|
|
+ # Review Area
|
|
|
+ #
|
|
|
+ reviewCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
+ reviewCollapsibleButton.text = "Review"
|
|
|
+ self.layout.addWidget(reviewCollapsibleButton)
|
|
|
+
|
|
|
+ reviewFormLayout = qt.QFormLayout(reviewCollapsibleButton)
|
|
|
+
|
|
|
+
|
|
|
+ self.reviewResult=qt.QComboBox()
|
|
|
+ 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")
|
|
|
+
|
|
|
+ self.reviewComment=qt.QLineEdit("this is a test")
|
|
|
+ reviewFormLayout.addRow("Comments (optional)",\
|
|
|
+ self.reviewComment)
|
|
|
+
|
|
|
+ self.submitReviewButton=qt.QPushButton("Submit")
|
|
|
+ reviewFormLayout.addRow("Submit to database",\
|
|
|
+ self.submitReviewButton)
|
|
|
+ self.submitReviewButton.clicked.connect(self.onSubmitReviewButtonClicked)
|
|
|
+
|
|
|
+
|
|
|
def onPatientListChanged(self,i):
|
|
|
idFilter={'variable':'PatientId','value':self.patientList.currentText,'oper':'eq'}
|
|
|
ds=self.network.filterDataset(self.project,self.dataset, [idFilter])
|
|
@@ -111,18 +170,21 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
except IndexError:
|
|
|
return
|
|
|
print("Visit: Selected item: {}->{}".format(i,s))
|
|
|
- idFilter={'variable':'PatientId','value':self.patientList.currentText,'oper':'eq'}
|
|
|
+ idFilter={'variable':'PatientId',\
|
|
|
+ 'value':self.patientList.currentText,'oper':'eq'}
|
|
|
sFilter={'variable':'SequenceNum','value':s,'oper':'eq'}
|
|
|
ds=self.network.filterDataset(self.project,self.dataset,[idFilter,sFilter])
|
|
|
if not len(ds['rows'])==1:
|
|
|
- print("Found incorrect number {} of matches for [{}]/[{}]".format(len(ds['rows']),\
|
|
|
+ print("Found incorrect number {} of matches for [{}]/[{}]".\
|
|
|
+ format(len(ds['rows']),\
|
|
|
self.patientList.currentText,s))
|
|
|
row=ds['rows'][0]
|
|
|
|
|
|
#copy row properties for data access
|
|
|
self.currentRow=row
|
|
|
- self.petCode.setText(row['petResampled'])
|
|
|
- self.ctCode.setText(row['ctResampled'])
|
|
|
+ self.petCode.setText(row[self.petField.text])
|
|
|
+ self.ctCode.setText(row[self.ctField.text])
|
|
|
+ self.segmentationCode.setText(row[self.segmentationField.text])
|
|
|
|
|
|
|
|
|
def onPatientLoadButtonClicked(self):
|
|
@@ -134,6 +196,20 @@ class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
# print("Missing current row")
|
|
|
# return
|
|
|
|
|
|
+ def onSubmitReviewButtonClicked(self):
|
|
|
+ print("Submit")
|
|
|
+ print("Selected review:{}/{}".format(self.reviewResult.currentIndex,
|
|
|
+ self.reviewResult.currentText))
|
|
|
+ print("Comment:{}".format(self.reviewComment))
|
|
|
+ self.logic.submitReview(self.currentRow,\
|
|
|
+ self.reviewResult.currentIndex,\
|
|
|
+ self.reviewComment.text)
|
|
|
+
|
|
|
+ def onPatientClearButtonClicked(self):
|
|
|
+ self.logic.clearVolumesAndSegmentations()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
def cleanup(self):
|
|
|
pass
|
|
|
|
|
@@ -170,11 +246,13 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
|
|
|
|
|
|
#fields={'ctResampled':True,'petResampled':False}
|
|
|
- fields=['ctResampled','petResampled']
|
|
|
+ fields={"CT":self.parent.ctField.text,\
|
|
|
+ "PET":self.parent.petField.text,\
|
|
|
+ "Segmentation":self.parent.segmentationField.text}
|
|
|
|
|
|
relativePaths={x:self.project+'/@files/preprocessedImages/'\
|
|
|
- +row['patientCode']+'/'+row['visitCode']+'/'+row[x]\
|
|
|
- for x in fields}
|
|
|
+ +row['patientCode']+'/'+row['visitCode']+'/'+row[y]\
|
|
|
+ for (x,y) in fields.items()}
|
|
|
|
|
|
volumeNode={}
|
|
|
for f in relativePaths:
|
|
@@ -186,13 +264,56 @@ class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
continue
|
|
|
|
|
|
#pushes it to background
|
|
|
- volumeNode[f]=self.net.loadNode(p,'VolumeFile',returnNode=True,keepCached=keepCached)
|
|
|
+ properties={}
|
|
|
+ #make sure segmentation gets loaded as a labelmap
|
|
|
+ if f=="Segmentation":
|
|
|
+ properties["labelmap"]=1
|
|
|
+
|
|
|
+ volumeNode[f]=self.net.loadNode(p,'VolumeFile',\
|
|
|
+ properties=properties,returnNode=True,keepCached=keepCached)
|
|
|
|
|
|
- slicer.util.setSliceViewerLayers(background=volumeNode['ctResampled'],\
|
|
|
- foreground=volumeNode['petResampled'],foregroundOpacity=0.5,fit=True)
|
|
|
+ slicer.util.setSliceViewerLayers(background=volumeNode['CT'],\
|
|
|
+ foreground=volumeNode['PET'],foregroundOpacity=0.5,fit=True)
|
|
|
+
|
|
|
+ def clearVolumesAndSegmentations(self):
|
|
|
+ nodes=slicer.util.getNodesByClass("vtkMRMLVolumeNode")
|
|
|
+ nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
|
|
|
+ res=[slicer.mrmlScene.RemoveNode(f) for f in nodes]
|
|
|
+
|
|
|
+ def submitReview(self,currentRow,idx,comment):
|
|
|
+ row={}
|
|
|
+
|
|
|
+ fields=['PatientId','SequenceNum']
|
|
|
+
|
|
|
+ #see if we have to update or insert
|
|
|
+ filters=[]
|
|
|
+
|
|
|
+ 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)
|
|
|
+
|
|
|
+ mode='insert'
|
|
|
+
|
|
|
+ if len(ds['rows'])>0:
|
|
|
+ row=ds['rows'][0]
|
|
|
+ mode='update'
|
|
|
+ else:
|
|
|
+ for f in fields:
|
|
|
+ row[f]=currentRow[f]
|
|
|
+
|
|
|
+ row['reviewResult']=idx+1 #labkey has 1-based arrays
|
|
|
+ row['reviewComment']=comment
|
|
|
+ row['Date']=datetime.datetime.now().ctime()
|
|
|
+ ds=self.net.modifyDataset(mode,self.parent.project,\
|
|
|
+ self.parent.reviewDataset,[row])
|
|
|
+
|
|
|
+ print("review submitted")
|
|
|
|
|
|
|
|
|
class irAEMMBrowserTest(ScriptedLoadableModuleTest):
|
|
|
+
|
|
|
"""
|
|
|
This is the test case for your scripted module.
|
|
|
Uses ScriptedLoadableModuleTest base class, available at:
|