labkeyBrowser.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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 labkeyBrowser(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 = "labkeyBrowser" # TODO make this more human readable by adding spaces
  18. self.parent.categories = ["LabKey"]
  19. self.parent.dependencies = []
  20. self.parent.contributors = ["Andrej Studen (UL/FMF)"] # replace with "Firstname Lastname (Organization)"
  21. self.parent.helpText = """
  22. Interface to files in LabKey
  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 labkeyBrowserWidget(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=labkeyBrowserLogic(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(os.path.expanduser('~'),".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(os.path.expanduser('~'),"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("Configuration:",self.loadConfigButton)
  69. self.saveConfigButton=qt.QPushButton("Save configuration")
  70. self.saveConfigButton.toolTip="Save configuration"
  71. self.saveConfigButton.clicked.connect(self.onSaveConfigButtonClicked)
  72. connectionFormLayout.addRow("Configuration:",self.saveConfigButton)
  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. self.network.parseConfig(filename,self)
  194. #self.serverURL.setText(self.network.hostname)
  195. #self.authName.setText(self.network.auth_name)
  196. #self.authPass.setText(self.network.auth_pass)
  197. self.loadConfigButton.setText(os.path.basename(filename))
  198. def onSaveConfigButtonClicked(self):
  199. connectionConfig={}
  200. #setup SSL
  201. if self.privateKeyButton.text=="Load" or\
  202. self.userCertButton.text=="Load" or\
  203. self.caCertButton.text=="Load":
  204. #no ssl
  205. pass
  206. else:
  207. sslSetup={}
  208. sslSetup['user']=self.userCertButton.text
  209. sslSetup['key']=self.privateKeyButton.text
  210. sslSetup['ca']=self.caCertButton.text
  211. sslSetup['keyPwd']=self.pwd
  212. connectionConfig['SSL']=sslSetup
  213. connectionConfig["host"]=self.serverURL.text
  214. connectionConfig["context"]="labkey"
  215. labkeySetup={}
  216. labkeySetup['user']=self.authName.text
  217. labkeySetup['password']=self.authPass.text
  218. connectionConfig['labkey']=labkeySetup
  219. orthancSetup={}
  220. if self.serverURL.text.find('merlin')>-1:
  221. orthancSetup['server']='https://orthanc.fmf.uni-lj.si'
  222. if self.serverURL.text.find('onko-nix')>-1:
  223. orthancSetup['server']='http://onko-nix.onko-i.si:8042'
  224. orthancSetup['user']='ask'
  225. orthancSetup['password']='askPassword'
  226. connectionConfig['orthanc']=orthancSetup
  227. fhome=os.path.expanduser('~')
  228. labkeyDir=os.path.join(fhome,".labkey")
  229. if not os.path.isdir(labkeyDir):
  230. os.mkdir(labkeyDir)
  231. fconfig=os.path.join(labkeyDir,'network.json')
  232. with open(fconfig,'w') as f:
  233. json.dump(connectionConfig,f,indent=3)
  234. print("Done")
  235. def onInitButtonClicked(self):
  236. if self.serverURL.text.find("https")==0:
  237. try:
  238. self.network.configureSSL(
  239. self.userCertButton.text,
  240. self.privateKeyButton.text,
  241. self.pwd,
  242. self.caCertButton.text)
  243. except AttributeError:
  244. pass
  245. self.network.initRemote()
  246. if self.network.getCSRF()==None:
  247. self.initButton.setStyleSheet("background-color: red")
  248. else:
  249. self.initButton.setStyleSheet("background-color: green")
  250. def updateAuthName(self,txt):
  251. self.network.auth_name=txt
  252. print("Setting username to {0}").format(self.network.auth_name);
  253. def updateAuthPass(self,txt):
  254. self.network.auth_pass=txt
  255. print("Setting password.")
  256. def updateServerURL(self,txt):
  257. self.network.hostname=txt
  258. print("Setting hostname to {0}").format(self.network.hostname);
  259. def onFileListDoubleClicked(self,item):
  260. if item == None:
  261. print("Selected items: None")
  262. return
  263. iText=item.text()
  264. print("Selected items: {0} ").format(iText)
  265. #this is hard -> compose path string from currentRemoteDir and selection
  266. if item.text().find('..')==0:
  267. #one up
  268. idx=self.currentRemoteDir.rfind('/')
  269. if idx<0:
  270. self.currentRemoteDir=''
  271. else:
  272. self.currentRemoteDir=self.currentRemoteDir[:idx]
  273. elif item.text().find('.')==0:
  274. pass
  275. else:
  276. if len(self.currentRemoteDir)>0:
  277. self.currentRemoteDir+='/'
  278. self.currentRemoteDir+=item.text()
  279. print("Listing {0}").format(self.currentRemoteDir)
  280. flist=self.network.toRelativePath(
  281. self.network.listRelativeDir(self.currentRemoteDir))
  282. print("Got")
  283. print(flist)
  284. flist.insert(0,'..')
  285. flist.insert(0,'.')
  286. self.populateFileList(flist)
  287. self.selectedFile.setText(self.currentRemoteDir)
  288. def onLoadFileButtonClicked(self):
  289. properties={}
  290. localPath=self.network.DownloadFileToCache(self.selectedFile.text)
  291. slicer.util.loadNodeFromFile(localPath,self.fileTypeSelector.currentText,
  292. properties,returnNode=False)
  293. if not self.keepCachedFileCheckBox.isChecked():
  294. os.remove(localPath)
  295. def onLoadDirButtonClicked(self):
  296. #dir=self.network.loadDir(self.selectedFile.text)
  297. dir=self.selectedFile.text
  298. try:
  299. self.loadDicomLogic.load(self.network,dir)
  300. except:
  301. self.loadDicomLogic=loadDicom.loadDicomLogic(self)
  302. self.loadDicomLogic.load(self.network,dir)
  303. # labkeySlicerPythonExtensionLogic
  304. #
  305. class labkeyBrowserLogic(ScriptedLoadableModuleLogic):
  306. """This class should implement all the actual
  307. computation done by your module. The interface
  308. should be such that other python code can import
  309. this class and make use of the functionality without
  310. requiring an instance of the Widget.
  311. Uses ScriptedLoadableModuleLogic base class, available at:
  312. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  313. """
  314. def __init__(self,parent):
  315. ScriptedLoadableModuleLogic.__init__(self, parent)
  316. self.qnam=qt.QNetworkAccessManager()
  317. def hasImageData(self,volumeNode):
  318. """This is a dummy logic method that
  319. returns true if the passed in volume
  320. node has valid image data
  321. """
  322. if not volumeNode:
  323. print('no volume node')
  324. return False
  325. if volumeNode.GetImageData() == None:
  326. print('no image data')
  327. return False
  328. return True
  329. def takeScreenshot(self,name,description,type=-1):
  330. # show the message even if not taking a screen shot
  331. self.delayDisplay(description)
  332. if self.enableScreenshots == 0:
  333. return
  334. lm = slicer.app.layoutManager()
  335. # switch on the type to get the requested window
  336. widget = 0
  337. if type == slicer.qMRMLScreenShotDialog.FullLayout:
  338. # full layout
  339. widget = lm.viewport()
  340. elif type == slicer.qMRMLScreenShotDialog.ThreeD:
  341. # just the 3D window
  342. widget = lm.threeDWidget(0).threeDView()
  343. elif type == slicer.qMRMLScreenShotDialog.Red:
  344. # red slice window
  345. widget = lm.sliceWidget("Red")
  346. elif type == slicer.qMRMLScreenShotDialog.Yellow:
  347. # yellow slice window
  348. widget = lm.sliceWidget("Yellow")
  349. elif type == slicer.qMRMLScreenShotDialog.Green:
  350. # green slice window
  351. widget = lm.sliceWidget("Green")
  352. else:
  353. # default to using the full window
  354. widget = slicer.util.mainWindow()
  355. # reset the type so that the node is set correctly
  356. type = slicer.qMRMLScreenShotDialog.FullLayout
  357. # grab and convert to vtk image data
  358. qpixMap = qt.QPixmap().grabWidget(widget)
  359. qimage = qpixMap.toImage()
  360. imageData = vtk.vtkImageData()
  361. slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
  362. annotationLogic = slicer.modules.annotations.logic()
  363. annotationLogic.CreateSnapShot(name, description, type, self.screenshotScaleFactor, imageData)
  364. def run(self,inputVolume,outputVolume,enableScreenshots=0,screenshotScaleFactor=1):
  365. """
  366. Run the actual algorithm
  367. """
  368. self.delayDisplay('Running the aglorithm')
  369. self.enableScreenshots = enableScreenshots
  370. self.screenshotScaleFactor = screenshotScaleFactor
  371. self.takeScreenshot('labkeySlicerPythonExtension-Start','Start',-1)
  372. return True
  373. class labkeyBrowserTest(ScriptedLoadableModuleTest):
  374. """
  375. This is the test case for your scripted module.
  376. Uses ScriptedLoadableModuleTest base class, available at:
  377. https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
  378. """
  379. def setUp(self):
  380. """ Do whatever is needed to reset the state - typically a scene clear will be enough.
  381. """
  382. slicer.mrmlScene.Clear(0)
  383. def runTest(self):
  384. """Run as few or as many tests as needed here.
  385. """
  386. self.setUp()
  387. self.test_labkeySlicerPythonExtension1()
  388. def test_labkeyBrowser(self):
  389. """ Ideally you should have several levels of tests. At the lowest level
  390. tests sould exercise the functionality of the logic with different inputs
  391. (both valid and invalid). At higher levels your tests should emulate the
  392. way the user would interact with your code and confirm that it still works
  393. the way you intended.
  394. One of the most important features of the tests is that it should alert other
  395. developers when their changes will have an impact on the behavior of your
  396. module. For example, if a developer removes a feature that you depend on,
  397. your test should break so they know that the feature is needed.
  398. """
  399. self.delayDisplay("Starting the test")
  400. #
  401. # first, get some data
  402. #
  403. import urllib
  404. downloads = (
  405. ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
  406. )
  407. for url,name,loader in downloads:
  408. filePath = slicer.app.temporaryPath + '/' + name
  409. if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
  410. print('Requesting download %s from %s...\n' % (name, url))
  411. urllib.urlretrieve(url, filePath)
  412. if loader:
  413. print('Loading %s...\n' % (name,))
  414. loader(filePath)
  415. self.delayDisplay('Finished with download and loading\n')
  416. volumeNode = slicer.util.getNode(pattern="FA")
  417. logic = labkeySlicerPythonExtensionLogic()
  418. self.assertTrue( logic.hasImageData(volumeNode) )
  419. self.delayDisplay('Test passed!')
  420. def getHomeDir():
  421. try:
  422. return os.environ['HOME']
  423. except:
  424. fhome=os.environ['HOMEDRIVE']+os.environ['HOMEPATH']
  425. return fhome