labkeySlicerPythonExtension.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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=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. self.network.configureSSL(
  197. self.userCertButton.text,
  198. self.privateKeyButton.text,
  199. self.pwd,
  200. self.caCertButton.text
  201. )
  202. self.network.initRemote()
  203. def updateAuthName(self,txt):
  204. self.network.auth_name=txt
  205. print("Setting username to {0}").format(self.network.auth_name);
  206. def updateAuthPass(self,txt):
  207. self.network.auth_pass=txt
  208. print("Setting password.")
  209. def updateServerURL(self,txt):
  210. self.network.hostname=txt
  211. print("Setting hostname to {0}").format(self.network.hostname);
  212. def onFileListDoubleClicked(self,item):
  213. if item == None:
  214. print("Selected items: None")
  215. return
  216. iText=item.text()
  217. print("Selected items: {0} ").format(iText)
  218. #this is hard -> compose path string from currentRemoteDir and selection
  219. if item.text().find('..')==0:
  220. #one up
  221. idx=self.currentRemoteDir.rfind('/')
  222. if idx<0:
  223. self.currentRemoteDir=''
  224. else:
  225. self.currentRemoteDir=self.currentRemoteDir[:idx]
  226. elif item.text().find('.')==0:
  227. pass
  228. else:
  229. if len(self.currentRemoteDir)>0:
  230. self.currentRemoteDir+='/'
  231. self.currentRemoteDir+=item.text()
  232. print("Listing {0}").format(self.currentRemoteDir)
  233. flist=self.network.toRelativePath(
  234. self.network.listRelativeDir(self.currentRemoteDir))
  235. print("Got")
  236. print(flist)
  237. flist.insert(0,'..')
  238. flist.insert(0,'.')
  239. self.populateFileList(flist)
  240. self.selectedFile.setText(self.currentRemoteDir)
  241. def onLoadFileButtonClicked(self):
  242. properties={}
  243. localPath=self.network.DownloadFileToCache(self.selectedFile.text)
  244. slicer.util.loadNodeFromFile(localPath,self.fileTypeSelector.currentText,
  245. properties,returnNode=false)
  246. if not self.keepCachedFileCheckBox.isChecked():
  247. os.remove(localPath)
  248. def onLoadDirButtonClicked(self):
  249. #dir=self.network.loadDir(self.selectedFile.text)
  250. dir=self.selectedFile.text
  251. try:
  252. self.loadDicomLogic.load(self.network,dir)
  253. except:
  254. self.loadDicomLogic=loadDicom.loadDicomLogic(self)
  255. self.loadDicomLogic.load(self.network,dir)
  256. # labkeySlicerPythonExtensionLogic
  257. #
  258. class labkeySlicerPythonExtensionLogic(ScriptedLoadableModuleLogic):
  259. """This class should implement all the actual
  260. computation done by your module. The interface
  261. should be such that other python code can import
  262. this class and make use of the functionality without
  263. requiring an instance of the Widget.
  264. Uses ScriptedLoadableModuleLogic base class, available at:
  265. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  266. """
  267. def __init__(self,parent):
  268. ScriptedLoadableModuleLogic.__init__(self, parent)
  269. self.qnam=qt.QNetworkAccessManager()
  270. def hasImageData(self,volumeNode):
  271. """This is a dummy logic method that
  272. returns true if the passed in volume
  273. node has valid image data
  274. """
  275. if not volumeNode:
  276. print('no volume node')
  277. return False
  278. if volumeNode.GetImageData() == None:
  279. print('no image data')
  280. return False
  281. return True
  282. def takeScreenshot(self,name,description,type=-1):
  283. # show the message even if not taking a screen shot
  284. self.delayDisplay(description)
  285. if self.enableScreenshots == 0:
  286. return
  287. lm = slicer.app.layoutManager()
  288. # switch on the type to get the requested window
  289. widget = 0
  290. if type == slicer.qMRMLScreenShotDialog.FullLayout:
  291. # full layout
  292. widget = lm.viewport()
  293. elif type == slicer.qMRMLScreenShotDialog.ThreeD:
  294. # just the 3D window
  295. widget = lm.threeDWidget(0).threeDView()
  296. elif type == slicer.qMRMLScreenShotDialog.Red:
  297. # red slice window
  298. widget = lm.sliceWidget("Red")
  299. elif type == slicer.qMRMLScreenShotDialog.Yellow:
  300. # yellow slice window
  301. widget = lm.sliceWidget("Yellow")
  302. elif type == slicer.qMRMLScreenShotDialog.Green:
  303. # green slice window
  304. widget = lm.sliceWidget("Green")
  305. else:
  306. # default to using the full window
  307. widget = slicer.util.mainWindow()
  308. # reset the type so that the node is set correctly
  309. type = slicer.qMRMLScreenShotDialog.FullLayout
  310. # grab and convert to vtk image data
  311. qpixMap = qt.QPixmap().grabWidget(widget)
  312. qimage = qpixMap.toImage()
  313. imageData = vtk.vtkImageData()
  314. slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
  315. annotationLogic = slicer.modules.annotations.logic()
  316. annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData)
  317. def run(self,inputVolume,outputVolume,enableScreenshots=0,screenshotScaleFactor=1):
  318. """
  319. Run the actual algorithm
  320. """
  321. self.delayDisplay('Running the aglorithm')
  322. self.enableScreenshots = enableScreenshots
  323. self.screenshotScaleFactor = screenshotScaleFactor
  324. self.takeScreenshot('labkeySlicerPythonExtension-Start','Start',-1)
  325. return True
  326. class labkeySlicerPythonExtensionTest(ScriptedLoadableModuleTest):
  327. """
  328. This is the test case for your scripted module.
  329. Uses ScriptedLoadableModuleTest base class, available at:
  330. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  331. """
  332. def setUp(self):
  333. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  334. """
  335. slicer.mrmlScene.Clear(0)
  336. def runTest(self):
  337. """Run as few or as many tests as needed here.
  338. """
  339. self.setUp()
  340. self.test_labkeySlicerPythonExtension1()
  341. def test_labkeySlicerPythonExtension1(self):
  342. """ Ideally you should have several levels of tests. At the lowest level
  343. tests sould exercise the functionality of the logic with different inputs
  344. (both valid and invalid). At higher levels your tests should emulate the
  345. way the user would interact with your code and confirm that it still works
  346. the way you intended.
  347. One of the most important features of the tests is that it should alert other
  348. developers when their changes will have an impact on the behavior of your
  349. module. For example, if a developer removes a feature that you depend on,
  350. your test should break so they know that the feature is needed.
  351. """
  352. self.delayDisplay("Starting the test")
  353. #
  354. # first, get some data
  355. #
  356. import urllib
  357. downloads = (
  358. ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
  359. )
  360. for url,name,loader in downloads:
  361. filePath = slicer.app.temporaryPath + '/' + name
  362. if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
  363. print('Requesting download %s from %s...\n' % (name, url))
  364. urllib.urlretrieve(url, filePath)
  365. if loader:
  366. print('Loading %s...\n' % (name,))
  367. loader(filePath)
  368. self.delayDisplay('Finished with download and loading\n')
  369. volumeNode = slicer.util.getNode(pattern="FA")
  370. logic = labkeySlicerPythonExtensionLogic()
  371. self.assertTrue( logic.hasImageData(volumeNode) )
  372. self.delayDisplay('Test passed!')
  373. def getHomeDir():
  374. try:
  375. return os.environ['HOME']
  376. except:
  377. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  378. return fhome