123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- import os
- import sys
- import unittest
- import vtk, qt, ctk, slicer
- from slicer.ScriptedLoadableModule import *
- import logging
- import parseDicom
- import vtkInterface as vi
- import fileIO
- import slicer
- import numpy as np
- import slicerNetwork
- #
- # 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 setup(self):
- ScriptedLoadableModuleWidget.setup(self)
- self.selectRemote=fileIO.remoteFileSelector()
- try:
- self.network=slicer.modules.labkeySlicerPythonExtensionWidget.network
- except:
- self.network=slicerNetwork.labkeyURIHandler()
- self.logic=cardiacSPECTLogic(self)
- self.logic.setURIHandler(self.network)
- self.selectRemote.setMaster(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)
- #pathGuess="file://"+os.environ['HOME']+"/SPECT"
- pathGuess="labkey://" + "dinamic_spect/%40files/Dinamika%20test2/SPECT_Dinamika_Rekonstruirano"
- self.dataPath=qt.QLineEdit(pathGuess)
- dataFormLayout.addRow("Data location",self.dataPath)
- self.remotePath=qt.QLineEdit();
- dataFormLayout.addRow('Remote Path', self.remotePath)
- self.remotePath.textChanged.connect(self.onRemotePathTextChanged)
- browseButton = qt.QPushButton("Browse local")
- browseButton.toolTip="Set file location"
- dataFormLayout.addRow("Select local",browseButton)
- browseButton.connect('clicked(bool)',self.onBrowseButtonClicked)
- browseRemoteButton = qt.QPushButton("Browse remote")
- browseRemoteButton.toolTip="Set remote location"
- dataFormLayout.addRow("Select remote",browseRemoteButton)
- browseRemoteButton.connect('clicked(bool)',self.onRemoteBrowseButtonClicked)
- dataLoadButton = qt.QPushButton("Load")
- dataLoadButton.toolTip="Load data from DICOM"
- dataFormLayout.addRow("Data",dataLoadButton)
- dataLoadButton.connect('clicked(bool)',self.onDataLoadButtonClicked)
- self.dataLoadButton = dataLoadButton
- # Add vertical spacer
- self.layout.addStretch(1)
- #addFrameButton=qt.QPushButton("Add Frame")
- #addFrameButton.toolTip="Add frame to VTK"
- #dataFormLayout.addWidget(addFrameButton)
- #addFrameButton.connect('clicked(bool)',self.onAddFrameButtonClicked)
- #addCTButton=qt.QPushButton("Add CT")
- #addCTButton.toolTip="Add CT to VTK"
- #dataFormLayout.addWidget(addCTButton)
- #addCTButton.connect('clicked(bool)',self.onAddCTButtonClicked)
- #
- # 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
- #
- hbox1=qt.QHBoxLayout()
- frameLabel = qt.QLabel()
- frameLabel.setText("Select frame")
- hbox1.addWidget(frameLabel)
- self.time_frame_select=qt.QSlider(qt.Qt.Horizontal)
- self.time_frame_select.valueChanged.connect(self.onTimeFrameSelect)
- #self.time_frame_select.connect('valueChanged()', self.onTimeFrameSelect)
- self.time_frame_select.setMinimum(0)
- self.time_frame_select.setMaximum(0)
- self.time_frame_select.setValue(0)
- self.time_frame_select.setTickPosition(qt.QSlider.TicksBelow)
- self.time_frame_select.setTickInterval(5)
- self.time_frame_select.toolTip = "Select the time frame"
- hbox1.addWidget(self.time_frame_select)
- parametersFormLayout.addRow(hbox1)
- hbox2 = qt.QHBoxLayout()
- meanROILabel = qt.QLabel()
- meanROILabel.setText("MeanROI")
- hbox2.addWidget(meanROILabel)
- self.meanROIVolume = qt.QLineEdit()
- self.meanROIVolume.setText("testVolume15")
- hbox2.addWidget(self.meanROIVolume)
- self.meanROISegment = qt.QLineEdit()
- self.meanROISegment.setText("Segment_1")
- hbox2.addWidget(self.meanROISegment)
- computeMeanROI = qt.QPushButton("Compute mean ROI")
- computeMeanROI.connect('clicked(bool)',self.onComputeMeanROIClicked)
- hbox2.addWidget(computeMeanROI)
- self.meanROIResult = qt.QLineEdit()
- self.meanROIResult.setText("0")
- hbox2.addWidget(self.meanROIResult)
- parametersFormLayout.addRow(hbox2)
- #row 3
- hbox3 = qt.QHBoxLayout()
- drawTimePlot=qt.QPushButton("Draw ROI time plot")
- drawTimePlot.connect('clicked(bool)',self.onDrawTimePlotClicked)
- hbox3.addWidget(drawTimePlot)
- parametersFormLayout.addRow(hbox3)
- #dataFormLayout.addWidget(hbox)
- #row 4
- hbox4 = qt.QHBoxLayout()
- countSegments=qt.QPushButton("Count segmentation segments")
- countSegments.connect('clicked(bool)',self.onCountSegmentsClicked)
- hbox4.addWidget(countSegments)
- self.countSegmentsDisplay=qt.QLineEdit()
- self.countSegmentsDisplay.setText("0")
- hbox4.addWidget(self.countSegmentsDisplay)
- parametersFormLayout.addRow(hbox4)
- #
- # 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)
- self.resetPosition=1
- def cleanup(self):
- pass
- def onApplyButton(self):
- pass
- #logic = cardiacSPECTLogic()
- #imageThreshold = self.imageThresholdSliderWidget.value
- def onBrowseButtonClicked(self):
- startDir=self.dataPath.text
- inputDir=qt.QFileDialog.getExistingDirectory(None,
- 'Select DICOM directory',startDir)
- self.dataPath.setText("file://"+inputDir)
- def onRemoteBrowseButtonClicked(self):
- self.selectRemote.show()
- def onDataLoadButtonClicked(self):
- self.logic.loadData(self)
- def onRemotePathTextChanged(self,str):
- self.dataPath.setText('labkey://'+str)
- def onTimeFrameSelect(self):
- it=self.time_frame_select.value
- selectionNode = slicer.app.applicationLogic().GetSelectionNode()
- print("Propagating CT volume")
- node=slicer.mrmlScene.GetFirstNodeByName("testCT")
- selectionNode.SetReferenceActiveVolumeID(node.GetID())
- if self.resetPosition==1:
- self.resetPosition=0
- slicer.app.applicationLogic().PropagateVolumeSelection(1)
- else:
- slicer.app.applicationLogic().PropagateVolumeSelection(0)
- print("Propagating SPECT volume")
- nodeName='testVolume'+str(it)
- node=slicer.mrmlScene.GetFirstNodeByName(nodeName)
- selectionNode.SetSecondaryVolumeID(node.GetID())
- slicer.app.applicationLogic().PropagateForegroundVolumeSelection(0)
- node.GetDisplayNode().SetAndObserveColorNodeID('vtkMRMLColorTableNodeRed')
- lm = slicer.app.layoutManager()
- sID=['Red','Yellow','Green']
- for s in sID:
- sliceLogic = lm.sliceWidget(s).sliceLogic()
- compositeNode = sliceLogic.GetSliceCompositeNode()
- compositeNode.SetForegroundOpacity(0.5)
- #make sure the viewer is matched to the volume
- print("Done")
- #to access sliceLogic (slice control) use
- #lcol=slicer.app.layoutManager().mrmlSliceLogics() (vtkCollection)
- #vtkMRMLSliceLogic are named by colors (Red,Green,Blue)
- def onComputeMeanROIClicked(self):
- s=self.logic.meanROI(self.meanROIVolume.text,self.meanROISegment.text)
- self.meanROIResult.setText(str(s))
- def onDrawTimePlotClicked(self):
- n=self.time_frame_select.maximum+1
- ft=self.logic.frame_time
- #find number of segments
- ns = self.logic.countSegments()
- #add the chart node
- cn = slicer.mrmlScene.AddNode(slicer.vtkMRMLChartNode())
- for j in range(0,ns):
- #add node for data
- dn = slicer.mrmlScene.AddNode(slicer.vtkMRMLDoubleArrayNode())
- dn.SetName(self.logic.getSegmentName(j))
- a = dn.GetArray()
- a.SetNumberOfTuples(n)
- dt=0;
- t0=0;
- for i in range(0,n):
- vol="testVolume"+str(i)
- fx=ft[i]
- fy=self.logic.meanROI(vol,j)
- dt=2*ft[i]-t0
- t0+=dt
- a.SetComponent(i, 0, fx)
- a.SetComponent(i, 1, fy/dt)
- a.SetComponent(i, 2, 0)
- print("[{0} at {1:.2f}:{2:.2f}]".format(vol,fx,fy))
- #fish the number of the segment
- cn.AddArray(self.logic.getSegmentName(j), dn.GetID())
- cn.SetProperty('default', 'title', 'ROI time plot')
- cn.SetProperty('default', 'xAxisLabel', 'time [ms]')
- cn.SetProperty('default', 'yAxisLabel', 'Activity (arb)')
- #update the chart node
- cvns = slicer.mrmlScene.GetNodesByClass('vtkMRMLChartViewNode')
- if cvns.GetNumberOfItems() == 0:
- cvn = slicer.mrmlScene.AddNode(slicer.vtkMRMLChartViewNode())
- else:
- cvn = cvns.GetItemAsObject(0)
- cvn.SetChartNodeID(cn.GetID())
- def onCountSegmentsClicked(self):
- self.countSegmentsDisplay.setText(self.logic.countSegments())
- #def onAddFrameButtonClicked(self):
- # it=int(self.time_frame_select.text)
- # self.logic.addFrame(it)
- # def onAddCTButtonClicked(self):
- # self.logic.addCT()
- #
- #
- # 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 __init__(self,parent):
- ScriptedLoadableModuleLogic.__init__(self, parent)
- self.pd=parseDicom.parseDicomLogic(self)
- def setURIHandler(self,net):
- self.net=net
- self.pd.setURIHandler(net)
- def loadData(self,widget):
- inputDir=str(widget.dataPath.text)
- self.frame_data, self.frame_time, self.frame_origin, \
- self.frame_pixel_size, self.frame_orientation=self.pd.read_dynamic_SPECT(inputDir)
- self.ct_data,self.ct_origin,self.ct_pixel_size, \
- self.ct_orientation=self.pd.read_CT(inputDir)
- self.ct_orientation=vi.completeOrientation(self.ct_orientation)
- self.frame_orientation=vi.completeOrientation(self.frame_orientation)
- self.addCT()
- self.addFrames()
- widget.time_frame_select.setMaximum(self.frame_data.shape[3]-1)
- #additional message via qt
- qt.QMessageBox.information(
- slicer.util.mainWindow(),
- 'Slicer Python','Data loaded')
- def addNode(self,nodeName,v, lpsOrigin, pixel_size, lpsOrientation, dataType):
- # if dataType=0 it is CT data, which gets propagated to background an is
- #used to fit the view field dimensions
- # if dataType=1, it is SPECT data, which gets propagated to foreground
- #and is not fit; keeping window set from CT
- #nodeName='testVolume'+str(it)
- newNode=slicer.vtkMRMLScalarVolumeNode()
- newNode.SetName(nodeName)
- #pixel_size=[0,0,0]
- #pixel_size=v.GetSpacing()
- #print(pixel_size)
- #origin=[0,0,0]
- #origin=v.GetOrigin()
- v.SetOrigin([0,0,0])
- v.SetSpacing([1,1,1])
- ijkToRAS = vtk.vtkMatrix4x4()
- #think how to do this with image orientation
- rasOrientation=[-lpsOrientation[i] if (i%3 < 2) else lpsOrientation[i]
- for i in range(0,len(lpsOrientation))]
- rasOrigin=[-lpsOrigin[i] if (i%3<2) else lpsOrigin[i] for i in range(0,len(lpsOrigin))]
- for i in range(0,3):
- for j in range(0,3):
- ijkToRAS.SetElement(i,j,pixel_size[i]*rasOrientation[3*j+i])
- ijkToRAS.SetElement(i,3,rasOrigin[i])
- newNode.SetIJKToRASMatrix(ijkToRAS)
- newNode.SetAndObserveImageData(v)
- slicer.mrmlScene.AddNode(newNode)
- def addFrames(self):
- #convert data from numpy.array to vtkImageData
- #use time point it
- print "NFrames: {}".format(self.frame_data.shape[3])
- for it in range(0,self.frame_data.shape[3]):
- frame_data=self.frame_data[:,:,:,it];
- nodeName='testVolume'+str(it)
- self.addNode(nodeName,
- vi.numpyToVTK(frame_data,frame_data.shape),
- self.frame_origin,
- self.frame_pixel_size,
- self.frame_orientation,1)
- def addCT(self):
- nodeName='testCT'
- self.addNode(nodeName,
- #vi.numpyToVTK3D(self.ct_data,
- # self.ct_origin,self.ct_pixel_size),
- vi.numpyToVTK(self.ct_data,self.ct_data.shape),
- self.ct_origin,self.ct_pixel_size,
- self.ct_orientation,0)
- def meanROI(self, volName1, i):
- s=0
- #get the segmentation mask
- fNode=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode").GetItemAsObject(0)
- print "Found segmentation node: {}".format(fNode.GetName())
- segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
- #no python bindings for vtkSegmentation
- #if segNode.GetSegmentation().GetNumberOfSegments()==0 :
- # print("No segments available")
- # return 0
- #edit here to change for more segments
- segment=segNode.GetSegmentation().GetNthSegmentID(int(i))
- mask = segNode.GetBinaryLabelmapRepresentation(segment)
- if mask==None:
- print("Segment {} not found".format(segment))
- return s
- print "Got mask for segment {}".format(segment)
- #get mask at (x,y,z)
- #mask.GetPointData().GetScalars().GetTuple1(mask.FindPoint([x,y,z]))
- #get the image data
- dataNode=slicer.mrmlScene.GetFirstNodeByName(volName1)
- dataImage=dataNode.GetImageData()
- # use IJK2RAS to get global coordinates
- ijkToRas = vtk.vtkMatrix4x4()
- dataNode.GetIJKToRASMatrix(ijkToRas)
- #iterate over volume pixelData
- n=dataImage.GetNumberOfPoints()
- extent=mask.GetExtent()
- fM=vtk.vtkMatrix4x4()
- mask.GetWorldToImageMatrix(fM)
- for i in range(0,n):
- #get global coordinates of point i
- [ix,iy,iz]=dataImage.GetPoint(i)
- position_ijk=[ix, iy, iz, 1]
- #ras are global coordinates (in mm)
- position_ras=ijkToRas.MultiplyPoint(position_ijk)
- fpos=[int(np.round(x)) for x in fM.MultiplyPoint(position_ras)]
- outOfRange=False
- for k in range(0,3):
- if fpos[k]<extent[2*k] or fpos[k]>extent[2*k+1]:
- outOfRange=True
- break;
- if outOfRange:
- continue
- #find point in mask with the same global coordinates
- maskValue=mask.GetPointData().GetScalars().GetTuple1(mask.ComputePointId(fpos[0:3]))
- if maskValue == 0:
- continue
- #use maskValue to project ROI data
- s+=maskValue*dataImage.GetPointData().GetScalars().GetTuple1(i)
- return s/n
- def countSegments(self):
- segNodeList=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode")
- if segNodeList.GetNumberOfItems()==0:
- return 0
- fNode=segNodeList.GetItemAsObject(0)
- segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
- if fNode==None:
- return 0
- return segNode.GetSegmentation().GetNumberOfSegments()
- def getSegmentName(self,i):
- segNodeList=slicer.mrmlScene.GetNodesByClass("vtkMRMLSegmentationNode")
- if segNodeList.GetNumberOfItems()==0:
- return "NONE"
- fNode=segNodeList.GetItemAsObject(0)
- segNode=slicer.vtkMRMLSegmentationNode.SafeDownCast(fNode)
- if fNode==None:
- return "NONE"
- return segNode.GetSegmentation().GetSegment(segNode.GetSegmentation().GetNthSegmentID(i)).GetName()
- 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!')
|