Plotting the signals

Hi Guys,

Is it possible to plot the signals in Slicer? I had collected the signal with different length at different times. I want to show these signals in one PlotView. I’m not sure whether PlotView support ‘subplot’ like in matplotlib. Or is there any other advices?
Thanks a lot!

I did something like this in the extension “SlicerArduinoController”.

You can give a look at this class:

Basically I receive the data on the serial port and then I plot the samples.

HTH

Paolo

Hi Paolo,

It’s a nice work! I wonder whether the plotview can show the signals from different channels. In your work, the signal from one channel can be plotted in the plotview. However, if there are signals from different channels, how to show these signals and respectively in one plotview?
Thanks!

Wenpeng

I think it is sufficient to have a table with a column for each channel you want to plot (in my case was just a channel).

The plot is linked to a table, so if you edit the table the plot will be updated accordingly.

Agree. In my case, the signal from different channels should be shown separately, otherwise they will overlap. So I had to add an offset to the amplitude of different signal. The result is shown in screenshot. I hope that the signal from each channel can be shown in one plotchart. However, plotview can only show one plotchart at the same time?
So is there any other advices?
Thanks!

One plot view can only show one chart at a time but you can show any number of plot views in a custom view layout. You can hide the controller bars to keep all the plots close together.

So the layout should be created dynamically, because the number of channels is unknown until the signal files are loaded. Therefore, the number of plot views is a parameter to create the customized layout, which is difficult to realize in this way (Script repository — 3D Slicer documentation).

No problem at all, you can change the layout anytime based on the number of channels you want to display. All you need is to generate the text description based on the number of channels, then update it in the layout node by calling SetLayoutDescription method.

Hi Lassoan,

Slicer crashed, when I try to show the signal from different channel separately.
Here is the code:

for channel in range(num):
        tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode","Table{}".format(channel))
        table=tableNode.GetTable()
        table.Initialize()

        arrX=vtk.vtkFloatArray()
        arrX.SetName("Time(ms)")
        table.AddColumn(arrX)

        arrY=vtk.vtkFloatArray()
        name="{0} mm".format(self.logic.Data[channel].dist)
        arrY.SetName(name)
        table.AddColumn(arrY)

        table.SetNumberOfRows(maxNumOfPoints)

        for j in range(maxNumOfPoints):
            table.SetValue(j,0,j)
            if j<self.logic.Data[channel].rawSignal.shape[0]:
                table.SetValue(j,1,self.logic.Data[channel].rawSignal[j])
            else:
                table.SetValue(j,1,0)

        table.Modified()

        name="{0} mm".format(self.logic.Data[channel].dist)
        plotSeriesNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode","Signal@{0}".format(name))
        plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
        plotSeriesNode.SetXColumnName("Time(ms)")
        plotSeriesNode.SetYColumnName(name)
        plotSeriesNode.SetPlotType(slicer.vtkMRMLPlotSeriesNode.PlotTypeLine)
        plotSeriesNode.SetUniqueColor()

        plotChartNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode","Chart@{0}".format(name))
        plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
        plotChartNode.SetXAxisTitle('Time(ms)')
        plotChartNode.SetYAxisTitle('Voltage(uV)')
        plotChartNode.SetYAxisRangeAuto(True)
        plotChartNode.SetXAxisRangeAuto(True)

        plotWidget=self.layoutManager.plotWidget(channel)
        plotViewNode=plotWidget.mrmlPlotViewNode()
        plotViewNode.SetPlotChartNodeID(plotChartNode.GetID())

Could you please provide an example that I can run as is?

Specify num, replace self.logic.Data[channel].rawSignal[j] with some hardcoded or random values, etc.

Here are two functions implementation( PlotSignal and SetLayout)
Slicer version: 4.11.20200930 r29402
OS: unbuntu: 16.04

def PlotSignal(self):
 
    import math
    num=10
    self.SetLayout(num)

    maxNumOfPoints=100

    self.layoutManager=slicer.app.layoutManager()
    if self.layoutManager.plotViewCount!=num:
        print("Plot view number({}) is not equal to signal channel number({})".format(self.layoutManager.plotViewCount,num))
        return

    for channel in range(num):
        tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode","Table{}".format(channel))
        table=tableNode.GetTable()
        table.Initialize()

        arrX=vtk.vtkFloatArray()
        arrX.SetName("Time(ms)")
        table.AddColumn(arrX)

        arrY=vtk.vtkFloatArray()
        name="{0} mm".format(channel)
        arrY.SetName(name)
        table.AddColumn(arrY)

        table.SetNumberOfRows(maxNumOfPoints)

        for j in range(maxNumOfPoints):
            print(j)
            table.SetValue(j,0,j)
            table.SetValue(j,1,math.sin(j))

        table.Modified()

        #Create two plot series nodes
        name="{0} mm".format(channel)
        plotSeriesNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode","Signal@{0}".format(name))
        plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
        plotSeriesNode.SetXColumnName("Time(ms)")
        plotSeriesNode.SetYColumnName(name)
        plotSeriesNode.SetPlotType(slicer.vtkMRMLPlotSeriesNode.PlotTypeLine)
        plotSeriesNode.SetUniqueColor()

        plotChartNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode","Chart@{0}".format(name))
        plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
        plotChartNode.SetXAxisTitle('Time(ms)')
        plotChartNode.SetYAxisTitle('Voltage(uV)')
        plotChartNode.SetYAxisRangeAuto(True)
        plotChartNode.SetXAxisRangeAuto(True)

        plotWidget=self.layoutManager.plotWidget(channel)
        plotViewNode=plotWidget.mrmlPlotViewNode()
        plotViewNode.SetPlotChartNodeID(plotChartNode.GetID())
