|
@@ -0,0 +1,228 @@
|
|
|
|
+import os
|
|
|
|
+import sys
|
|
|
|
+import unittest
|
|
|
|
+import vtk, qt, ctk, slicer
|
|
|
|
+from slicer.ScriptedLoadableModule import *
|
|
|
|
+import logging
|
|
|
|
+
|
|
|
|
+#
|
|
|
|
+# cardiacSPECT
|
|
|
|
+#
|
|
|
|
+
|
|
|
|
+class cardiacSPECT(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)
|
|
|
|
+ parent.title = "Cardiac SPECT"
|
|
|
|
+ parent.categories = ["Examples"]
|
|
|
|
+ parent.dependencies = []
|
|
|
|
+ parent.contributors = ["Andrej Studen (FMF/JSI)"] # replace with "Firstname Lastname (Org)"
|
|
|
|
+ parent.helpText = """
|
|
|
|
+ Load dynamic cardiac SPECT data to Slicer
|
|
|
|
+ """
|
|
|
|
+ parent.acknowledgementText = """
|
|
|
|
+ This module was developed within the frame of the ARRS sponsored medical
|
|
|
|
+ physics research programe to investigate quantitative measurements of cardiac
|
|
|
|
+ function using sestamibi-like tracers
|
|
|
|
+ """ # replace with organization, grant and thanks.
|
|
|
|
+ self.parent.helpText += self.getDefaultModuleDocumentationLink()
|
|
|
|
+ self.parent = parent
|
|
|
|
+
|
|
|
|
+#
|
|
|
|
+# cardiacSPECTWidget
|
|
|
|
+#
|
|
|
|
+
|
|
|
|
+class cardiacSPECTWidget(ScriptedLoadableModuleWidget):
|
|
|
|
+ """Uses ScriptedLoadableModuleWidget base class, available at:
|
|
|
|
+ https://github.com/Slicer/Slicer/blob/master/Base/Python/slicer/ScriptedLoadableModule.py
|
|
|
|
+ """
|
|
|
|
+ def __init__(self, parent):
|
|
|
|
+ ScriptedLoadableModuleWidget.__init__(self, parent)
|
|
|
|
+ self.logic=cardiacSPECTLogic()
|
|
|
|
+
|
|
|
|
+ def setup(self):
|
|
|
|
+ ScriptedLoadableModuleWidget.setup(self)
|
|
|
|
+
|
|
|
|
+ # Instantiate and connect widgets ...
|
|
|
|
+ dataButton = ctk.ctkCollapsibleButton()
|
|
|
|
+ dataButton.text = "Data"
|
|
|
|
+ self.layout.addWidget(dataButton)
|
|
|
|
+
|
|
|
|
+ # Layout within the sample collapsible button
|
|
|
|
+ dataFormLayout = qt.QFormLayout(dataButton)
|
|
|
|
+
|
|
|
|
+ dataLoadButton = qt.QPushButton("Load")
|
|
|
|
+ dataLoadButton.toolTip="Load data from DICOM"
|
|
|
|
+ dataFormLayout.addWidget(dataLoadButton)
|
|
|
|
+ dataLoadButton.connect('clicked(bool)',self.onDataLoadButtonClicked)
|
|
|
|
+
|
|
|
|
+ # Set local var as instance attribute
|
|
|
|
+ self.dataLoadButton = dataLoadButton
|
|
|
|
+
|
|
|
|
+ # Add vertical spacer
|
|
|
|
+ self.layout.addStretch(1)
|
|
|
|
+
|
|
|
|
+ dataImportButton=qt.QPushButton("Import")
|
|
|
|
+ dataImportButton.toolTip="Import time frame to VTK"
|
|
|
|
+ dataFormLayout.addWidget(dataImportButton)
|
|
|
|
+ dataImportButton.connect('clicked(bool)',self.onImportButtonClicked)
|
|
|
|
+
|
|
|
|
+ #
|
|
|
|
+ # Parameters Area
|
|
|
|
+ #
|
|
|
|
+
|
|
|
|
+ parametersCollapsibleButton = ctk.ctkCollapsibleButton()
|
|
|
|
+ parametersCollapsibleButton.text = "Parameters"
|
|
|
|
+ self.layout.addWidget(parametersCollapsibleButton)
|
|
|
|
+
|
|
|
|
+ # Layout within the dummy collapsible button
|
|
|
|
+ parametersFormLayout = qt.QFormLayout(parametersCollapsibleButton)
|
|
|
|
+
|
|
|
|
+ #
|
|
|
|
+ # check box to trigger taking screen shots for later use in tutorials
|
|
|
|
+ #
|
|
|
|
+ self.time_frame_select=qt.QLineEdit()
|
|
|
|
+ self.time_frame_select.setText("2")
|
|
|
|
+ self.time_frame_select.toolTip = "Select the time frame"
|
|
|
|
+ parametersFormLayout.addRow(self.time_frame_select)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ #
|
|
|
|
+ # Apply Button
|
|
|
|
+ #
|
|
|
|
+ self.applyButton = qt.QPushButton("Apply")
|
|
|
|
+ self.applyButton.toolTip = "Run the algorithm."
|
|
|
|
+ self.applyButton.enabled = False
|
|
|
|
+ parametersFormLayout.addRow(self.applyButton)
|
|
|
|
+
|
|
|
|
+ # connections
|
|
|
|
+ self.applyButton.connect('clicked(bool)', self.onApplyButton)
|
|
|
|
+ #self.inputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
|
|
|
|
+ #self.outputSelector.connect("currentNodeChanged(vtkMRMLNode*)", self.onSelect)
|
|
|
|
+
|
|
|
|
+ # Add vertical spacer
|
|
|
|
+ self.layout.addStretch(1)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ def cleanup(self):
|
|
|
|
+ pass
|
|
|
|
+
|
|
|
|
+ def onApplyButton(self):
|
|
|
|
+ #logic = cardiacSPECTLogic()
|
|
|
|
+ #imageThreshold = self.imageThresholdSliderWidget.value
|
|
|
|
+
|
|
|
|
+ def onDataLoadButtonClicked(self):
|
|
|
|
+ self.logic.load()
|
|
|
|
+
|
|
|
|
+ def onImportButtonClicked(self):
|
|
|
|
+ it=int(self.time_frame_select.text())
|
|
|
|
+ self.logic.setVolume(it)
|
|
|
|
+#
|
|
|
|
+# cardiacSPECTLogic
|
|
|
|
+#
|
|
|
|
+
|
|
|
|
+class cardiacSPECTLogic(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 load(self):
|
|
|
|
+ startDir=os.environ['HOME']
|
|
|
|
+ inputDir=qt.QFileDialog.getExistingDirectory(None,
|
|
|
|
+ 'Select DICOM directory',startDir)
|
|
|
|
+
|
|
|
|
+ #use another script from the same file
|
|
|
|
+ mypath=os.environ['HOME']+'/software/build/dynamicSPECT'
|
|
|
|
+ sys.path.append(mypath)
|
|
|
|
+ import parseData as pd
|
|
|
|
+
|
|
|
|
+ self.frame_data,self.frame_time=pd.read_dynamic_SPECT(inputDir)
|
|
|
|
+
|
|
|
|
+ #additional message via qt
|
|
|
|
+ qt.QMessageBox.information(
|
|
|
|
+ slicer.util.mainWindow(),
|
|
|
|
+ 'Slicer Python','Data loaded')
|
|
|
|
+
|
|
|
|
+ def setVolume(self,it):
|
|
|
|
+ #convert data from numpy.array to vtkImageData
|
|
|
|
+ #use time point it
|
|
|
|
+ frame_data=self.frame_data;
|
|
|
|
+
|
|
|
|
+ v=vtk.vtkImageData()
|
|
|
|
+ v.SetDimensions(
|
|
|
|
+ frame_data.shape[0],
|
|
|
|
+ frame_data.shape[1],
|
|
|
|
+ frame_data.shape[2])
|
|
|
|
+ v.SetOrigin(0.0*frame_data.shape[0],
|
|
|
|
+ 0.0*frame_data.shape[1],
|
|
|
|
+ 0.0*frame_data.shape[2])
|
|
|
|
+ sp = 1.0
|
|
|
|
+ v.SetSpacing(sp, sp, sp)
|
|
|
|
+ scalars = vtk.vtkShortArray()
|
|
|
|
+ for k in range(0,frame_data.shape[2]):
|
|
|
|
+ kOffset = k * frame_data.shape[1]*self.frame_data.shape[0]
|
|
|
|
+ for j in range(0,frame_data.shape[1]):
|
|
|
|
+ jOffset = j * frame_data.shape[0]
|
|
|
|
+ for i in range(0,frame_data.shape[0]):
|
|
|
|
+ offset = i + jOffset + kOffset;
|
|
|
|
+ scalars.InsertTuple1(offset,frame_data[i,j,k,it])
|
|
|
|
+ v.GetPointData().SetScalars(scalars);
|
|
|
|
+ self.v=v
|
|
|
|
+
|
|
|
|
+ nodeName='testVolume'+str(it)
|
|
|
|
+ newNode=slicer.vtkMRMLScalarVolumeNode()
|
|
|
|
+ newNode.SetName(nodeName)
|
|
|
|
+ ijkToRAS = vtk.vtkMatrix4x4()
|
|
|
|
+ ijkToRAS.Identity()
|
|
|
|
+ newNode.SetIJKToRASMatrix(ijkToRAS)
|
|
|
|
+ newNode.SetAndObserveImageData(v)
|
|
|
|
+ slicer.mrmlScene.AddNode(newNode)
|
|
|
|
+ selectionNode = slicer.app.applicationLogic().GetSelectionNode()
|
|
|
|
+ selectionNode.SetReferenceActiveVolumeID(newNode.GetID())
|
|
|
|
+ slicer.app.applicationLogic().PropagateVolumeSelection(0)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class cardiacSPECTTest(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_cardiacSPECT1()
|
|
|
|
+
|
|
|
|
+ def test_cardiacSPECT1(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
|
|
|
|
+ #
|
|
|
|
+
|
|
|
|
+ self.delayDisplay('Test passed!')
|