Ver código fonte

Merged to head

Andrej Studen @ Win7 6 anos atrás
pai
commit
50a39e4349

+ 25 - 0
README.md

@@ -0,0 +1,25 @@
+This package allows user to connect to a labkey server through a slicer interface.
+
+To use, add path to unpackaged labkeySlicerPythonAPI/labkeySlicerPythonExtension to
+Additional module paths in Slicer->Edit->Application Settings->Modules submenu.
+
+Then the package will appear under Examples.
+
+If you are interested in the code, you should look at slicerNetwork.py which 
+implements the communication with the server and forms HTTP Labkey API commands.
+
+You will need your key and certificate in a separate file to use the code.
+On linux, this is accomplished by:
+
+openssl pkcs12 -in yourP12File.p12 -nocerts -out privateKey.key
+
+openssl pkcs12 -in yourP12File.p12 -nokeys -out privateKey.crt
+
+The server certificate can be copied from:
+http://merlin.fmf.uni-lj.si/signing-ca-1.crt
+
+Enjoy!
+
+Andrej
+
+

+ 261 - 0
labkeySlicerPythonExtension/fileIO.py

@@ -0,0 +1,261 @@
+import slicerNetwork
+import slicer
+import qt
+import ctk
+import json
+import os
+
+
+class fileIO(slicer.ScriptedLoadableModule.ScriptedLoadableModule):
+    def __init__(self,parent):
+        slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self, parent)
+        self.className="fileIO"
+        self.parent.title="fileIO"
+
+        self.parent.categories = ["Examples"]
+        self.parent.dependencies = []
+        self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
+        self.parent.helpText = """
+                    File IO for Labkey interface to slicer
+                                """
+        self.parent.acknowledgementText = """
+            Developed within the medical physics research programme of the Slovenian research agency.
+                """ # replace with organization, grant and thanks.
+
+class remoteFileSelector(qt.QMainWindow):
+    def __init__(self, parent=None):
+        super(remoteFileSelector, self).__init__(parent)
+        self.setup()
+
+
+    def setMaster(self,master):
+        self.master=master
+
+    def setup(self):
+        centralWidget=qt.QWidget(self)
+        fileDialogFormLayout = qt.QFormLayout(centralWidget)
+        #self.layout.addWidget(fileDialogFormLayout)
+
+        #add item list for each found file/directory
+        self.fileList=qt.QListWidget()
+        self.fileList.toolTip="Select remote file"
+        self.fileList.itemDoubleClicked.connect(self.onFileListDoubleClicked)
+        self.currentRemoteDir=''
+
+        #add dummy entry
+        items=('.','..')
+        self.populateFileList(items)
+
+        fileDialogFormLayout.addWidget(self.fileList)
+
+        self.selectedPath=qt.QLineEdit("")
+        self.selectedPath.toolTip="Selected path"
+
+        fileDialogFormLayout.addRow("Selected path :",self.selectedPath)
+
+        self.closeButton=qt.QPushButton("Close")
+        self.closeButton.toolTip="Close"
+        self.closeButton.connect('clicked(bool)',self.onCloseButtonClicked)
+        fileDialogFormLayout.addRow(self.closeButton)
+        self.setCentralWidget(centralWidget);
+
+    def onFileListDoubleClicked(self,item):
+        if item == None:
+            print("Selected items: None")
+            return
+
+        iText=item.text()
+        print ("Selected items: {0} ").format(iText)
+
+
+        #this is hard -> compose path string from currentRemoteDir and selection
+        if item.text().find('..')==0:
+            #one up
+            idx=self.currentRemoteDir.rfind('/')
+            if idx<0:
+                self.currentRemoteDir=''
+            else:
+                self.currentRemoteDir=self.currentRemoteDir[:idx]
+        elif item.text().find('.')==0:
+            pass
+        else:
+            if len(self.currentRemoteDir)>0:
+                self.currentRemoteDir+='/'
+            self.currentRemoteDir+=item.text()
+
+        print ("Listing {0}").format(self.currentRemoteDir)
+        flist=self.master.network.toRelativePath(
+            self.master.network.listDir(self.currentRemoteDir))
+        print ("Got")
+        print (flist)
+        flist.insert(0,'..')
+        flist.insert(0,'.')
+        self.populateFileList(flist)
+        self.selectedPath.setText(self.currentRemoteDir)
+        self.master.remotePath.setText(self.master.network.GetLabkeyWebdavUrl()+"/"+self.currentRemoteDir)
+
+    def populateFileList(self,items):
+        self.fileList.clear()
+        for it_text in items:
+          item=qt.QListWidgetItem(self.fileList)
+          item.setText(it_text)
+          item.setIcon(qt.QIcon(it_text))
+
+    def onCloseButtonClicked(self):
+        self.hide()
+
+
+class fileIOWidget(slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget):
+    def __init__(self,parent):
+       slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget.__init__(self, parent)
+       self.selectRemote=remoteFileSelector()
+       #self.selectLocal=qt.QFileDialog()
+       #self.selectLocal.connect('fileSelected(QString)',self.onLocalFileSelected)
+       self.selectRemote.setMaster(self)
+       self.network=slicerNetwork.labkeyURIHandler()
+
+    def setup(self):
+        connectionCollapsibleButton = ctk.ctkCollapsibleButton()
+        connectionCollapsibleButton.text = "Connection"
+        self.layout.addWidget(connectionCollapsibleButton)
+
+        connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
+        self.loadConfigButton=qt.QPushButton("Load configuration")
+        self.loadConfigButton.toolTip="Load configuration"
+        self.loadConfigButton.connect('clicked(bool)',self.onLoadConfigButtonClicked)
+        connectionFormLayout.addRow("Connection:",self.loadConfigButton)
+
+        fileDialogCollapsibleButton = ctk.ctkCollapsibleButton()
+        fileDialogCollapsibleButton.text = "Paths"
+        self.layout.addWidget(fileDialogCollapsibleButton)
+
+        fileDialogFormLayout = qt.QFormLayout(fileDialogCollapsibleButton)
+
+        self.listRemoteButton=qt.QPushButton("List Remote")
+        self.listRemoteButton.toolTip="List Remote"
+        self.listRemoteButton.connect('clicked(bool)',self.onListRemoteButtonClicked)
+        fileDialogFormLayout.addRow(self.listRemoteButton)
+
+        self.listLocalButton=qt.QPushButton("List Local")
+        self.listLocalButton.toolTip="List Local"
+        self.listLocalButton.connect('clicked(bool)',self.onListLocalButtonClicked)
+        fileDialogFormLayout.addRow(self.listLocalButton)
+
+        #add selected file display
+        self.remotePath=qt.QLineEdit("")
+        self.remotePath.toolTip="Remote path"
+
+        fileDialogFormLayout.addRow("RemotePath :",self.remotePath)
+
+        self.localPath=qt.QLineEdit("")
+        self.localPath.toolTip="Local path"
+
+        fileDialogFormLayout.addRow("LocalPath :",self.localPath)
+
+        copyToRemoteButton=qt.QPushButton("Local->Remote")
+        copyToRemoteButton.toolTip="Local -> Remote"
+        copyToRemoteButton.connect('clicked(bool)',self.onCopyToRemoteButtonClicked)
+        fileDialogFormLayout.addRow(copyToRemoteButton)
+
+        removeRemoteButton=qt.QPushButton("Remove remote")
+        removeRemoteButton.toolTip="Remove Remote"
+        removeRemoteButton.connect('clicked(bool)',self.onRemoveRemoteButtonClicked)
+        fileDialogFormLayout.addRow(removeRemoteButton)
+
+        mkdirRemoteButton=qt.QPushButton("Mkdir remote")
+        mkdirRemoteButton.toolTip="Mkdir Remote"
+        mkdirRemoteButton.connect('clicked(bool)',self.onMkdirRemoteButtonClicked)
+        fileDialogFormLayout.addRow(mkdirRemoteButton)
+
+    def onListLocalButtonClicked(self):
+        self.localPath.setText(qt.QFileDialog.getOpenFileName(None,"Select local"))
+
+    def onListRemoteButtonClicked(self):
+        self.selectRemote.show()
+
+    def onLoadConfigButtonClicked(self):
+
+        filename=qt.QFileDialog.getOpenFileName(None,'Open configuration file (JSON)',
+            '.', '*.json')
+
+        with open(filename,'r') as f:
+           dt=json.load(f)
+
+        if dt.has_key('host'):
+           self.network.hostname=dt['host']
+
+        if dt.has_key('SSL'):
+           self.network.configureSSL(dt['SSL']['user'],dt['SSL']['key'],
+                dt['SSL']['keyPwd'],dt['SSL']['ca'])
+
+        if dt.has_key('labkey'):
+           if dt['labkey'].has_key('user'):
+               self.network.auth_name=dt['labkey']['user']
+           if dt['labkey'].has_key('password'):
+               self.network.auth_pass=dt['labkey']['password']
+
+        self.loadConfigButton.setText(os.path.basename(filename))
+        self.network.initRemote()
+
+    def copyLocalToRemote(self,localPath,remotePath):
+        if os.path.isfile(localPath):
+            #end recursion
+            print ("Copy {} to {}").format(localPath,remotePath)
+            self.network.copyFileToRemote(localPath,remotePath)
+            return
+
+        dirName=os.path.basename(os.path.normpath(localPath))
+        remotePath+='/'+dirName
+        if not self.network.isDir(remotePath):
+            self.network.mkdir(remotePath+'/')
+        for f in os.listdir(localPath):
+            #enter recursion
+            localfile=os.path.join(localPath,f)
+            self.copyLocalToRemote(localfile,remotePath)
+
+    def onCopyToRemoteButtonClicked(self):
+        self.copyLocalToRemote(self.localPath.text,self.remotePath.text)
+
+    def onRemoveRemoteButtonClicked(self):
+        remotePath=self.remotePath.text
+        if self.network.isDir(remotePath):
+            resp=qt.QMessageBox.question(None,'Delete directory',
+                'Do you want to delete directory {}'.format(remotePath))
+            if resp == qt.QMessageBox.No:
+                return
+        self.network.rmdir(remotePath)
+
+    def onMkdirRemoteButtonClicked(self):
+        remotePath=self.remotePath.text
+        self.network.mkdir(remotePath)
+
+
+class fileIOLogic(slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic):
+    def __init__(self,parent):
+       slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic.__init__(self, parent)
+
+
+class fileIOTest(slicer.ScriptedLoadableModule.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_fileIO()
+
+  def test_fileIO(self):
+    """ Ideally you should have several levels of tests.  At the lowest level
+    tests should exercise the functionality of the logic with different inputs
+    (both valid andclass dataExplorerTest(ScriptedLoadableModuleTest):
+  """
+    pass

+ 95 - 34
labkeySlicerPythonExtension/labkeySlicerPythonExtension.py

@@ -3,6 +3,8 @@ import unittest
 from __main__ import vtk, qt, ctk, slicer
 from slicer.ScriptedLoadableModule import *
 import slicerNetwork
+import loadDicom
+import json
 
 #
 # labkeySlicerPythonExtension
@@ -48,10 +50,13 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
     self.layout.addWidget(connectionCollapsibleButton)
 
     connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
+    self.configDir=os.getenv("HOME")+os.sep+".labkey";
 
     self.serverURL=qt.QLineEdit("https://merlin.fmf.uni-lj.si")
-
+    self.serverURL.textChanged.connect(self.updateServerURL)
     connectionFormLayout.addRow("Server: ", self.serverURL)
+    #copy initial setting
+    self.updateServerURL(self.serverURL.text);
 
     try:
       self.startDir=os.path.join(os.environ['HOME'],"temp/crt")
@@ -78,12 +83,25 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
 
     connectionFormLayout.addRow("CA certificate:",self.caCertButton)
 
-    self.auth_pwd=''
-    self.connectButton=qt.QPushButton("Connect")
-    self.connectButton.toolTip="Connect to the server"
-    self.connectButton.connect('clicked(bool)',self.onConnectButtonClicked)
 
-    connectionFormLayout.addRow("Connection:",self.connectButton)
+    self.loadConfigButton=qt.QPushButton("Load configuration")
+    self.loadConfigButton.toolTip="Load configuration"
+    self.loadConfigButton.connect('clicked(bool)',self.onLoadConfigButtonClicked)
+    connectionFormLayout.addRow("Connection:",self.loadConfigButton)
+
+    self.initButton=qt.QPushButton("Init")
+    self.initButton.toolTip="Initialize connection to the server"
+    self.initButton.connect('clicked(bool)',self.onInitButtonClicked)
+    connectionFormLayout.addRow("Connection:",self.initButton)
+
+    self.authName=qt.QLineEdit("email")
+    self.authName.textChanged.connect(self.updateAuthName)
+    connectionFormLayout.addRow("Labkey username: ", self.authName)
+
+    self.authPass=qt.QLineEdit("")
+    self.authPass.setEchoMode(qt.QLineEdit.Password)
+    self.authPass.textChanged.connect(self.updateAuthPass)
+    connectionFormLayout.addRow("Labkey password: ", self.authPass)
 
     fileDialogCollapsibleButton = ctk.ctkCollapsibleButton()
     fileDialogCollapsibleButton.text = "Remote files"
@@ -93,7 +111,7 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
     fileDialogFormLayout = qt.QFormLayout(fileDialogCollapsibleButton)
 
     #add item list for each found file/directory
-    self.fileList=qt.QListWidget(parent=self)
+    self.fileList=qt.QListWidget()
     self.fileList.toolTip="Select remote file"
     self.fileList.itemDoubleClicked.connect(self.onFileListDoubleClicked)
     self.currentRemoteDir=''
@@ -105,7 +123,7 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
     fileDialogFormLayout.addWidget(self.fileList)
 
     #add selected file display
-    self.selectedFile=qt.QLineEdit(parent=self)
+    self.selectedFile=qt.QLineEdit("")
     self.selectedFile.toolTip="Selected file"
 
     fileDialogFormLayout.addRow("Selected file :",self.selectedFile)
@@ -166,33 +184,34 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
      #pwd=qt.QInputDialog.getText(None,'Certificate password',
      # 'Enter certificate password',qt.QLineEdit.Password)
      if not(filename) :
-         print "No file selected"
+         print("No file selected")
          return
 
      f=qt.QFile(filename)
      if not (f.open(qt.QIODevice.ReadOnly)) :
-         print "Could not open file"
+         print("Could not open file")
          return
 
      certList=qt.QSslCertificate.fromPath(filename)
      if len(certList) < 1:
-         print "Troubles parsing {0}".format(filename)
+         print ("Troubles parsing {0}").format(filename)
          return
 
      self.logic.cert=qt.QSslCertificate(f)
-     print "cert.isNull()={0}".format(self.logic.cert.isNull())
+     print("cert.isNull()={0}").format(self.logic.cert.isNull())
      self.userCertButton.setText(filename)
+     self.authName.setText(self.logic.cert.subjectInfo("emailAddress"))
 
   def onPrivateKeyButtonClicked(self):
       filename=qt.QFileDialog.getOpenFileName(None,'Open private key',
             self.startDir, '*.key')
       if not (filename) :
-          print "No file selected"
+          print ("No file selected")
           return
 
       f=qt.QFile(filename)
       if not (f.open(qt.QIODevice.ReadOnly)) :
-          print "Could not open file"
+          print ("Could not open file")
           return
       self.pwd=qt.QInputDialog.getText(None,'Private key password',
         'Enter key password',qt.QLineEdit.Password)
@@ -205,24 +224,55 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
       filename=qt.QFileDialog.getOpenFileName(None,'Open authority certificate',
                self.startDir, '*.crt')
       if not(filename) :
-         print "No file selected"
+         print("No file selected")
          return
 
       f=qt.QFile(filename)
 
       if not (f.open(qt.QIODevice.ReadOnly)) :
-          print "Could not open file"
+          print("Could not open file")
           return
 
       certList=qt.QSslCertificate.fromPath(filename)
 
       if len(certList) < 1:
-          print "Troubles parsing {0}".format(filename)
+          print("Troubles parsing {0}").format(filename)
           return
       self.logic.caCert=qt.QSslCertificate(f)#certList[0]
       self.caCertButton.setText(filename)
 
-  def onConnectButtonClicked(self):
+  def onLoadConfigButtonClicked(self):
+       filename=qt.QFileDialog.getOpenFileName(None,'Open configuration file (JSON)',
+            self.configDir, '*.json')
+       with open(filename,'r') as f:
+           dt=json.load(f)
+       if dt.has_key('host'):
+           self.serverURL.setText(dt['host'])
+       if dt.has_key('dataset'):
+           pass
+           #self.datasetName.setText(dt['dataset'])
+       if dt.has_key('project'):
+           pass
+           #self.datasetProject.setText(dt['project'])
+       if dt.has_key('SSL'):
+           if dt['SSL'].has_key('user'):
+               self.userCertButton.setText(dt['SSL']['user'])
+           if dt['SSL'].has_key('key'):
+               self.privateKeyButton.setText(dt['SSL']['key'])
+           if dt['SSL'].has_key('keyPwd'):
+               self.pwd=dt['SSL']['keyPwd']
+           if dt['SSL'].has_key('ca'):
+               self.caCertButton.setText(dt['SSL']['ca'])
+       if dt.has_key('labkey'):
+           if dt['labkey'].has_key('user'):
+               self.network.auth_name=dt['labkey']['user']
+               self.authName.setText(self.network.auth_name)
+           if dt['labkey'].has_key('password'):
+               self.network.auth_pass=dt['labkey']['password']
+               self.authPass.setText(self.network.auth_pass)
+       self.loadConfigButton.setText(os.path.basename(filename))
+
+  def onInitButtonClicked(self):
       self.network.configureSSL(
         self.userCertButton.text,
         self.privateKeyButton.text,
@@ -230,22 +280,27 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
         self.caCertButton.text
       )
 
-      uname=str(self.logic.cert.subjectInfo("emailAddress"))
-      uname=qt.QInputDialog.getText(None,
-        "Labkey credentials","Enter username",qt.QLineEdit.Normal,uname)
+      self.network.initRemote()
 
-      self.auth_pwd=qt.QInputDialog.getText(None,
-        "Labkey credentials","Enter password",qt.QLineEdit.Password,self.auth_pwd)
+  def updateAuthName(self,txt):
+      self.network.auth_name=txt
+      print("Setting username to {0}").format(self.network.auth_name);
 
-      self.network.connectRemote(str(self.serverURL.text),uname,self.auth_pwd)
+  def updateAuthPass(self,txt):
+      self.network.auth_pass=txt
+      print("Setting password.")
+
+  def updateServerURL(self,txt):
+      self.network.hostname=txt
+      print("Setting hostname to {0}").format(self.network.hostname);
 
   def onFileListDoubleClicked(self,item):
         if item == None:
-            print "Selected items: None"
+            print("Selected items: None")
             return
 
         iText=item.text()
-        print "Selected items: {0} ".format(iText)
+        print("Selected items: {0} ").format(iText)
 
 
         #this is hard -> compose path string from currentRemoteDir and selection
@@ -262,11 +317,11 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
             if len(self.currentRemoteDir)>0:
                 self.currentRemoteDir+='/'
             self.currentRemoteDir+=item.text()
-        print "Listing {0}".format(self.currentRemoteDir)
+        print("Listing {0}").format(self.currentRemoteDir)
         flist=self.network.toRelativePath(
             self.network.listDir(self.currentRemoteDir))
-        print "Got"
-        print flist
+        print("Got")
+        print(flist)
         flist.insert(0,'..')
         flist.insert(0,'.')
         self.populateFileList(flist)
@@ -274,14 +329,20 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
 
   def onLoadFileButtonClicked(self):
       properties={}
-      properties['keepCachedFile']=self.keepCachedFileCheckBox.isChecked()
-      self.network.loadNodeFromFile(self.selectedFile.text,
-            self.fileTypeSelector.currentText, properties)
+      localPath=self.network.GetFile(self.selectedFile.text)
+      slicer.util.loadNodeFromFile(localPath,self.fileTypeSelector.currentText,
+        properties,returnNode=false)
+      if not self.keepCachedFileCheckBox.isChecked():
+              os.remove(localPath)
 
   def onLoadDirButtonClicked(self):
-    self.network.loadDir(self.selectedFile.text)
+    localDir=self.network.loadDir(self.selectedFile.text)
+    try:
+        self.loadDicomLogic.load(localDir)
+    except:
+        self.loadDicom=loadDicom.loadDicomLogic(self)
+        self.loadDicom.load(localDir)
 
-#
 # labkeySlicerPythonExtensionLogic
 #
 

+ 100 - 0
labkeySlicerPythonExtension/loadDicom.py

@@ -0,0 +1,100 @@
+import slicer
+import os
+import subprocess
+import re
+
+dicomModify=os.getenv("HOME")
+if not dicomModify==None:
+    dicomModify+="/software/install/"
+    dicomModify+="dicomModify/bin/dicomModify"
+
+class loadDicom(slicer.ScriptedLoadableModule.ScriptedLoadableModule):
+    def __init__(self,parent):
+        slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self, parent)
+        self.className="loadDicom"
+        self.parent.title="loadDicom"
+        self.parent.categories = ["Examples"]
+        self.parent.dependencies = []
+        self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
+        self.parent.helpText = """
+            utilities for parsing dicom entries
+            """
+        self.parent.acknowledgementText = """
+            Developed within the medical physics research programme of the Slovenian research agency.
+            """ # replace with organization, grant and thanks.
+
+class loadDicomWidget(slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget):
+  """Uses ScriptedLoadableModuleWidget base class, available at:
+  https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
+  """
+
+  def setup(self):
+    slicer.ScriptedLoadableModule.ScriptedLoadableModuleWidget.setup(self)
+
+
+class loadDicomLogic(slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic):
+    def __init__(self,parent):
+      slicer.ScriptedLoadableModule.ScriptedLoadableModuleLogic.__init__(self, parent)
+
+    def load(self,sNet,dir,doRemove=True):
+        dicomFiles=sNet.listDir(dir)
+        #filelist=[os.path.join(dir,f) for f in os.listdir(dir)]
+        filelist=[]
+        for f in dicomFiles:
+                localPath=sNet.GetFile(f)
+                f0=localPath
+                f1=f0+"1"
+                subprocess.call(dicomModify+" "+f0+" "+f1+" && mv "+f1+" "+f0+";", shell=True)
+                filelist.append(localPath)
+
+        try:
+            loadables=self.volumePlugin.examineForImport([filelist])
+        except AttributeError:
+            self.volumePlugin=slicer.modules.dicomPlugins['DICOMScalarVolumePlugin']()
+            loadables=self.volumePlugin.examineForImport([filelist])
+
+
+        for loadable in loadables:
+            #TODO check if it makes sense to load a particular loadable
+            if loadable.name.find('imageOrientationPatient')>-1:
+                continue
+            volumeNode=self.volumePlugin.load(loadable)
+            if volumeNode != None:
+                vName='Series'+dicomValue(loadable.files[0],"0020,0011")
+                volumeNode.SetName(vName)
+
+        try:
+            loadableRTs=self.RTPlugin.examineForImport([filelist])
+        except:
+            self.RTPlugin=plugin=slicer.modules.dicomPlugins['DicomRtImportExportPlugin']()
+            loadableRTs=self.RTPlugin.examineForImport([filelist])
+
+        for loadable in loadableRTs:
+            segmentationNode=self.RTPlugin.load(loadable)
+            #if segmentationNode!= None:
+            #    segmentationNode.SetName('SegmentationBR')
+
+        if not doRemove:
+            return
+
+        for f in filelist:
+            os.remove(f)
+
+def dicomValue(file,tag):
+    dcmdump=os.path.join(os.environ['SLICER_HOME'],"bin")
+    dcmdump=os.path.join(dcmdump,"dcmdump")
+    try:
+        out=subprocess.check_output([dcmdump,'+P',tag,file])
+        out=re.sub(r'^.*\[(.*)\].*\n$',r'\1',out)
+        return out
+    except:
+        return None
+
+def clearNodes():
+    nodes=[]
+    nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeNode"))
+    nodes.extend(slicer.util.getNodesByClass("vtkMRMLScalarVolumeDisplayNode"))
+    nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationNode"))
+    nodes.extend(slicer.util.getNodesByClass("vtkMRMLSegmentationDisplayNode"))
+    for node in nodes:
+        slicer.mrmlScene.RemoveNode(node)

