浏览代码

Adding crude versions of loadDicom and fileIO modules with gentle updates to slicerNetwork

Andrej Studen 6 年之前
父节点
当前提交
dc01281f25

+ 225 - 0
labkeySlicerPythonExtension/fileIO.py

@@ -0,0 +1,225 @@
+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)
+
+
+    def onListLocalButtonClicked(self):
+        self.localPath.setText(qt.QFileDialog.getOpenFileName())
+
+    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 onCopyToRemoteButtonClicked(self):
+            if os.path.isfile(self.localPath.text):
+                self.network.copyFileToRemote(self.localPath.text,self.remotePath.text)
+            else:
+                print "Not implemented for directories"
+
+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

+ 12 - 28
labkeySlicerPythonExtension/labkeySlicerPythonExtension.py

@@ -3,6 +3,7 @@ import unittest
 from __main__ import vtk, qt, ctk, slicer
 from slicer.ScriptedLoadableModule import *
 import slicerNetwork
+import loadDicom
 import json
 
 #
@@ -83,12 +84,6 @@ 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)
@@ -246,23 +241,6 @@ class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
       self.logic.caCert=qt.QSslCertificate(f)#certList[0]
       self.caCertButton.setText(filename)
 
-  def onConnectButtonClicked(self):
-      self.network.configureSSL(
-        self.userCertButton.text,
-        self.privateKeyButton.text,
-        self.pwd,
-        self.caCertButton.text
-      )
-
-      uname=str(self.logic.cert.subjectInfo("emailAddress"))
-      uname=qt.QInputDialog.getText(None,
-        "Labkey credentials","Enter username",qt.QLineEdit.Normal,uname)
-
-      self.auth_pwd=qt.QInputDialog.getText(None,
-        "Labkey credentials","Enter password",qt.QLineEdit.Password,self.auth_pwd)
-
-      self.network.connectRemote(str(self.serverURL.text),uname,self.auth_pwd)
-
   def onLoadConfigButtonClicked(self):
        filename=qt.QFileDialog.getOpenFileName(None,'Open configuration file (JSON)',
             self.configDir, '*.json')
@@ -351,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
 #
 

+ 98 - 0
labkeySlicerPythonExtension/loadDicom.py

@@ -0,0 +1,98 @@
+import slicer
+import os
+import subprocess
+import re
+
+dicomModify="/afs/f9.ijs.si/home/studen/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)

+ 102 - 51
labkeySlicerPythonExtension/slicerNetwork.py

@@ -27,8 +27,10 @@ class MethodRequest(urllib2.Request):
             return self._method
         return urllib2.Request.get_method(self, *args, **kwargs)
 
-class slicerNetwork:
-    def __init__(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):
@@ -115,7 +117,9 @@ 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
@@ -126,14 +130,16 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         local=open(dest,'wb')
         #make sure we are at the begining of the file
 
-	#check file size
-	remote.seek(0,2)
-	sz=remote.tell()
-	print "Remote size: {0}".format(sz)
+	    #check file size
+        if debug:
+	         remote.seek(0,2)
+	         sz=remote.tell()
+	         print "Remote size: {0}".format(sz)
 
-	remote.seek(0)
+        remote.seek(0)
         shutil.copyfileobj(remote,local)
-	print "Local size: {0}".format(local.tell())
+        if debug:
+	         print "Local size: {0}".format(local.tell())
         local.close()
 
     def StageFileWrite(self,source,dest):
@@ -144,28 +150,28 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         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
 
@@ -181,23 +187,6 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         self.mode="https"
 
 
-    def connectRemote(self,serverUrl,uname,pwd):
-        https_handler=urllib2.HTTPSHandler(context=self.ctx)
-        self.SetHostName(serverUrl)
-        #cookie part
-        cj=cookielib.CookieJar()
-        cookie_handler=urllib2.HTTPCookieProcessor(cj)
-        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
-        #print "Data: '{0}".format(data)
-        r.add_data(data)
-
-        self.opener.open(r)
-
     def initRemote(self):
         if self.mode=="https":
             http_handler=urllib2.HTTPSHandler(context=self.ctx)
@@ -233,8 +222,10 @@ class labkeyURIHandler(slicer.vtkURIHandler):
 
     def get(self,url):
 
-        print "GET: {0}".format(url)
-        print "as {0}".format(self.auth_name)
+        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)
@@ -320,6 +311,38 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         status,dirs=self.listRemoteDir(dirUrl)
         return dirs
 
+    def isDir(self, 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)
+        dirs=[]
+        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
@@ -387,6 +410,24 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         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)
@@ -397,7 +438,7 @@ class labkeyURIHandler(slicer.vtkURIHandler):
                 fdir=os.path.dirname(self.GetFile(f))
             except:
                 #fails if there is a subdirectory; go recursively
-                self.readDir(f)
+                print "self.readDir(f) not implemented"
         return fdir
 
     def loadDataset(self,project,dataset):
@@ -405,6 +446,16 @@ class labkeyURIHandler(slicer.vtkURIHandler):
         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={}