labkeySlicerPythonExtension.py 16 KB

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