+ 360 - 86
labkeySlicerPythonExtension/slicerNetwork.py

@@ -9,11 +9,29 @@ import slicer
 import shutil
 import distutils
 import os
+import base64
+import json
 
-class slicerNetwork:
-    def __init__(self,parent):
-        parent.title = "slicerNetwork"
-        self.parent=parent
+#see https://gist.github.com/logic/2715756, allow requests to do Put
+class MethodRequest(urllib2.Request):
+    def __init__(self, *args, **kwargs):
+        if 'method' in kwargs:
+            self._method = kwargs['method']
+            del kwargs['method']
+        else:
+            self._method = None
+        return urllib2.Request.__init__(self, *args, **kwargs)
+
+    def get_method(self, *args, **kwargs):
+        if self._method is not None:
+            return self._method
+        return urllib2.Request.get_method(self, *args, **kwargs)
+
+class slicerNetwork(slicer.ScriptedLoadableModule.ScriptedLoadableModule):
+    def __init__(self, parent):
+        slicer.ScriptedLoadableModule.ScriptedLoadableModule.__init__(self,parent)
+        self.parent.title="slicerNetwork"
+        pass
 
 class labkeyURIHandler(slicer.vtkURIHandler):
     def __init__(self):
@@ -21,15 +39,20 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         self.className="labkeyURIHandler"
         slicer.mrmlScene.AddURIHandler(self)
         try:
