labkeySlicerPythonExtension.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. import os
  2. import unittest
  3. from __main__ import vtk, qt, ctk, slicer
  4. from slicer.ScriptedLoadableModule import *
  5. #
  6. # labkeySlicerPythonExtension
  7. #
  8. class labkeySlicerPythonExtension(ScriptedLoadableModule):
  9. """Uses ScriptedLoadableModule base class, available at:
  10. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  11. """
  12. def __init__(self, parent):
  13. ScriptedLoadableModule.__init__(self, parent)
  14. self.parent.title = "labkeySlicerPythonExtension" # TODO make this more human readable by adding spaces
  15. self.parent.categories = ["Examples"]
  16. self.parent.dependencies = []
  17. self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
  18. self.parent.helpText = """
  19. Labkey interface to slicer
  20. """
  21. self.parent.acknowledgementText = """
  22. Developed within the medical physics research programme of the Slovenian research agency.
  23. """ # replace with organization, grant and thanks.
  24. #
  25. # labkeySlicerPythonExtensionWidget
  26. #
  27. class labkeySlicerPythonExtensionWidget(ScriptedLoadableModuleWidget):
  28. """Uses ScriptedLoadableModuleWidget base class, available at:
  29. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  30. """
  31. def setup(self):
  32. ScriptedLoadableModuleWidget.setup(self)
  33. # Instantiate and connect widgets ...
  34. self.logic=labkeySlicerPythonExtensionLogic(self)
  35. #
  36. # Parameters Area
  37. #
  38. connectionCollapsibleButton = ctk.ctkCollapsibleButton()
  39. connectionCollapsibleButton.text = "Connection"
  40. self.layout.addWidget(connectionCollapsibleButton)
  41. connectionFormLayout = qt.QFormLayout(connectionCollapsibleButton)
  42. self.serverURL=qt.QLineEdit("https://merlin.fmf.uni-lj.si")
  43. connectionFormLayout.addRow("Server: ", self.serverURL)
  44. self.userCertButton=qt.QPushButton("Load")
  45. self.userCertButton.toolTip="Load user certificate (crt)"
  46. self.userCertButton.connect('clicked(bool)',self.onUserCertButtonClicked)
  47. connectionFormLayout.addRow("User certificate:",self.userCertButton)
  48. self.privateKeyButton=qt.QPushButton("Load")
  49. self.privateKeyButton.toolTip="Load private key"
  50. self.privateKeyButton.connect('clicked(bool)',self.onPrivateKeyButtonClicked)
  51. connectionFormLayout.addRow("Private key:",self.privateKeyButton)
  52. self.caCertButton=qt.QPushButton("Load")
  53. self.caCertButton.toolTip="Load CA certificate (crt)"
  54. self.caCertButton.connect('clicked(bool)',self.onCaCertButtonClicked)
  55. connectionFormLayout.addRow("CA certificate:",self.caCertButton)
  56. self.connectButton=qt.QPushButton("Connect")
  57. self.connectButton.toolTip="Connect to the server"
  58. self.connectButton.connect('clicked(bool)',self.onConnectButtonClicked)
  59. connectionFormLayout.addRow("Connection:",self.connectButton)
  60. parametersCollapsibleButton = ctk.ctkCollapsibleButton()
  61. parametersCollapsibleButton.text = "Parameters"
  62. self.layout.addWidget(parametersCollapsibleButton)
  63. # Layout within the dummy collapsible button
  64. parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton)
  65. #
  66. # input volume selector
  67. #
  68. self.inputSelector = slicer.qMRMLNodeComboBox()
  69. self.inputSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" )
  70. self.inputSelector.addAttribute( "vtkMRMLScalarVolumeNode", "LabelMap", 0 )
  71. self.inputSelector.selectNodeUponCreation = True
  72. self.inputSelector.addEnabled = False
  73. self.inputSelector.removeEnabled = False
  74. self.inputSelector.noneEnabled = False
  75. self.inputSelector.showHidden = False
  76. self.inputSelector.showChildNodeTypes = False
  77. self.inputSelector.setMRMLScene( slicer.mrmlScene )
  78. self.inputSelector.setToolTip( "Pick the input to the algorithm." )
  79. parametersFormLayout.addRow("Input Volume: ", self.inputSelector)
  80. #
  81. # output volume selector
  82. #
  83. self.outputSelector = slicer.qMRMLNodeComboBox()
  84. self.outputSelector.nodeTypes = ( ("vtkMRMLScalarVolumeNode"), "" )
  85. self.outputSelector.addAttribute( "vtkMRMLScalarVolumeNode", "LabelMap", 0 )
  86. self.outputSelector.selectNodeUponCreation = False
  87. self.outputSelector.addEnabled = True
  88. self.outputSelector.removeEnabled = True
  89. self.outputSelector.noneEnabled = False
  90. self.outputSelector.showHidden = False
  91. self.outputSelector.showChildNodeTypes = False
  92. self.outputSelector.setMRMLScene( slicer.mrmlScene )
  93. self.outputSelector.setToolTip( "Pick the output to the algorithm." )
  94. parametersFormLayout.addRow("Output Volume: ", self.outputSelector)
  95. #
  96. # check box to trigger taking screen shots for later use in tutorials
  97. #
  98. self.enableScreenshotsFlagCheckBox = qt.QCheckBox()
  99. self.enableScreenshotsFlagCheckBox.checked = 0
  100. self.enableScreenshotsFlagCheckBox.setToolTip("If checked, take screen shots for tutorials. Use Save Data to write them to disk.")
  101. parametersFormLayout.addRow("Enable Screenshots", self.enableScreenshotsFlagCheckBox)
  102. #
  103. # scale factor for screen shots
  104. #
  105. self.screenshotScaleFactorSliderWidget = ctk.ctkSliderWidget()
  106. self.screenshotScaleFactorSliderWidget.singleStep = 1.0
  107. self.screenshotScaleFactorSliderWidget.minimum = 1.0
  108. self.screenshotScaleFactorSliderWidget.maximum = 50.0
  109. self.screenshotScaleFactorSliderWidget.value = 1.0
  110. self.screenshotScaleFactorSliderWidget.setToolTip("Set scale factor for the screen shots.")
  111. parametersFormLayout.addRow("Screenshot scale factor", self.screenshotScaleFactorSliderWidget)
  112. #
  113. # Apply Button
  114. #
  115. self.applyButton = qt.QPushButton("Apply")
  116. self.applyButton.toolTip = "Run the algorithm."
  117. self.applyButton.enabled = False
  118. parametersFormLayout.addRow(self.applyButton)
  119. # connections
  120. self.applyButton.connect('clicked(bool)', self.onApplyButton)
  121. self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
  122. self.outputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
  123. # Add vertical spacer
  124. self.layout.addStretch(1)
  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. startDir=os.environ['HOME']+"/temp"
  137. filename=qt.QFileDialog.getOpenFileName(None,'Open user certificate',
  138. startDir, '*.crt')
  139. #pwd=qt.QInputDialog.getText(None,'Certificate password',
  140. # 'Enter certificate password',qt.QLineEdit.Password)
  141. if not(filename) :
  142. print "No file selected"
  143. return
  144. f=qt.QFile(filename)
  145. if not (f.open(qt.QIODevice.ReadOnly)) :
  146. print "Could not open file"
  147. return
  148. certList=qt.QSslCertificate.fromPath(filename)
  149. if len(certList) < 1:
  150. print "Troubles parsing {0}".format(filename)
  151. return
  152. self.logic.cert=qt.QSslCertificate(f)
  153. print "cert.isNull()={0}".format(self.logic.cert.isNull())
  154. self.userCertButton.setText(filename)
  155. def onPrivateKeyButtonClicked(self):
  156. startDir=os.environ['HOME']+"/temp"
  157. filename=qt.QFileDialog.getOpenFileName(None,'Open private key',
  158. startDir, '*.key')
  159. if not (filename) :
  160. print "No file selected"
  161. return
  162. f=qt.QFile(filename)
  163. if not (f.open(qt.QIODevice.ReadOnly)) :
  164. print "Could not open file"
  165. return
  166. pwd=qt.QInputDialog.getText(None,'Private key password',
  167. 'Enter key password',qt.QLineEdit.Password)
  168. self.logic.key=qt.QSslKey(f,qt.QSsl.Rsa,qt.QSsl.Pem,qt.QSsl.PrivateKey,str(pwd))
  169. self.privateKeyButton.setText(filename)
  170. def onCaCertButtonClicked(self):
  171. startDir=os.environ['HOME']+"/temp"
  172. filename=qt.QFileDialog.getOpenFileName(None,'Open authority certificate',
  173. startDir, '*.crt')
  174. if not(filename) :
  175. print "No file selected"
  176. return
  177. f=qt.QFile(filename)
  178. if not (f.open(qt.QIODevice.ReadOnly)) :
  179. print "Could not open file"
  180. return
  181. certList=qt.QSslCertificate.fromPath(filename)
  182. if len(certList) < 1:
  183. print "Troubles parsing {0}".format(filename)
  184. return
  185. self.logic.caCert=qt.QSslCertificate(f)#certList[0]
  186. self.caCertButton.setText(filename)
  187. def onConnectButtonClicked(self):
  188. uname=str(self.logic.cert.subjectInfo("emailAddress"))
  189. uname=qt.QInputDialog.getText(None,
  190. "Labkey credentials","Enter username",qt.QLineEdit.Normal,uname)
  191. pwd=qt.QInputDialog.getText(None,
  192. "Labkey credentials","Enter password",qt.QLineEdit.Password)
  193. self.logic.connectRemote(str(self.serverURL.text),uname,pwd)
  194. #
  195. # labkeySlicerPythonExtensionLogic
  196. #
  197. class labkeySlicerPythonExtensionLogic(ScriptedLoadableModuleLogic):
  198. """This class should implement all the actual
  199. computation done by your module. The interface
  200. should be such that other python code can import
  201. this class and make use of the functionality without
  202. requiring an instance of the Widget.
  203. Uses ScriptedLoadableModuleLogic base class, available at:
  204. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  205. """
  206. def __init__(self,parent):
  207. ScriptedLoadableModuleLogic.__init__(self, parent)
  208. self.qnam=qt.QNetworkAccessManager()
  209. def connectRemote(self,serverURL,uname,pwd):
  210. request=qt.QNetworkRequest()
  211. request.setUrl(qt.QUrl(serverURL));
  212. request.setHeader(qt.QNetworkRequest.ContentTypeHeader,
  213. "application/x-www-form-urlencoded")
  214. data="email="+uname+"&password="+pwd;
  215. #setup the transfer
  216. sConfig=qt.QSslConfiguration()
  217. #user certificate
  218. sConfig.setLocalCertificate(self.cert)
  219. sConfig.setPrivateKey(self.key)
  220. #ca certificate
  221. caList=[self.caCert]
  222. sConfig.setCaCertificates(caList)
  223. request.setSslConfiguration(sConfig)
  224. #post
  225. r=self.qnam.post(request,data)
  226. connect(qnam, qt.QNetworkAccessManager.finished, self, replyFinished);
  227. def replyFinished(self,res):
  228. print "Reply finished"
  229. def hasImageData(self,volumeNode):
  230. """This is a dummy logic method that
  231. returns true if the passed in volume
  232. node has valid image data
  233. """
  234. if not volumeNode:
  235. print('no volume node')
  236. return False
  237. if volumeNode.GetImageData() == None:
  238. print('no image data')
  239. return False
  240. return True
  241. def takeScreenshot(self,name,description,type=-1):
  242. # show the message even if not taking a screen shot
  243. self.delayDisplay(description)
  244. if self.enableScreenshots == 0:
  245. return
  246. lm = slicer.app.layoutManager()
  247. # switch on the type to get the requested window
  248. widget = 0
  249. if type == slicer.qMRMLScreenShotDialog.FullLayout:
  250. # full layout
  251. widget = lm.viewport()
  252. elif type == slicer.qMRMLScreenShotDialog.ThreeD:
  253. # just the 3D window
  254. widget = lm.threeDWidget(0).threeDView()
  255. elif type == slicer.qMRMLScreenShotDialog.Red:
  256. # red slice window
  257. widget = lm.sliceWidget("Red")
  258. elif type == slicer.qMRMLScreenShotDialog.Yellow:
  259. # yellow slice window
  260. widget = lm.sliceWidget("Yellow")
  261. elif type == slicer.qMRMLScreenShotDialog.Green:
  262. # green slice window
  263. widget = lm.sliceWidget("Green")
  264. else:
  265. # default to using the full window
  266. widget = slicer.util.mainWindow()
  267. # reset the type so that the node is set correctly
  268. type = slicer.qMRMLScreenShotDialog.FullLayout
  269. # grab and convert to vtk image data
  270. qpixMap = qt.QPixmap().grabWidget(widget)
  271. qimage = qpixMap.toImage()
  272. imageData = vtk.vtkImageData()
  273. slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
  274. annotationLogic = slicer.modules.annotations.logic()
  275. annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData)
  276. def run(self,inputVolume,outputVolume,enableScreenshots=0,screenshotScaleFactor=1):
  277. """
  278. Run the actual algorithm
  279. """
  280. self.delayDisplay('Running the aglorithm')
  281. self.enableScreenshots = enableScreenshots
  282. self.screenshotScaleFactor = screenshotScaleFactor
  283. self.takeScreenshot('labkeySlicerPythonExtension-Start','Start',-1)
  284. return True
  285. class labkeySlicerPythonExtensionTest(ScriptedLoadableModuleTest):
  286. """
  287. This is the test case for your scripted module.
  288. Uses ScriptedLoadableModuleTest base class, available at:
  289. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  290. """
  291. def setUp(self):
  292. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  293. """
  294. slicer.mrmlScene.Clear(0)
  295. def runTest(self):
  296. """Run as few or as many tests as needed here.
  297. """
  298. self.setUp()
  299. self.test_labkeySlicerPythonExtension1()
  300. def test_labkeySlicerPythonExtension1(self):
  301. """ Ideally you should have several levels of tests. At the lowest level
  302. tests sould exercise the functionality of the logic with different inputs
  303. (both valid and invalid). At higher levels your tests should emulate the
  304. way the user would interact with your code and confirm that it still works
  305. the way you intended.
  306. One of the most important features of the tests is that it should alert other
  307. developers when their changes will have an impact on the behavior of your
  308. module. For example, if a developer removes a feature that you depend on,
  309. your test should break so they know that the feature is needed.
  310. """
  311. self.delayDisplay("Starting the test")
  312. #
  313. # first, get some data
  314. #
  315. import urllib
  316. downloads = (
  317. ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
  318. )
  319. for url,name,loader in downloads:
  320. filePath = slicer.app.temporaryPath + '/' + name
  321. if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
  322. print('Requesting download %s from %s...\n' % (name, url))
  323. urllib.urlretrieve(url, filePath)
  324. if loader:
  325. print('Loading %s...\n' % (name,))
  326. loader(filePath)
  327. self.delayDisplay('Finished with download and loading\n')
  328. volumeNode = slicer.util.getNode(pattern="FA")
  329. logic = labkeySlicerPythonExtensionLogic()
  330. self.assertTrue( logic.hasImageData(volumeNode) )
  331. self.delayDisplay('Test passed!')