labkeySlicerPythonExtension.py 17 KB

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