-	   self.localCacheDirectory=os.path.join(
-	     os.environ["HOME"],"labkeyCache")
-	except:
-	   #in windows, the variable is called HOMEPATH
-	   fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
-	   self.localCacheDirectory=os.path.join(fhome,"labkeyCache")
+            fhome=os.environ["HOME"]
+        except:
+	           #in windows, the variable is called HOMEPATH
+            fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
+
+        self.localCacheDirectory=os.path.join(fhome,"labkeyCache")
+        self.configDir=os.path.join(fhome,".labkey")
+        self.mode="http"
+        #try initializing network from default config file, if found
+        self.initFromConfig()
+
 
     def CanHandleURI(self,uri):
-        print "labkeyURIHandler::CanHandleURI({0})".format(uri)
+        print("labkeyURIHandler::CanHandleURI({0})").format(uri)
         if uri.find('labkey://')==0:
             return 1
         return 0
@@ -49,13 +72,47 @@ class labkeyURIHandler(slicer.vtkURIHandler):
     def GetLocalCacheDirectory(self):
         return self.localCacheDirectory
 
+    def GetLabkeyUrl(self):
+        return self.hostname+"/labkey"
+
+    def GetLabkeyWebdavUrl(self):
+        return self.GetLabkeyUrl()+"/_webdav"
+
     def GetLocalPath(self,source):
