|
@@ -0,0 +1,229 @@
|
|
|
+import os
|
|
|
+import unittest
|
|
|
+from __main__ import vtk, qt, ctk, slicer
|
|
|
+from slicer.ScriptedLoadableModule import *
|
|
|
+import slicerNetwork
|
|
|
+import loadDicom
|
|
|
+import json
|
|
|
+
|
|
|
+#
|
|
|
+# labkeySlicerPythonExtension
|
|
|
+#
|
|
|
+
|
|
|
+class iraemmBrowser(ScriptedLoadableModule):
|
|
|
+ """Uses ScriptedLoadableModule base class, available at:
|
|
|
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self, parent):
|
|
|
+ ScriptedLoadableModule.__init__(self, parent)
|
|
|
+ self.parent.title = "irAEMM Browser" # TODO make this more human readable by adding spaces
|
|
|
+ self.parent.categories = ["LabKey"]
|
|
|
+ self.parent.dependencies = []
|
|
|
+ self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
|
|
|
+ self.parent.helpText = """
|
|
|
+ Interface to irAEMM files in LabKey
|
|
|
+ """
|
|
|
+ self.parent.acknowledgementText = """
|
|
|
+ Developed within the medical physics research programme of the Slovenian research agency.
|
|
|
+ """ # replace with organization, grant and thanks.
|
|
|
+
|
|
|
+#
|
|
|
+# labkeySlicerPythonExtensionWidget
|
|
|
+#
|
|
|
+
|
|
|
+class iraemmBrowserWidget(ScriptedLoadableModuleWidget):
|
|
|
+ """Uses ScriptedLoadableModuleWidget base class, available at:
|
|
|
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
+ """
|
|
|
+
|
|
|
+ def setup(self):
|
|
|
+ print("Setting up iraemmBrowserWidget")
|
|
|
+ ScriptedLoadableModuleWidget.setup(self)
|
|
|
+ # Instantiate and connect widgets ...
|
|
|
+ self.network=slicerNetwork.labkeyURIHandler()
|
|
|
+
|
|
|
+ fconfig=os.path.join(os.path.expanduser('~'),'.labkey','onko-nix.json')
|
|
|
+ self.network.parseConfig(fconfig)
|
|
|
+ self.network.initRemote()
|
|
|
+ self.project="iPNUMMretro/Study"
|
|
|
+ self.dataset="Imaging1"
|
|
|
+
|
|
|
+
|
|
|
+ self.logic=iraemmBrowserLogic(self)
|
|
|
+
|
|
|
+
|
|
|
+ ds=self.network.filterDataset(self.project,self.dataset,[])
|
|
|
+ ids=[row['PatientId'] for row in ds['rows']]
|
|
|
+ ids=list(set(ids))
|
|
|
+
|
|
|
+
|
|
|
+ #
|
|
|
+ # Parameters Area
|
|
|
+ #
|
|
|
+ connectionCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
+ connectionCollapsibleButton.text = "Patients"
|
|
|
+ self.layout.addWidget(connectionCollapsibleButton)
|
|
|
+
|
|
|
+ connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
|
|
|
+
|
|
|
+
|
|
|
+ self.patientList=qt.QComboBox()
|
|
|
+ for id in ids:
|
|
|
+ self.patientList.addItem(id)
|
|
|
+ self.patientList.currentIndexChanged.connect(self.onPatientListChanged)
|
|
|
+ connectionFormLayout.addRow("Patient:",self.patientList)
|
|
|
+
|
|
|
+ self.visitList=qt.QComboBox()
|
|
|
+ self.visitList.currentIndexChanged.connect(self.onVisitListChanged)
|
|
|
+ connectionFormLayout.addRow("Visit:",self.visitList)
|
|
|
+
|
|
|
+ self.ctCode=qt.QLabel("ctCode")
|
|
|
+ connectionFormLayout.addRow("CT:",self.ctCode)
|
|
|
+
|
|
|
+ self.petCode=qt.QLabel("petCode")
|
|
|
+ connectionFormLayout.addRow("PET:",self.petCode)
|
|
|
+
|
|
|
+ self.patientLoad=qt.QPushButton("Load")
|
|
|
+ self.patientLoad.clicked.connect(self.onPatientLoadButtonClicked)
|
|
|
+ connectionFormLayout.addRow("Load patient",self.patientLoad)
|
|
|
+
|
|
|
+ self.keepCached=qt.QCheckBox("keep Cached")
|
|
|
+ self.keepCached.setChecked(1)
|
|
|
+ connectionFormLayout.addRow("Keep cached",self.keepCached)
|
|
|
+
|
|
|
+ #set to a defined state
|
|
|
+ self.onPatientListChanged(0)
|
|
|
+
|
|
|
+ def onPatientListChanged(self,i):
|
|
|
+ idFilter={'variable':'PatientId','value':self.patientList.currentText,'oper':'eq'}
|
|
|
+ ds=self.network.filterDataset(self.project,self.dataset, [idFilter])
|
|
|
+ seq=[int(row['SequenceNum']) for row in ds['rows']]
|
|
|
+ self.visitList.clear()
|
|
|
+
|
|
|
+ for s in seq:
|
|
|
+ self.visitList.addItem("Visit "+str(s))
|
|
|
+ self.onVisitListChanged(0)
|
|
|
+
|
|
|
+ def onVisitListChanged(self,i):
|
|
|
+ try:
|
|
|
+ s=self.visitList.currentText.split(' ')[1]
|
|
|
+ except IndexError:
|
|
|
+ return
|
|
|
+ print("Visit: Selected item: {}->{}".format(i,s))
|
|
|
+ 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']),\
|
|
|
+ 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'])
|
|
|
+
|
|
|
+
|
|
|
+ def onPatientLoadButtonClicked(self):
|
|
|
+ print("Load")
|
|
|
+ #delegate loading to logic
|
|
|
+ #try:
|
|
|
+ self.logic.loadImage(self.currentRow,self.keepCached.isChecked())
|
|
|
+ #except AttributeError:
|
|
|
+ # print("Missing current row")
|
|
|
+ # return
|
|
|
+
|
|
|
+ def cleanup(self):
|
|
|
+ pass
|
|
|
+
|
|
|
+#
|
|
|
+# irAEMMBrowserLogic
|
|
|
+#
|
|
|
+
|
|
|
+class iraemmBrowserLogic(ScriptedLoadableModuleLogic):
|
|
|
+ """This class should implement all the actual
|
|
|
+ computation done by your module. The interface
|
|
|
+ should be such that other python code can import
|
|
|
+ this class and make use of the functionality without
|
|
|
+ requiring an instance of the Widget.
|
|
|
+ Uses ScriptedLoadableModuleLogic base class, available at:
|
|
|
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
+ """
|
|
|
+ def __init__(self,parent=None):
|
|
|
+ ScriptedLoadableModuleLogic.__init__(self, parent)
|
|
|
+ if not parent==None:
|
|
|
+ #assume parent has the network set up
|
|
|
+ self.parent=parent
|
|
|
+ self.net=parent.network
|
|
|
+ self.project=parent.project
|
|
|
+
|
|
|
+ def setLabkeyInterface(self,net):
|
|
|
+ #additional way of setting the labkey network interface
|
|
|
+ #if no parent was provided in logic initialization (stand-alone mode)
|
|
|
+ self.net=net
|
|
|
+
|
|
|
+ def setLabkeyProject(self,project):
|
|
|
+ self.project=project
|
|
|
+
|
|
|
+ def loadImage(self,row,keepCached):
|
|
|
+
|
|
|
+
|
|
|
+ #fields={'ctResampled':True,'petResampled':False}
|
|
|
+ fields=['ctResampled','petResampled']
|
|
|
+
|
|
|
+ relativePaths={x:self.project+'/@files/preprocessedImages/'\
|
|
|
+ +row['patientCode']+'/'+row['visitCode']+'/'+row[x]\
|
|
|
+ for x in fields}
|
|
|
+
|
|
|
+ volumeNode={}
|
|
|
+ for f in relativePaths:
|
|
|
+ p=relativePaths[f]
|
|
|
+ labkeyPath=self.net.GetLabkeyPathFromRelativePath(p)
|
|
|
+ rp=self.net.head(labkeyPath)
|
|
|
+ if not rp.code==200:
|
|
|
+ print("Failed to get {}".format(labkeyPath))
|
|
|
+ continue
|
|
|
+
|
|
|
+ #pushes it to background
|
|
|
+ volumeNode[f]=self.net.loadNode(p,'VolumeFile',returnNode=True,keepCached=keepCached)
|
|
|
+
|
|
|
+ slicer.util.setSliceViewerLayers(background=volumeNode['ctResampled'],\
|
|
|
+ foreground=volumeNode['petResampled'],foregroundOpacity=0.5,fit=True)
|
|
|
+
|
|
|
+
|
|
|
+class irAEMMBrowserTest(ScriptedLoadableModuleTest):
|
|
|
+ """
|
|
|
+ This is the test case for your scripted module.
|
|
|
+ Uses ScriptedLoadableModuleTest base class, available at:
|
|
|
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
+ """
|
|
|
+
|
|
|
+ def setUp(self):
|
|
|
+ """ Do whatever is needed to reset the state - typically a scene clear will be enough.
|
|
|
+ """
|
|
|
+ slicer.mrmlScene.Clear(0)
|
|
|
+
|
|
|
+ def runTest(self):
|
|
|
+ """Run as few or as many tests as needed here.
|
|
|
+ """
|
|
|
+ self.setUp()
|
|
|
+ self.test_irAEMMBrowser()
|
|
|
+
|
|
|
+ def test_irAEMMBrowser(self):
|
|
|
+ """ Ideally you should have several levels of tests. At the lowest level
|
|
|
+ tests sould exercise the functionality of the logic with different inputs
|
|
|
+ (both valid and invalid). At higher levels your tests should emulate the
|
|
|
+ way the user would interact with your code and confirm that it still works
|
|
|
+ the way you intended.
|
|
|
+ One of the most important features of the tests is that it should alert other
|
|
|
+ developers when their changes will have an impact on the behavior of your
|
|
|
+ module. For example, if a developer removes a feature that you depend on,
|
|
|
+ your test should break so they know that the feature is needed.
|
|
|
+ """
|
|
|
+
|
|
|
+ self.delayDisplay("Starting the test")
|
|
|
+ #
|
|
|
+ # first, get some data
|
|
|
+ #
|
|
|
+
|