labkeySlicerPythonExtension.py 17 KB

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