|
@@ -1,318 +0,0 @@
|
|
|
-import os
|
|
|
-import unittest
|
|
|
-import vtk, qt, ctk, slicer
|
|
|
-from slicer.ScriptedLoadableModule import *
|
|
|
-import logging
|
|
|
-import slicerNetwork
|
|
|
-import json
|
|
|
-import loadPatient
|
|
|
-
|
|
|
-#
|
|
|
-# dataExplorer
|
|
|
-#
|
|
|
-
|
|
|
-class dataExplorer(ScriptedLoadableModule):
|
|
|
- """Uses ScriptedLoadableModule base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def __init__(self, parent):
|
|
|
- ScriptedLoadableModule.__init__(self, parent)
|
|
|
- self.parent.title = "dataExplorer" # TODO make this more human readable by adding spaces
|
|
|
- self.parent.categories = ["EMBRACE"]
|
|
|
- self.parent.dependencies = []
|
|
|
- self.parent.contributors = ["Andrej Studen (University of Ljubljana)"] # replace with "Firstname Lastname (Organization)"
|
|
|
- self.parent.helpText = """
|
|
|
-This is an example of scripted loadable module bundled in an extension.
|
|
|
-It performs a simple thresholding on the input volume and optionally captures a screenshot.
|
|
|
-"""
|
|
|
- self.parent.helpText += self.getDefaultModuleDocumentationLink()
|
|
|
- self.parent.acknowledgementText = """
|
|
|
-This extension developed within Medical Physics research programe of ARRS
|
|
|
-""" # replace with organization, grant and thanks.
|
|
|
-
|
|
|
-#
|
|
|
-# dataExplorerWidget
|
|
|
-#
|
|
|
-
|
|
|
-class dataExplorerWidget(ScriptedLoadableModuleWidget):
|
|
|
- """Uses ScriptedLoadableModuleWidget base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def setup(self):
|
|
|
- ScriptedLoadableModuleWidget.setup(self)
|
|
|
- self.loadPatientLogic=loadPatient.loadPatientLogic(self)
|
|
|
- # Instantiate and connect widgets ...
|
|
|
-
|
|
|
- #
|
|
|
- # Parameters Area
|
|
|
- #
|
|
|
- basePath=os.path.join(os.path.expanduser('~'),'.EMBRACE')
|
|
|
- #'D:\\Sw\\src\\EMBRACE'
|
|
|
- netConfig=os.path.join(basePath,'onko-nix.json')
|
|
|
- self.sNet=slicerNetwork.labkeyURIHandler()
|
|
|
- self.sNet.parseConfig(netConfig)
|
|
|
- self.sNet.initRemote()
|
|
|
-
|
|
|
- self.loadPatientLogic.setURIHandler(self.sNet)
|
|
|
- self.project="EMBRACE/Studija"
|
|
|
- self.dataset="ImagingVisitsManaged"
|
|
|
-
|
|
|
-
|
|
|
- datasetCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
- datasetCollapsibleButton.text = "Dataset"
|
|
|
- self.layout.addWidget(datasetCollapsibleButton)
|
|
|
- # Layout within the dummy collapsible button
|
|
|
- datasetFormLayout = qt.QFormLayout(datasetCollapsibleButton)
|
|
|
-
|
|
|
- self.datasetButton=qt.QPushButton("Load")
|
|
|
- self.datasetButton.connect('clicked(bool)',self.onDatasetLoadButtonClicked)
|
|
|
- datasetFormLayout.addRow("Data:",self.datasetButton)
|
|
|
-
|
|
|
- self.patientId=qt.QLineEdit("LJU004")
|
|
|
- datasetFormLayout.addRow("Patient ID:",self.patientId)
|
|
|
-
|
|
|
- loadCTButton=qt.QPushButton("CT")
|
|
|
- loadCTButton.clicked.connect(self.onLoadCTButtonClicked)
|
|
|
- datasetFormLayout.addRow("Load:",loadCTButton)
|
|
|
-
|
|
|
- loadCTRSButton=qt.QPushButton("CT-RS")
|
|
|
- loadCTRSButton.clicked.connect(self.onLoadCTRSButtonClicked)
|
|
|
- datasetFormLayout.addRow("Load:",loadCTRSButton)
|
|
|
-
|
|
|
- loadDMRButton=qt.QPushButton("DMR")
|
|
|
- loadDMRButton.clicked.connect(self.onLoadDMRButtonClicked)
|
|
|
- datasetFormLayout.addRow("Load:",loadDMRButton)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- dataCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
- dataCollapsibleButton.text = "Data"
|
|
|
- self.layout.addWidget(dataCollapsibleButton)
|
|
|
- # Layout within the dummy collapsible button
|
|
|
- dataFormLayout = qt.QVBoxLayout(dataCollapsibleButton)
|
|
|
-
|
|
|
- self.data=qt.QTableWidget(3,3)
|
|
|
- dataFormLayout.addWidget(self.data)
|
|
|
-
|
|
|
- patientsCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
- patientsCollapsibleButton.text = "Patients"
|
|
|
- self.layout.addWidget(patientsCollapsibleButton)
|
|
|
- # Layout within the dummy collapsible button
|
|
|
- self.patientsFormLayout = qt.QVBoxLayout(patientsCollapsibleButton)
|
|
|
- self.signalMapper=qt.QSignalMapper()
|
|
|
- self.signalMapper.connect(self.signalMapper, qt.SIGNAL("mapped(const QString &)"), self.onPatientButtonClicked)
|
|
|
-
|
|
|
-
|
|
|
- def cleanup(self):
|
|
|
- pass
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def onDatasetLoadButtonClicked(self):
|
|
|
-
|
|
|
- ds=self.sNet.loadDataset(self.project,self.dataset)
|
|
|
- #loaded a JSON object -> convert to suitable display
|
|
|
- columns=ds['columnModel']
|
|
|
- self.data.setColumnCount(len(columns))
|
|
|
- self.data.setRowCount(len(ds['rows']))
|
|
|
- columnNames=[f['header'] for f in columns]
|
|
|
- self.data.setHorizontalHeaderLabels(columnNames)
|
|
|
- columnID=[f['dataIndex'] for f in columns]
|
|
|
- insertRowID=0
|
|
|
- for row in ds['rows']:
|
|
|
- insertColumnID=0
|
|
|
- for c in columnID:
|
|
|
- item=qt.QTableWidgetItem(str(row[c]))
|
|
|
- self.data.setItem(insertRowID,insertColumnID,item)
|
|
|
- insertColumnID+=1
|
|
|
- insertRowID+=1
|
|
|
-
|
|
|
- #clear patient list
|
|
|
- while self.patientsFormLayout.count():
|
|
|
- child=self.patientsFormLayout.takeAt(0)
|
|
|
- if child.widget():
|
|
|
- self.signalMapper.removeMapping(child.widget())
|
|
|
- child.widget().deleteLater()
|
|
|
-
|
|
|
- #populate patient list
|
|
|
- patientList=[f['EMBRACE_ID'] for f in ds['rows']]
|
|
|
- #remove duplicates
|
|
|
- patientSet=set(patientList)
|
|
|
- for p in patientSet:
|
|
|
- patientButton=qt.QPushButton(p)
|
|
|
- patientButton.connect(patientButton, qt.SIGNAL("clicked()"),
|
|
|
- self.signalMapper, qt.SLOT("map()"))
|
|
|
- self.patientsFormLayout.addWidget(patientButton)
|
|
|
- self.signalMapper.setMapping(patientButton,p)
|
|
|
-
|
|
|
- def onPatientButtonClicked(self, label):
|
|
|
- print "onPatientButtonClicked() for {}".format(label)
|
|
|
- self.loadPatientLogic.load(label)
|
|
|
-
|
|
|
- def onLoadCTButtonClicked(self):
|
|
|
- self.CT=self.loadPatientLogic.loadCT(self.patientId.text)
|
|
|
- self.CT[0]['node'].SetName(self.patientId.text+"_CT")
|
|
|
-
|
|
|
- def onLoadCTRSButtonClicked(self):
|
|
|
- self.CTRS=self.loadPatientLogic.loadCTRS(self.patientId.text)
|
|
|
-
|
|
|
- def onLoadDMRButtonClicked(self):
|
|
|
- self.DMR=self.loadPatientLogic.loadDMR(self.patientId.text)
|
|
|
- self.DMR[0]['node'].SetName(self.patientId.text+"_DMR")
|
|
|
-
|
|
|
-#
|
|
|
-# dataExplorerLogic
|
|
|
-#
|
|
|
-
|
|
|
-class dataExplorerLogic(ScriptedLoadableModuleLogic):
|
|
|
- """This class should implement all the actual
|
|
|
- computation done by your module. The interface
|
|
|
- should be such that other python code can import
|
|
|
- this class and make use of the functionality without
|
|
|
- requiring an instance of the Widget.
|
|
|
- Uses ScriptedLoadableModuleLogic base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def hasImageData(self,volumeNode):
|
|
|
- """This is an example logic method that
|
|
|
- returns true if the passed in volume
|
|
|
- node has valid image data
|
|
|
- """
|
|
|
- if not volumeNode:
|
|
|
- logging.debug('hasImageData failed: no volume node')
|
|
|
- return False
|
|
|
- if volumeNode.GetImageData() is None:
|
|
|
- logging.debug('hasImageData failed: no image data in volume node')
|
|
|
- return False
|
|
|
- return True
|
|
|
-
|
|
|
- def isValidInputOutputData(self, inputVolumeNode, outputVolumeNode):
|
|
|
- """Validates if the output is not the same as input
|
|
|
- """
|
|
|
- if not inputVolumeNode:
|
|
|
- logging.debug('isValidInputOutputData failed: no input volume node defined')
|
|
|
- return False
|
|
|
- if not outputVolumeNode:
|
|
|
- logging.debug('isValidInputOutputData failed: no output volume node defined')
|
|
|
- return False
|
|
|
- if inputVolumeNode.GetID()==outputVolumeNode.GetID():
|
|
|
- logging.debug('isValidInputOutputData failed: input and output volume is the same. Create a new volume for output to avoid this error.')
|
|
|
- return False
|
|
|
- return True
|
|
|
-
|
|
|
- def takeScreenshot(self,name,description,type=-1):
|
|
|
- # show the message even if not taking a screen shot
|
|
|
- slicer.util.delayDisplay('Take screenshot: '+description+'.\nResult is available in the Annotations module.', 3000)
|
|
|
-
|
|
|
- lm = slicer.app.layoutManager()
|
|
|
- # switch on the type to get the requested window
|
|
|
- widget = 0
|
|
|
- if type == slicer.qMRMLScreenShotDialog.FullLayout:
|
|
|
- # full layout
|
|
|
- widget = lm.viewport()
|
|
|
- elif type == slicer.qMRMLScreenShotDialog.ThreeD:
|
|
|
- # just the 3D window
|
|
|
- widget = lm.threeDWidget(0).threeDView()
|
|
|
- elif type == slicer.qMRMLScreenShotDialog.Red:
|
|
|
- # red slice window
|
|
|
- widget = lm.sliceWidget("Red")
|
|
|
- elif type == slicer.qMRMLScreenShotDialog.Yellow:
|
|
|
- # yellow slice window
|
|
|
- widget = lm.sliceWidget("Yellow")
|
|
|
- elif type == slicer.qMRMLScreenShotDialog.Green:
|
|
|
- # green slice window
|
|
|
- widget = lm.sliceWidget("Green")
|
|
|
- else:
|
|
|
- # default to using the full window
|
|
|
- widget = slicer.util.mainWindow()
|
|
|
- # reset the type so that the node is set correctly
|
|
|
- type = slicer.qMRMLScreenShotDialog.FullLayout
|
|
|
-
|
|
|
- # grab and convert to vtk image data
|
|
|
- qimage = ctk.ctkWidgetsUtils.grabWidget(widget)
|
|
|
- imageData = vtk.vtkImageData()
|
|
|
- slicer.qMRMLUtils().qImageToVtkImageData(qimage,imageData)
|
|
|
-
|
|
|
- annotationLogic = slicer.modules.annotations.logic()
|
|
|
- annotationLogic.CreateSnapShot(name, description, type, 1, imageData)
|
|
|
-
|
|
|
- def run(self, inputVolume, outputVolume, imageThreshold, enableScreenshots=0):
|
|
|
- """
|
|
|
- Run the actual algorithm
|
|
|
- """
|
|
|
-
|
|
|
- if not self.isValidInputOutputData(inputVolume, outputVolume):
|
|
|
- slicer.util.errorDisplay('Input volume is the same as output volume. Choose a different output volume.')
|
|
|
- return False
|
|
|
-
|
|
|
- logging.info('Processing started')
|
|
|
-
|
|
|
- # Compute the thresholded output volume using the Threshold Scalar Volume CLI module
|
|
|
- cliParams = {'InputVolume': inputVolume.GetID(), 'OutputVolume': outputVolume.GetID(), 'ThresholdValue' : imageThreshold, 'ThresholdType' : 'Above'}
|
|
|
- cliNode = slicer.cli.run(slicer.modules.thresholdscalarvolume, None, cliParams, wait_for_completion=True)
|
|
|
-
|
|
|
- # Capture screenshot
|
|
|
- if enableScreenshots:
|
|
|
- self.takeScreenshot('dataExplorerTest-Start','MyScreenshot',-1)
|
|
|
-
|
|
|
- logging.info('Processing completed')
|
|
|
-
|
|
|
- return True
|
|
|
-
|
|
|
-
|
|
|
-class dataExplorerTest(ScriptedLoadableModuleTest):
|
|
|
- """
|
|
|
- This is the test case for your scripted module.
|
|
|
- Uses ScriptedLoadableModuleTest base class, available at:
|
|
|
- https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
- """
|
|
|
-
|
|
|
- def setUp(self):
|
|
|
- """ Do whatever is needed to reset the state - typically a scene clear will be enough.
|
|
|
- """
|
|
|
- slicer.mrmlScene.Clear(0)
|
|
|
-
|
|
|
- def runTest(self):
|
|
|
- """Run as few or as many tests as needed here.
|
|
|
- """
|
|
|
- self.setUp()
|
|
|
- self.test_dataExplorer1()
|
|
|
-
|
|
|
- def test_dataExplorer1(self):
|
|
|
- """ Ideally you should have several levels of tests. At the lowest level
|
|
|
- tests should exercise the functionality of the logic with different inputs
|
|
|
- (both valid and invalid). At higher levels your tests should emulate the
|
|
|
- way the user would interact with your code and confirm that it still works
|
|
|
- the way you intended.
|
|
|
- One of the most important features of the tests is that it should alert other
|
|
|
- developers when their changes will have an impact on the behavior of your
|
|
|
- module. For example, if a developer removes a feature that you depend on,
|
|
|
- your test should break so they know that the feature is needed.
|
|
|
- """
|
|
|
-
|
|
|
- self.delayDisplay("Starting the test")
|
|
|
- #
|
|
|
- # first, get some data
|
|
|
- #
|
|
|
- import urllib
|
|
|
- downloads = (
|
|
|
- ('http://slicer.kitware.com/midas3/download?items=5767', 'FA.nrrd', slicer.util.loadVolume),
|
|
|
- )
|
|
|
-
|
|
|
- for url,name,loader in downloads:
|
|
|
- filePath = slicer.app.temporaryPath + '/' + name
|
|
|
- if not os.path.exists(filePath) or os.stat(filePath).st_size == 0:
|
|
|
- logging.info('Requesting download %s from %s...\n' % (name, url))
|
|
|
- urllib.urlretrieve(url, filePath)
|
|
|
- if loader:
|
|
|
- logging.info('Loading %s...' % (name,))
|
|
|
- loader(filePath)
|
|
|
- self.delayDisplay('Finished with download and loading')
|
|
|
-
|
|
|
- volumeNode = slicer.util.getNode(pattern="FA")
|
|
|
- logic = dataExplorerLogic()
|
|
|
- self.assertIsNotNone( logic.hasImageData(volumeNode) )
|
|
|
- self.delayDisplay('Test passed!')
|