# Plotting the signals

**URL:** https://discourse.slicer.org/t/plotting-the-signals/20242
**Category:** Development
**Created:** [October 19, 2021, 3:11pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242 "2021-10-19T15:11:27Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 19, 2021, 3:11pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/1 "2021-10-19T15:11:27Z")

</div>

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!

---

<div class="post-metadata">

### Author: ![PaoloZaffino](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/paolozaffino/32/81052_2.png) [@PaoloZaffino](https://discourse.slicer.org/u/PaoloZaffino)
#### Post date: [October 19, 2021, 3:16pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/2 "2021-10-19T15:16:38Z")

</div>

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

You can give a look at this class:

> <https://github.com/pzaffino/SlicerArduinoController/blob/master/ArduinoConnect/ArduinoConnect.py#L40>

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

HTH

Paolo

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 19, 2021, 3:49pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/3 "2021-10-19T15:49:12Z")

</div>

> [@PaoloZaffino](#):
>
> t and then I plot the samples.

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

---

<div class="post-metadata">

### Author: ![PaoloZaffino](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/paolozaffino/32/81052_2.png) [@PaoloZaffino](https://discourse.slicer.org/u/PaoloZaffino)
#### Post date: [October 19, 2021, 8:12pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/4 "2021-10-19T20:12:09Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 20, 2021, 4:43am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/5 "2021-10-20T04:43:04Z")

</div>

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](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/c/0/c080d0d0432f093c5a9d7b235a14ad8d8735e54f.png). 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!

---

<div class="post-metadata">

### Author: ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)
#### Post date: [October 20, 2021, 5:44am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/6 "2021-10-20T05:44:25Z")

</div>

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](https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html#customize-view-layout). You can [hide the controller bars](https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html#hide-view-controller-bars) to keep all the plots close together.

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 20, 2021, 6:26am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/7 "2021-10-20T06:26:29Z")

</div>

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](https://slicer.readthedocs.io/en/latest/developer_guide/script_repository.html#customize-view-layout)).

---

<div class="post-metadata">

### Author: ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)
#### Post date: [October 20, 2021, 6:31am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/8 "2021-10-20T06:31:10Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 21, 2021, 3:27am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/9 "2021-10-21T03:27:58Z")

</div>

Hi Lassoan,

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

```auto
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())

```

---

<div class="post-metadata">

### Author: ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)
#### Post date: [October 21, 2021, 3:34am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/10 "2021-10-21T03:34:38Z")

</div>

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.

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 21, 2021, 5:22am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/11 "2021-10-21T05:22:23Z")

</div>

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

```auto
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())

```

```auto
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)

```

---

<div class="post-metadata">

### Author: ![lassoan](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.slicer.org/lassoan/32/13_2.png) [@lassoan](https://discourse.slicer.org/u/lassoan)
#### Post date: [October 21, 2021, 3:30pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/12 "2021-10-21T15:30:54Z")

</div>

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:

 ![image](https://us1.discourse-cdn.com/flex002/uploads/slicer/original/3X/4/f/4fe3edd87094a9e201bcbf42f7f8a03d8cd77b39.png)

The updated code:

```python
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()

```

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [October 22, 2021, 2:37pm UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/13 "2021-10-22T14:37:34Z")

</div>

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

Thanks so much!

---

<div class="post-metadata">

### Author: ![wpgao](https://avatars.discourse-cdn.com/v4/letter/w/8e8cbc/32.png) [@wpgao](https://discourse.slicer.org/u/wpgao)
#### Post date: [November 23, 2021, 6:57am UTC](https://discourse.slicer.org/t/plotting-the-signals/20242/14 "2021-11-23T06:57:54Z")

</div>

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.