+        debug=False
         relativePath=re.sub('labkey://','',source)
-	sp=os.sep.encode('string-escape')
-        print "Substituting / with {0} in {1}".format(sp,relativePath)
-	relativePath=re.sub('/',sp,relativePath)
+        sp=os.sep.encode('string-escape')
+        if debug:
+            print("Substituting / with {0} in {1}").format(sp,relativePath)
+        relativePath=re.sub('/',sp,relativePath)
         return os.path.join(self.localCacheDirectory,relativePath)
 
+    def GetRemotePath(self,source):
+        return self.GetLabkeyWebdavUrl()+"/"+GetLabkeyPathFromLocalPath(source)
+
+    def GetLabkeyPathFromLocalPath(self,f):
+        #report it with URL separator, forward-slash
+        if f.find(self.localCacheDirectory)<-1:
+            print("Localpath misformation. Exiting")
+            return "NULL"
+        dest=re.sub(self.localCacheDirectory,'',f)
+        #leaves /ContextPath/%40files/subdirectory_list/files
+        #remove first separator
+        sp=os.sep.encode('string-escape')
+        if dest[0]==sp:
+            dest=dest[1:len(dest)]
+        return re.sub(sp,'/',dest)
+
+    def GetLabkeyPathFromRemotePath(self,f):
+        #used to query labkey stuff, so URL separator is used
+        f=re.sub('labkey://','',f)
+        f=re.sub(self.GetLabkeyWebdavUrl(),'',f)
+
+        if f[0]=='/':
+            f=f[1:len(f)]
+        return f;
+
+
     def GetFile(self,source):
         # check for file in cache. If available, use, if not, download
         localPath=self.GetLocalPath(source)