def SetLayout(self,nChannels):
    if self.setLayout is False:
        customLayout=(
        "<layout type=\"horizontal\" split=\"true\">" 
            "<item>"
                "<layout type=\"vertical\" split=\"true\">"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Red\">"
                            "<property name=\"orientation\" action=\"default\">Axis</property>"
                            "<property name=\"viewlabel\" action=\"default\">R</property>"
                            "<property name=\"viewcolor\" action=\"default\">#F34A33</property>"
                        "</view>"
                    "</item>"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Yellow\">"
                            "<property name=\"orientation\" action=\"default\">Sagittal</property>"
                            "<property name=\"viewlabel\" action=\"default\">Y</property>"
                            "<property name=\"viewcolor\" action=\"default\">#EDD54C</property>"
                        "</view>"
                    "</item>"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Green\">"
                            "<property name=\"orientation\" action=\"default\">Coronal</property>"
                            "<property name=\"viewlabel\" action=\"default\">G</property>"
                            "<property name=\"viewcolor\" action=\"default\">#6EB04B</property>"
                        "</view>"
                    "</item>"
                "</layout>"
            "</item>"
            "<item>"
                "<view class=\"vtkMRMLViewNode\" singletontag=\"1\">"
                    "<property name=\"viewlabel\" action=\"default\">1</property>"
                "</view>"
            "</item>"
            "<item>"
                "<layout type=\"vertical\" split=\"true\">")
        
        for i in range(nChannels):
            customLayout+='<item><view class="vtkMRMLPlotViewNode" singletontag="SignalView'+'-{}"'.format(i)+'>'
            customLayout+='<property name="viewlabel" action="default">'+'P{}'.format(i+1)+'</property>'
            customLayout+='</view></item>'

        customLayout+='</layout></item></layout>'

        '''
        customLayout=(
        "<layout type=\"horizontal\" split=\"true\">" 
            "<item>"
                "<layout type=\"vertical\" split=\"true\">"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Red\">"
                            "<property name=\"orientation\" action=\"default\">Axis</property>"
                            "<property name=\"viewlabel\" action=\"default\">R</property>"
                            "<property name=\"viewcolor\" action=\"default\">#F34A33</property>"
                        "</view>"
                    "</item>"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Yellow\">"
                            "<property name=\"orientation\" action=\"default\">Sagittal</property>"
                            "<property name=\"viewlabel\" action=\"default\">Y</property>"
                            "<property name=\"viewcolor\" action=\"default\">#EDD54C</property>"
                        "</view>"
                    "</item>"
                    "<item>"
                        "<view class=\"vtkMRMLSliceNode\" singletontag=\"Green\">"
                            "<property name=\"orientation\" action=\"default\">Coronal</property>"
                            "<property name=\"viewlabel\" action=\"default\">G</property>"
                            "<property name=\"viewcolor\" action=\"default\">#6EB04B</property>"
                        "</view>"
                    "</item>"
                "</layout>"
            "</item>"
            "<item>"
                "<view class=\"vtkMRMLViewNode\" singletontag=\"1\">"
                    "<property name=\"viewlabel\" action=\"default\">1</property>"
                "</view>"
            "</item>"
            "<item>"
                "<layout type=\"horizontal\" split=\"true\">"
                    "<view class=\"vtkMRMLPlotViewNode\" singletontag=\"MERView\">"
                        "<property name=\"viewlabel\" action=\"default\">2</property>"
                    "</view>"
                "</layout>"
            "</item>"            
        "</layout>")
        '''

        customLayoutId=501
        layoutManager=slicer.app.layoutManager()
        if not layoutManager.layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId,customLayout):
            layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId,customLayout)
        layoutManager.setLayout(customLayoutId)

        for i in range(nChannels):
            layoutManager.plotWidget(i).plotController().setVisible(False)
1 Like

I could not reproduce any crash with the scripts above, so maybe the issue is how you fill the tables with real data. I’ve made a couple of fixes and improvement (fix order of views, fit plot to the view, adjust labeling, etc.) and this is the result:

The updated code:

