labkeyBrowser.py 19 KB

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