@@ -65,21 +122,21 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         return localPath
 
     def StageFileRead(self,source,dest):
-        print "labkeyURIHandler::StageFileRead({0},{1})".format(
-            source,dest)
+        debug=False
+        if debug:
+            print("labkeyURIHandler::StageFileRead({0},{1})").format(source,dest)
         labkeyPath=re.sub('labkey://','',source)
         remote=self.readFile(self.hostname,labkeyPath)
         #make all necessary directories
         path=os.path.dirname(dest)
-        try:
+        if not os.path.isdir(path):
             os.makedirs(path)
-        except:
-            if not os.path.isdir(path):
-                raise
+
         local=open(dest,'wb')
         #make sure we are at the begining of the file
 
 	    #check file size
+<<<<<<< HEAD
 	    remote.seek(0,2)
 	    sz=remote.tell()
 	    print "Remote size: {0}".format(sz)
@@ -87,70 +144,114 @@ class labkeyURIHandler(slicer.vtkURIHandler):
 	    remote.seek(0)
         shutil.copyfileobj(remote,local)
 	    print "Local size: {0}".format(local.tell())
+=======
+        if debug:
+	         remote.seek(0,2)
+	         sz=remote.tell()
+	         print("Remote size: {0}").format(sz)
+
+        remote.seek(0)
+        shutil.copyfileobj(remote,local)
+        if debug:
+	         print("Local size: {0}").format(local.tell())
+>>>>>>> 7212758e9e58c9afa6f1d2a0b25eaef3d0a6ad64
         local.close()
 
     def StageFileWrite(self,source,dest):
