wpgao
October 19, 2021, 3:11pm
1
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:
#EXAMPLE TO PRINT THE RECEIVED VALUE:
print("FIRED! %s" % (self.ArduinoNode.GetParameter("Data")))
def sendDataToArduino(self, message):
messageSent = slicer.modules.arduinoconnect.widgetRepresentation().self().logic.sendMessage(message)
#
#ArduinoPlotter
#
class ArduinoPlotter():
def __init__(self, numberOfSamples):
self.active = True
self.ArduinoNode = slicer.mrmlScene.GetFirstNodeByName("arduinoNode")
sceneModifiedObserverTag = self.ArduinoNode.AddObserver(vtk.vtkCommand.ModifiedEvent, self.addPointToPlot)
# Add data into table vtk
self.tableNode = slicer.mrmlScene.AddNewNodeByClass("vtkMRMLTableNode")
self.tableNode.SetName("Arduino plotting table")
Basically I receive the data on the serial port and then I plot the samples.
HTH
Paolo
wpgao
October 19, 2021, 3:49pm
3
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.
wpgao
October 20, 2021, 4:43am
5
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
. 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!
lassoan
(Andras Lasso)
October 20, 2021, 5:44am
6
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.
wpgao
October 20, 2021, 6:26am
7
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 ).
lassoan
(Andras Lasso)
October 20, 2021, 6:31am
8
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.
wpgao
October 21, 2021, 3:27am
9
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())
lassoan
(Andras Lasso)
October 21, 2021, 3:34am
10
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.
wpgao
October 21, 2021, 5:22am
11
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
lassoan
(Andras Lasso)
October 21, 2021, 3:30pm
12
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
wpgao
October 22, 2021, 2:37pm
13
Agree, I will check the data filled in the table.
Thanks so much!
wpgao
November 23, 2021, 6:57am
14
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