def SetLayout(nChannels):
    customLayout=(
    "<layout type=\"horizontal\" split=\"true\">" 
        "<item>"
            "<layout type=\"vertical\" split=\"true\">"
                "<item>"
                    "<view class=\"vtkMRMLSliceNode\" singletontag=\"Red\">"
                        "<property name=\"orientation\" action=\"default\">Axis</property>"
                        "<property name=\"viewlabel\" action=\"default\">R</property>"
                        "<property name=\"viewcolor\" action=\"default\">#F34A33</property>"
                    "</view>"
                "</item>"
                "<item>"
                    "<view class=\"vtkMRMLSliceNode\" singletontag=\"Yellow\">"
                        "<property name=\"orientation\" action=\"default\">Sagittal</property>"
                        "<property name=\"viewlabel\" action=\"default\">Y</property>"
                        "<property name=\"viewcolor\" action=\"default\">#EDD54C</property>"
                    "</view>"
                "</item>"
                "<item>"
                    "<view class=\"vtkMRMLSliceNode\" singletontag=\"Green\">"
                        "<property name=\"orientation\" action=\"default\">Coronal</property>"
                        "<property name=\"viewlabel\" action=\"default\">G</property>"
                        "<property name=\"viewcolor\" action=\"default\">#6EB04B</property>"
                    "</view>"
                "</item>"
            "</layout>"
        "</item>"
        "<item>"
            "<view class=\"vtkMRMLViewNode\" singletontag=\"1\">"
                "<property name=\"viewlabel\" action=\"default\">1</property>"
            "</view>"
        "</item>"
        "<item>"
            "<layout type=\"vertical\">")
    for i in range(nChannels):
        customLayout+='<item><view class="vtkMRMLPlotViewNode" singletontag="SignalView'+'-{}"'.format(i)+'>'
        customLayout+='<property name="viewlabel" action="default">'+'P{}'.format(i+1)+'</property>'
        customLayout+='</view></item>'
    customLayout+='</layout></item></layout>'
    slicer.customLayout = customLayout
    customLayoutId=501
    layoutManager=slicer.app.layoutManager()
    if not layoutManager.layoutLogic().GetLayoutNode().SetLayoutDescription(customLayoutId,customLayout):
        layoutManager.layoutLogic().GetLayoutNode().AddLayoutDescription(customLayoutId,customLayout)
    layoutManager.setLayout(customLayoutId)
    for i in range(nChannels):
        layoutManager.plotWidget(i).plotController().setVisible(False)

def PlotSignal():
    import math
    num=10
    SetLayout(num)
    maxNumOfPoints=100
    layoutManager=slicer.app.layoutManager()
    if layoutManager.plotViewCount!=num:
        print("Plot view number({}) is not equal to signal channel number({})".format(layoutManager.plotViewCount,num))
        return
    for channel in range(num):
        tableNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode","Table{}".format(channel))
        table=tableNode.GetTable()
        table.Initialize()
        arrX=vtk.vtkFloatArray()
        arrX.SetName("Time(ms)")
        table.AddColumn(arrX)
        arrY=vtk.vtkFloatArray()
        name="{0} mm".format(channel)
        arrY.SetName(name)
        table.AddColumn(arrY)
        table.SetNumberOfRows(maxNumOfPoints)
        for j in range(maxNumOfPoints):
            table.SetValue(j,0,j)
            table.SetValue(j,1,math.sin(j))
        table.Modified()
        #Create two plot series nodes
        name="{0} mm".format(channel)
        plotSeriesNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotSeriesNode","Signal@{0}".format(name))
        plotSeriesNode.SetAndObserveTableNodeID(tableNode.GetID())
        plotSeriesNode.SetXColumnName("Time(ms)")
        plotSeriesNode.SetYColumnName(name)
        plotSeriesNode.SetPlotType(slicer.vtkMRMLPlotSeriesNode.PlotTypeLine)
        plotSeriesNode.SetUniqueColor()
        plotChartNode=slicer.mrmlScene.AddNewNodeByClass("vtkMRMLPlotChartNode","Chart@{0}".format(name))
        plotChartNode.AddAndObservePlotSeriesNodeID(plotSeriesNode.GetID())
        plotChartNode.SetXAxisTitle('') # 'Time(ms)'
        plotChartNode.SetYAxisTitle(f'uV @{name}')
        plotChartNode.SetAxisTitleFontSize(9)
        plotChartNode.SetAxisLabelFontSize(9)
        plotChartNode.SetYAxisRangeAuto(True)
        plotChartNode.SetXAxisRangeAuto(True)
        plotChartNode.SetLegendVisibility(False)
        plotViewNode=slicer.mrmlScene.GetSingletonNode(f'SignalView-{channel}','vtkMRMLPlotViewNode')
        plotViewNode.SetPlotChartNodeID(plotChartNode.GetID())
    for i in range(layoutManager.plotViewCount):
        layoutManager.plotWidget(i).plotView().fitToContent()

PlotSignal()
1 Like

Agree, I will check the data filled in the table.

Thanks so much!

Hi Lasso,

The reason why the application crashed is that after setting the layout, the layout containing the plot views was collapsed in default. Then, I specified “splitSize” in “item” when defining the customLayout and the layout containing the plot views were shown and then the signal can be plotted successfully.

1 Like