-        print "labkeyURIHandler::StageFileWrite({0},{1}) not implemented yet".format(
+        print("labkeyURIHandler::StageFileWrite({0},{1}) not implemented yet").format(
             source,dest)
 
     def fileTypesAvailable(self):
         return ('VolumeFile','SegmentationFile','TransformFile')
 
     #mimic slicer.util.loadNodeFromFile
-    def loadNodeFromFile(self, filename, filetype, properties={}, returnNode=False):
-        #this is the only relevant part - file must be downloaded to cache
-        localPath=self.GetFile(filename)
-        slicer.util.loadNodeFromFile(localPath,filetype,properties,returnNode)
-        #remove retrieved file
-        try:
-            if not(properties['keepCachedFile']) :
-                os.remove(localPath)
-        except:
-            pass
-
-
-    def loadVolume(self,filename, properties={}, returnNode=False):
-        filetype = 'VolumeFile'
-        #redirect to self.loadNodeFromFile first to get the cached file
-        return self.loadNodeFromFile(filename,filetype, properties,returnNode)
-
-    def loadSegmentation(self,filename,properties={},returnNode=False):
-        filetype='SegmentationFile'
-        #redirect to self.loadNodeFromFile first to get the cached file
-        return self.loadNodeFromFile(filename,filetype, properties,returnNode)
-    #add others if needed
-
+    # def loadNodeFromFile(self, filename, filetype, properties={}, returnNode=False):
+    #     #this is the only relevant part - file must be downloaded to cache
+    #     localPath=self.GetFile(filename)
+    #     slicer.util.loadNodeFromFile(localPath,filetype,properties,returnNode)
+    #     #remove retrieved file
+    #     try:
+    #         if not(properties['keepCachedFile']) :
+    #             os.remove(localPath)
+    #     except:
+    #         pass
+
+
+    # def loadVolume(self,filename, properties={}, returnNode=False):
+    #     filetype = 'VolumeFile'
+    #     #redirect to self.loadNodeFromFile first to get the cached file
+    #     return self.loadNodeFromFile(filename,filetype, properties,returnNode)
+    #
+    # def loadSegmentation(self,filename,properties={},returnNode=False):
+    #     filetype='SegmentationFile'
+    #     #redirect to self.loadNodeFromFile first to get the cached file
+    #     return self.loadNodeFromFile(filename,filetype, properties,returnNode)
+    # #add others if needed
+
+    ## setup & initialization routines
 
     def configureSSL(self,cert,key,pwd,cacert):
         #do this first
-        self.ctx=ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-        self.ctx.load_cert_chain(cert,key,pwd)
-        self.ctx.verify_mode=ssl.CERT_REQUIRED
-        self.ctx.load_verify_locations(cacert)
-
-    def connectRemote(self,serverUrl,uname,pwd):
-        https_handler=urllib2.HTTPSHandler(context=self.ctx)
-        self.SetHostName(serverUrl)
+        try:
+            self.ctx=ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+            self.ctx.load_cert_chain(cert,key,pwd)
+            self.ctx.verify_mode=ssl.CERT_REQUIRED
+            self.ctx.load_verify_locations(cacert)
+        except ssl.SSLError as err:
+            print(" Failed to configure SSL: {0}").format(str(err))
+        self.mode="https"
+
+
+    def initRemote(self):
+        if self.mode=="https":
+            http_handler=urllib2.HTTPSHandler(context=self.ctx)
+        if self.mode=="http":
+            http_handler=urllib2.HTTPHandler()
         #cookie part
         cj=cookielib.CookieJar()
         cookie_handler=urllib2.HTTPCookieProcessor(cj)
+        self.opener=urllib2.build_opener(http_handler,cookie_handler)
+
+    def initFromConfig(self):
+        path=os.path.join(self.configDir,"Remote.json")
+        try:
+            self.parseConfig(path)
+        except OSError:
+            return
+        self.initRemote()
+
+    def parseConfig(self,fname):
+        try:
+            f=open(fname)
+        except OSError as e:
+            print("Confgiuration error: OS error({0}): {1}").format(e.errno, e.strerror)
+            raise
+
+        dt=json.load(f)
+        if dt.has_key('SSL'):
+            self.configureSSL(
+                dt['SSL']['user'],
+                dt['SSL']['key'],
+                dt['SSL']['keyPwd'],
+                dt['SSL']['ca']
+                )
+        self.hostname=dt['host']
+        self.auth_name=dt['labkey']['user']
+        self.auth_pass=dt['labkey']['password']
 
-        self.opener=urllib2.build_opener(https_handler,cookie_handler)
-        loginUrl=serverUrl+"/labkey/login/login.post"
-        r=urllib2.Request(str(loginUrl))
-        print "Connecting to {0}".format(loginUrl)
-        r.add_header('ContentType','application/x-www-form-urlencoded')
-        data="email="+uname+"&password="+pwd
-        r.add_data(data)
 
-        self.opener.open(r)
 
-        #cj in opener contains the cookies
+    #cj in opener contains the cookies
 
     def get(self,url):
 
-        #r1=urllib2.Request('https://merlin.fmf.uni-lj.si/labkey/query/motionData/selectRows.api?schemaName=core&query.queryName=Users')
+        debug=False
+        if debug:
+            print("GET: {0}").format(url)
+            print("as {0}").format(self.auth_name)
         r=urllib2.Request(url)
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
         return self.opener.open(r)
         #f contains json as a return value
 
@@ -159,15 +260,129 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         r=urllib2.Request(url)
         #makes it a post
         r.add_data(data)
-        r.add_header("content-type","application/json")
+        r.add_header("Content-Type","application/json")
+        #add csrf
+        csrf=self.getCSRF()
+        r.add_header("X-LABKEY-CSRF",csrf)
+
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+        print("{}: {}").format(r.get_method(),r.get_full_url())
+        print("data: {}").format(r.get_data())
+        print("Content-Type: {}").format(r.get_header('Content-Type'))
+        try:
+            return self.opener.open(r)
+        except urllib2.HTTPError as e:
+            print e.code
+            print e.read()
+            return e
+        #f contains json as a return value
 
-        f=self.opener.open(r)
+    def put(self,url,data):
+
+        print("PUT: {0}").format(url)
+        r=MethodRequest(url.encode('utf-8'),method="PUT")
+        #makes it a post
+        r.add_data(data)
+        r.add_header("content-type","application/octet-stream")
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+
+        return self.opener.open(r)
         #f contains json as a return value
 
+    def getCSRF(self):
+        url=self.GetLabkeyUrl()+'/login/whoAmI.view'
+        jsonData=json.load(self.get(url))
+        return jsonData["CSRF"]
+
+
+    def remoteDirExists(self,url):
+        status,dirs=self.listRemoteDir(url);
+        return status
+
+    def mkdir(self,remoteDir):
+        if self.remoteDirExists(remoteDir):
+            return False
+        r=MethodRequest(remoteDir,method="MKCOL")
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+        try:
+            f=self.opener.open(r)
+        except:
+            print("Error: Failed MKCOL {}").format(remoteDir)
+            return False
+        return True
+
+    def mkdirs(self,remoteDir):
+        labkeyPath=self.GetLabkeyPathFromRemotePath(remoteDir)
+        s=0
+        while True:
+            s1=labkeyPath.find('/',s)
+            if s1<0:
+                break
+            path=labkeyPath[0:s1]
+            remotePath=self.GetLabkeyWebdavUrl()+'/'+path
+            dirExists=self.remoteDirExists(remotePath)
+            if not dirExists:
+                if not self.mkdir(remotePath):
+                    return False
+            s=s1+1
+        return self.mkdir(remoteDir)
+
+    def rmdir(self,remoteDir):
+        if not self.remoteDirExists(remoteDir):
+            return True
+        r=MethodRequest(remoteDir,method="DELETE")
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+        try:
+            f=self.opener.open(r)
+        except:
+            print("Error: Failed DELETE {}").format(remoteDir)
+            return False
+        return True
+
     def listDir(self,dir):
-        print "Listing for {0}".format(dir)
-        dirUrl=self.hostname+"/labkey/_webdav"+"/"+dir
-        r=propfindRequest(dirUrl)
+        print("Listing for {0}").format(dir)
+        dirUrl=self.GetLabkeyWebdavUrl()+"/"+dir
+        status,dirs=self.listRemoteDir(dirUrl)
+        return dirs
+
+    def isDir(self, remotePath):
+        #print "isDir: {}".format(remotePath)
+        r=MethodRequest(remotePath,method="PROPFIND")
+        PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
+            <a:propfind xmlns:a="DAV:">\n
+            <a:prop>\n
+            <a:resourcetype/>\n
+            </a:prop>\n
+            </a:propfind>"""
+        r.add_header('content-type','text/xml; charset="utf-8"')
+        r.add_header('content-length',str(len(PROPFIND)))
+        r.add_header('Depth','0')
+        r.add_data(PROPFIND)
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+        print("PROPFIND: {0}").format(remotePath)
+        try:
+            f=self.opener.open(r)
+        except:
+            return False
+        tree=ET.XML(f.read())
+        try:
+            rps=tree.find('{DAV:}response').find('{DAV:}propstat')
+            rps=rps.find('{DAV:}prop')
+            rps=rps.find('{DAV:}resourcetype').find('{DAV:}collection')
+            if rps != None:
+                return True
+            else:
+                return False
+        except:
+            return False
+
+    def listRemoteDir(self,dirUrl):
+        r=MethodRequest(dirUrl,method="PROPFIND")
         PROPFIND=u"""<?xml version="1.0" encoding="utf-8"?>\n
                     <propfind xmlns="DAV:">\n
                     <prop>\n
@@ -178,17 +393,23 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         r.add_header('content-length',str(len(PROPFIND)))
         r.add_header('Depth','1')
         r.add_data(PROPFIND)
-        f=self.opener.open(r)
+        base64string = base64.b64encode('%s:%s' % (self.auth_name, self.auth_pass))
+        r.add_header("Authorization", "Basic %s" % base64string)
+        print("PROPFIND: {0}").format(dirUrl)
+        dirs=[]
+        try:
+            f=self.opener.open(r)
+        except:
+            return False,dirs
         tree=ET.XML(f.read())
         rps=tree.findall('{DAV:}response')
-        dirs=[]
         for r in rps:
             hr=r.find('{DAV:}href')
             dirent=hr.text
             dirent=re.sub('/labkey/_webdav/','',dirent)
             dirs.append(dirent)
         del dirs[0]
-        return dirs
+        return True,dirs
 
     def toRelativePath(self,dirs):
         flist1=[]
@@ -205,27 +426,80 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         f=self.get(dirUrl)
         return StringIO.StringIO(f.read())
 
+    def uploadFile(self,localPath):
+        #get upstream directories sorted out
+        labkeyPath=self.GetLabkeyPathFromLocalPath(localPath)
+        if labkeyPath=="NULL":
+            errorCode="Failed to upload {}. Potential incorrect location"
+            errorCode+=". Should be in labkeyCache!"
+            print(errorCode.format(localPath))
+            return False
+        labkeyDir=labkeyPath[0:labkeyPath.rfind('/')]
+        remoteDir=self.GetLabkeyWebdavUrl()+'/'+labkeyDir
+        if not self.remoteDirExists(remoteDir):
+            if not self.mkdirs(remoteDir):
+                errorCode="UploadFile: Could not create directory {}"
+                print(errorCode.format(remoteDir))
+                return False
+
+        #make an URL request
+        with open(localPath, 'r') as f:
+            data=f.read()
+        remotePath=self.GetLabkeyWebdavUrl()+'/'+labkeyPath
+        self.put(remotePath,data)
+
+    def copyFileToRemote(self,localPath, remotePath):
+        #get upstream directories sorted out
+
+        labkeyDir=os.path.dirname(remotePath)
+        if not self.remoteDirExists(labkeyDir):
+            if not self.mkdirs(labkeyDir):
+                errorCode="UploadFile: Could not create directory {}"
+                print(errorCode.format(labkeyDir))
+                return False
+
+        #make an URL request
+        if (self.isDir(remotePath)):
+            remotePath=remotePath+'/'+os.path.basename(localPath)
+
+        with open(localPath, 'r') as f:
+            data=f.read()
+        self.put(remotePath,data)
+
     def loadDir(self, path):
         #dirURL=serverUrl+"/labkey/_webdav/"+path
         files=self.listDir(path)
+        fdir="NONE"
         for f in files:
             #returns local path
             try:
-                self.GetFile(f)
+                fdir=os.path.dirname(self.GetFile(f))
             except:
                 #fails if there is a subdirectory; go recursively
-                self.readDir(f)
-
-
-class propfindRequest(urllib2.Request):
-    """
-    This request subclass allows explicit specification of
-    the HTTP request method. Basic urllib2.Request class
-    chooses GET or POST depending on self.has_data()
-    """
-    def __init__(self, *args, **kwargs):
-        #self.method = 'Propfind'
-        urllib2.Request.__init__(self, *args, **kwargs)
-
-    def get_method(self):
-        return 'PROPFIND'
+                print("self.readDir(f) not implemented")
+        return fdir
+
+    def loadDataset(self,project,dataset):
+        url=self.GetLabkeyUrl()+'/'+project
+        url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
+        return json.load(self.get(url))
+
+    def filterDataset(self,project,dataset,variable,value,oper="eq"):
+        debug=True
+        url=self.GetLabkeyUrl()+'/'+project
+        url+='/query-selectRows.api?schemaName=study&query.queryName='+dataset
+        url+="&query."+variable+"~"+oper+"="+value
+        if debug:
+            print("Sending {}").format(url)
+        return json.load(self.get(url))
+
+
+    def modifyDataset(self,method,project,dataset,rows):
+        #method can be insert or update
+        data={}
+        data['schemaName']='study'
+        data['queryName']=dataset
+        data['rows']=rows
+        url=self.GetLabkeyUrl()+'/'+project
+        url+='/query-'+method+'Rows.api?'
+        return self.post(url,json.dumps(data))