labkeyBrowser.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import os
  2. import unittest
  3. from __main__ import vtk, qt, ctk, slicer
  4. from slicer.ScriptedLoadableModule import *
  5. import slicerNetwork
  6. import loadDicom
  7. import json
  8. #
  9. # labkeySlicerPythonExtension
  10. #
  11. class labkeyBrowser(ScriptedLoadableModule):
  12. """Uses ScriptedLoadableModule base class, available at:
  13. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  14. """
  15. def __init__(self, parent):
  16. ScriptedLoadableModule.__init__(self, parent)
  17. self.parent.title = "labkeyBrowser" # TODO make this more human readable by adding spaces
  18. self.parent.categories = ["LabKey"]
  19. self.parent.dependencies = []
  20. self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
  21. self.parent.helpText = """
  22. Interface to files in LabKey
  23. """
  24. self.parent.acknowledgementText = """
  25. Developed within the medical physics research programme of the Slovenian research agency.
  26. """ # replace with organization, grant and thanks.
  27. #
  28. # labkeySlicerPythonExtensionWidget
  29. #
  30. class labkeyBrowserWidget(ScriptedLoadableModuleWidget):
  31. """Uses ScriptedLoadableModuleWidget base class, available at:
  32. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  33. """
  34. def setup(self):
  35. ScriptedLoadableModuleWidget.setup(self)
  36. # Instantiate and connect widgets ...
  37. self.logic=labkeyBrowserLogic(self)
  38. self.network=slicerNetwork.labkeyURIHandler()
  39. #
  40. # Parameters Area
  41. #
  42. connectionCollapsibleButton = ctk.ctkCollapsibleButton()
  43. connectionCollapsibleButton.text = "Connection"
  44. self.layout.addWidget(connectionCollapsibleButton)
  45. connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
  46. self.configDir=os.path.join(getHomeDir(),".labkey")
  47. self.serverURL=qt.QLineEdit("https://merlin.fmf.uni-lj.si")
  48. self.serverURL.textChanged.connect(self.updateServerURL)
  49. connectionFormLayout.addRow("Server: ", self.serverURL)
  50. #copy initial setting
  51. self.updateServerURL(self.serverURL.text);
  52. self.startDir=os.path.join(getHomeDir(),"temp/crt")
  53. self.userCertButton=qt.QPushButton("Load")
  54. self.userCertButton.toolTip="Load user certificate (crt)"
  55. self.userCertButton.connect('clicked(bool)',self.onUserCertButtonClicked)
  56. connectionFormLayout.addRow("User certificate:",self.userCertButton)
  57. self.privateKeyButton=qt.QPushButton("Load")
  58. self.privateKeyButton.toolTip="Load private key"
  59. self.privateKeyButton.connect('clicked(bool)',self.onPrivateKeyButtonClicked)
  60. connectionFormLayout.addRow("Private key:",self.privateKeyButton)
  61. self.caCertButton=qt.QPushButton("Load")
  62. self.caCertButton.toolTip="Load CA certificate (crt)"
  63. self.caCertButton.connect('clicked(bool)',self.onCaCertButtonClicked)
  64. connectionFormLayout.addRow("CA certificate:",self.caCertButton)
  65. self.loadConfigButton=qt.QPushButton("Load configuration")
  66. self.loadConfigButton.toolTip="Load configuration"
  67. self.loadConfigButton.connect('clicked(bool)',self.onLoadConfigButtonClicked)
  68. connectionFormLayout.addRow("Connection:",self.loadConfigButton)
  69. self.initButton=qt.QPushButton("Init")
  70. self.initButton.toolTip="Initialize connection to the server"
  71. self.initButton.connect('clicked(bool)',self.onInitButtonClicked)
  72. connectionFormLayout.addRow("Connection:",self.initButton)
  73. self.authName=qt.QLineEdit("email")
  74. self.authName.textChanged.connect(self.updateAuthName)
  75. connectionFormLayout.addRow("Labkey username: ", self.authName)
  76. self.authPass=qt.QLineEdit("")
  77. self.authPass.setEchoMode(qt.QLineEdit.Password)
  78. self.authPass.textChanged.connect(self.updateAuthPass)
  79. connectionFormLayout.addRow("Labkey password: ", self.authPass)
  80. fileDialogCollapsibleButton = ctk.ctkCollapsibleButton()
  81. fileDialogCollapsibleButton.text = "Remote files"
  82. self.layout.addWidget(fileDialogCollapsibleButton)
  83. # Layout within the dummy collapsible button
  84. fileDialogFormLayout = qt.QFormLayout(fileDialogCollapsibleButton)
  85. #add item list for each found file/directory
  86. self.fileList=qt.QListWidget()
  87. self.fileList.toolTip="Select remote file"
  88. self.fileList.itemDoubleClicked.connect(self.onFileListDoubleClicked)
  89. self.currentRemoteDir=''
  90. #add dummy entry
  91. items=('.','..')
  92. self.populateFileList(items)
  93. fileDialogFormLayout.addWidget(self.fileList)
  94. #add selected file display
  95. self.selectedFile=qt.QLineEdit("")
  96. self.selectedFile.toolTip="Selected file"
  97. fileDialogFormLayout.addRow("Selected file :",self.selectedFile)
  98. #add possible file Content
  99. self.fileTypeSelector=qt.QComboBox()
  100. self.fileTypeSelector.toolTip="Select file type"
  101. items=self.network.fileTypesAvailable()
  102. self.populateFileTypeSelector(items)
  103. fileDialogFormLayout.addRow("File type :",self.fileTypeSelector)
  104. self.keepCachedFileCheckBox=qt.QCheckBox("keep cached file")
  105. self.keepCachedFileCheckBox.toolTip="Toggle local storage of labkey files"
  106. self.keepCachedFileCheckBox.setChecked(True)
  107. fileDialogFormLayout.addRow("Manage cache :",self.keepCachedFileCheckBox)
  108. loadFileButton=qt.QPushButton("Load file")
  109. loadFileButton.toolTip="Load file"
  110. loadFileButton.clicked.connect(self.onLoadFileButtonClicked)
  111. fileDialogFormLayout.addRow("Action :",loadFileButton)
  112. loadDirButton=qt.QPushButton("Load directory")
  113. loadDirButton.toolTip="Load directory"
  114. loadDirButton.clicked.connect(self.onLoadDirButtonClicked)
  115. fileDialogFormLayout.addRow("Action :",loadDirButton)
  116. def populateFileList(self,items):
  117. self.fileList.clear()
  118. for it_text in items:
  119. item=qt.QListWidgetItem(self.fileList)
  120. item.setText(it_text)
  121. item.setIcon(qt.QIcon(it_text))
  122. def populateFileTypeSelector(self,items):
  123. for item in items:
  124. self.fileTypeSelector.addItem(item)
  125. def cleanup(self):
  126. pass
  127. def onSelect(self):
  128. self.applyButton.enabled = self.inputSelector.currentNode() and self.outputSelector.currentNode()
  129. def onApplyButton(self):
  130. #logic = labkeySlicerPythonExtensionLogic()
  131. #enableScreenshotsFlag = self.enableScreenshotsFlagCheckBox.checked
  132. #screenshotScaleFactor = int(self.screenshotScaleFactorSliderWidget.value)
  133. print("Run the algorithm")
  134. #logic.run(self.inputSelector.currentNode(), self.outputSelector.currentNode(), enableScreenshotsFlag,screenshotScaleFactor)
  135. def onUserCertButtonClicked(self):
  136. filename=qt.QFileDialog.getOpenFileName(None,'Open user certificate',
  137. self.startDir, '*.crt')
  138. #pwd=qt.QInputDialog.getText(None,'Certificate password',
  139. # 'Enter certificate password',qt.QLineEdit.Password)
  140. if not(filename) :
  141. print("No file selected")
  142. return
  143. f=qt.QFile(filename)
  144. if not (f.open(qt.QIODevice.ReadOnly)) :
  145. print("Could not open file")
  146. return
  147. certList=qt.QSslCertificate.fromPath(filename)
  148. if len(certList) < 1:
  149. print ("Troubles parsing {0}").format(filename)
  150. return
  151. self.logic.cert=qt.QSslCertificate(f)
  152. print("cert.isNull()={0}").format(self.logic.cert.isNull())
  153. self.userCertButton.setText(filename)
  154. self.authName.setText(self.logic.cert.subjectInfo("emailAddress"))
  155. def onPrivateKeyButtonClicked(self):
  156. filename=qt.QFileDialog.getOpenFileName(None,'Open private key',
  157. self.startDir, '*.key')
  158. if not (filename) :
  159. print ("No file selected")
  160. return
  161. f=qt.QFile(filename)
  162. if not (f.open(qt.QIODevice.ReadOnly)) :
  163. print ("Could not open file")
  164. return
  165. self.pwd=qt.QInputDialog.getText(None,'Private key password',
  166. 'Enter key password',qt.QLineEdit.Password)
  167. self.logic.key=qt.QSslKey(f,qt.QSsl.Rsa,qt.QSsl.Pem,qt.QSsl.PrivateKey,
  168. str(self.pwd))
  169. self.privateKeyButton.setText(filename)
  170. def onCaCertButtonClicked(self):
  171. filename=qt.QFileDialog.getOpenFileName(None,'Open authority certificate',
  172. self.startDir, '*.crt')
  173. if not(filename) :
  174. print("No file selected")
  175. return
  176. f=qt.QFile(filename)
  177. if not (f.open(qt.QIODevice.ReadOnly)) :
  178. print("Could not open file")
  179. return
  180. certList=qt.QSslCertificate.fromPath(filename)
  181. if len(certList) < 1:
  182. print("Troubles parsing {0}").format(filename)
  183. return
  184. self.logic.caCert=qt.QSslCertificate(f)#certList[0]
  185. self.caCertButton.setText(filename)
  186. def onLoadConfigButtonClicked(self):
  187. filename=qt.QFileDialog.getOpenFileName(None,'Open configuration file (JSON)',
  188. self.configDir, '*.json')
  189. self.network.parseConfig(filename)
  190. self.serverURL.setText(self.network.hostname)
  191. self.authName.setText(self.network.auth_name)
  192. self.authPass.setText(self.network.auth_pass)
  193. self.loadConfigButton.setText(os.path.basename(filename))
  194. def onInitButtonClicked(self):
  195. if self.serverURL.text.find("https")==0:
  196. try:
  197. self.network.configureSSL(
  198. self.userCertButton.text,
  199. self.privateKeyButton.text,
  200. self.pwd,
  201. self.caCertButton.text)
  202. except AttributeError:
  203. pass
  204. self.network.initRemote()
  205. def updateAuthName(self,txt):
  206. self.network.auth_name=txt
  207. print("Setting username to {0}").format(self.network.auth_name);
  208. def updateAuthPass(self,txt):
  209. self.network.auth_pass=txt
  210. print("Setting password.")
  211. def updateServerURL(self,txt):
  212. self.network.hostname=txt
  213. print("Setting hostname to {0}").format(self.network.hostname);
  214. def onFileListDoubleClicked(self,item):
  215. if item == None:
  216. print("Selected items: None")
  217. return
  218. iText=item.text()
  219. print("Selected items: {0} ").format(iText)
  220. #this is hard -> compose path string from currentRemoteDir and selection
  221. if item.text().find('..')==0:
  222. #one up
  223. idx=self.currentRemoteDir.rfind('/')
  224. if idx<0:
  225. self.currentRemoteDir=''
  226. else:
  227. self.currentRemoteDir=self.currentRemoteDir[:idx]
  228. elif item.text().find('.')==0:
  229. pass
  230. else:
  231. if len(self.currentRemoteDir)>0:
  232. self.currentRemoteDir+='/'
  233. self.currentRemoteDir+=item.text()
  234. print("Listing {0}").format(self.currentRemoteDir)
  235. flist=self.network.toRelativePath(
  236. self.network.listRelativeDir(self.currentRemoteDir))
  237. print("Got")
  238. print(flist)
  239. flist.insert(0,'..')
  240. flist.insert(0,'.')
  241. self.populateFileList(flist)
  242. self.selectedFile.setText(self.currentRemoteDir)
  243. def onLoadFileButtonClicked(self):
  244. properties={}
  245. localPath=self.network.DownloadFileToCache(self.selectedFile.text)
  246. slicer.util.loadNodeFromFile(localPath,self.fileTypeSelector.currentText,
  247. properties,returnNode=False)
  248. if not self.keepCachedFileCheckBox.isChecked():
  249. os.remove(localPath)
  250. def onLoadDirButtonClicked(self):
  251. #dir=self.network.loadDir(self.selectedFile.text)
  252. dir=self.selectedFile.text
  253. try:
  254. self.loadDicomLogic.load(self.network,dir)
  255. except:
  256. self.loadDicomLogic=loadDicom.loadDicomLogic(self)
  257. self.loadDicomLogic.load(self.network,dir)
  258. # labkeySlicerPythonExtensionLogic
  259. #
  260. class labkeyBrowserLogic(ScriptedLoadableModuleLogic):
  261. """This class should implement all the actual
  262. computation done by your module. The interface
  263. should be such that other python code can import
  264. this class and make use of the functionality without
  265. requiring an instance of the Widget.
  266. Uses ScriptedLoadableModuleLogic base class, available at:
  267. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  268. """
  269. def __init__(self,parent):
  270. ScriptedLoadableModuleLogic.__init__(self, parent)
  271. self.qnam=qt.QNetworkAccessManager()
  272. def hasImageData(self,volumeNode):
  273. """This is a dummy logic method that
  274. returns true if the passed in volume
  275. node has valid image data
  276. """
  277. if not volumeNode:
  278. print('no volume node')
  279. return False
  280. if volumeNode.GetImageData() == None:
  281. print('no image data')
  282. return False
  283. return True
  284. def takeScreenshot(self,name,description,type=-1):
  285. # show the message even if not taking a screen shot
  286. self.delayDisplay(description)
  287. if self.enableScreenshots == 0:
  288. return
  289. lm = slicer.app.layoutManager()
  290. # switch on the type to get the requested window
  291. widget = 0
  292. if type == slicer.qMRMLScreenShotDialog.FullLayout:
  293. # full layout
  294. widget = lm.viewport()
  295. elif type == slicer.qMRMLScreenShotDialog.ThreeD:
  296. # just the 3D window
  297. widget = lm.threeDWidget(0).threeDView()
  298. elif type == slicer.qMRMLScreenShotDialog.Red:
  299. # red slice window
  300. widget = lm.sliceWidget("Red")
  301. elif type == slicer.qMRMLScreenShotDialog.Yellow:
  302. # yellow slice window
  303. widget = lm.sliceWidget("Yellow")
  304. elif type == slicer.qMRMLScreenShotDialog.Green:
  305. # green slice window
  306. widget = lm.sliceWidget("Green")
  307. else:
  308. # default to using the full window
  309. widget = slicer.util.mainWindow()
  310. # reset the type so that the node is set correctly
  311. type = slicer.qMRMLScreenShotDialog.FullLayout
  312. # grab and convert to vtk image data
  313. qpixMap = qt.QPixmap().grabWidget(widget)
  314. qimage = qpixMap.toImage()
  315. imageData = vtk.vtkImageData()
  316. slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
  317. annotationLogic = slicer.modules.annotations.logic()
  318. annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData)
  319. def run(self,inputVolume,outputVolume,enableScreenshots=0,screenshotScaleFactor=1):
  320. """
  321. Run the actual algorithm
  322. """
  323. self.delayDisplay('Running the aglorithm')
  324. self.enableScreenshots = enableScreenshots
  325. self.screenshotScaleFactor = screenshotScaleFactor
  326. self.takeScreenshot('labkeySlicerPythonExtension-Start','Start',-1)
  327. return True
  328. class labkeyBrowserTest(ScriptedLoadableModuleTest):
  329. """
  330. This is the test case for your scripted module.
  331. Uses ScriptedLoadableModuleTest base class, available at:
  332. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  333. """
  334. def setUp(self):
  335. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  336. """
  337. slicer.mrmlScene.Clear(0)
  338. def runTest(self):
  339. """Run as few or as many tests as needed here.
  340. """
  341. self.setUp()
  342. self.test_labkeySlicerPythonExtension1()
  343. def test_labkeyBrowser(self):
  344. """ Ideally you should have several levels of tests. At the lowest level
  345. tests sould exercise the functionality of the logic with different inputs
  346. (both valid and invalid). At higher levels your tests should emulate the
  347. way the user would interact with your code and confirm that it still works
  348. the way you intended.
  349. One of the most important features of the tests is that it should alert other
  350. developers when their changes will have an impact on the behavior of your
  351. module. For example, if a developer removes a feature that you depend on,
  352. your test should break so they know that the feature is needed.
  353. """
  354. self.delayDisplay("Starting the test")
  355. #
  356. # first, get some data
  357. #
  358. import urllib
  359. downloads = (
  360. ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
  361. )
  362. for url,name,loader in downloads:
  363. filePath = slicer.app.temporaryPath + '/' + name
  364. if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
  365. print('Requesting download %s from %s...\n' % (name, url))
  366. urllib.urlretrieve(url, filePath)
  367. if loader:
  368. print('Loading %s...\n' % (name,))
  369. loader(filePath)
  370. self.delayDisplay('Finished with download and loading\n')
  371. volumeNode = slicer.util.getNode(pattern="FA")
  372. logic = labkeySlicerPythonExtensionLogic()
  373. self.assertTrue( logic.hasImageData(volumeNode) )
  374. self.delayDisplay('Test passed!')
  375. def getHomeDir():
  376. try:
  377. return os.environ['HOME']
  378. except:
  379. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  380. return fhome