OpenGL Stripchart plotter for a user defined number of channels.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | #!/usr/bin/env python
"""
Author: Flavio Codeco Coelho
License: GPL
Stripchart module for vpython
Features:
-user defined number of channels
"""
import psyco
import time
from visual import *
from visual.text import *
from RandomArray import random
from psyco.classes import *
psyco.full()
class Strip:
"""
Stripchart object.
"""
def __init__(self, nch=1, xlabel='t(s)',ylabel='V'):
self.scene = display(title="Stripchart Recorder",width=600,height=400, uniform=0, ambient=1,autoscale=1)
#create the curves
self.channels = self.addch(nch)
self.gridon = True
self.grid = frame() #collection of horizontal grid lines
self.ticklabels=frame()#collection of labels
self.xlabel = label(frame=self.grid,pos=(9,-9,0),text=xlabel,color=color.orange,box=0, line=0)
self.ylabel = label(frame=self.grid,pos=(-9,9,0),text=ylabel,color=color.orange,box=0,line=0)
self.drawGrid()
self.drawTickLabels(range(-10,10),range(-10,10))
def addch(self, nch=1):
"""
Creates a channel object (curve)
nch is the number of channels.
"""
col = [color.white,color.blue,color.red,color.green,color.yellow,color.cyan,color.magenta, color.orange]
tplch = tuple([curve(radius=0, color=col[n%len(col)])for n in xrange(nch)])
return tplch
def toggleGrid(self):
"""
Toggle visibility of gridlines
"""
r = len(self.grid.objects)
if self.gridon:
for i in range(r):
self.grid.objects[0].visible = 0
self.gridon = False
else:
self.drawGrid()
self.gridon = True
pass
def drawGrid(self):
"""
draw grid lines
"""
hrng = int(self.scene.range[1])
vrng = int(self.scene.range[0])
[curve(frame=self.grid,pos=[(-vrng,i),(vrng,i)],color=color.white) for i in xrange(-hrng, hrng, hrng*2/10)]
[curve(frame=self.grid,pos=[(j,-hrng),(j,hrng)],color=color.white) for j in xrange(-vrng, vrng, vrng*2/10)]
def drawTickLabels(self,xticks,yticks):
"""
Draw x and y tick labels
"""
xticks2 = array([round(i,2) for i in xticks])
yticks2 = array([round(i,2) for i in yticks])
labidx = range(0,len(xticks2),len(xticks2)/10) #indices to the labels to be plotted
Pos = range(-10,10,2) #position of the ticklabels within the frame coordinates
#List comprehensions to draw ticklabels. [(xticks,yticks) for i in xrange(len(Pos))]
# Not as clear as a regular "for" but much faster...
[label(frame=self.ticklabels,pos=(Pos[i],-8,0),text=str(xticks2[labidx[i]]),color=color.orange,box=0,line=0) for i in xrange(len(Pos))] #x ticklables
[label(frame=self.ticklabels,pos=(-8,Pos[i],0),text=str(yticks2[labidx[i]]),color=color.orange,box=0,line=0) for i in xrange(len(Pos))] #y ticklabels
def updateTicks(self,xticks,yticks):
"""
Update tick labels values
"""
xticks2 = array([round(i,2) for i in xticks])
yticks2 = array([round(i,2) for i in yticks])
labidx = range(0,len(xticks2),len(xticks2)/10) #indices to the labels to be plotted
Pos = range(-10,10,2) #position of the ticklabels within the frame coordinates
i = j = 0
for l in self.ticklabels.objects:
if self.ticklabels.objects.index(l) < 10: #update x axis
l.text = str(xticks2[labidx[i]])
i += 1
else: #update y axis
l.text = str(yticks2[labidx[j]])
j += 1
def plot(self,ch,point):
"""
Appends a point to the channel(curve) object ch.
Fills the scene and after it is full,
shifts the curve one step left, for each new point.
If its the first point it creates the plot.
"""
#Adjust the scene and frames vertically to folow data
if point[1] > self.scene.range[1]:
self.scene.center[1] = point[1]-9
self.grid.y = point[1]-9
self.ticklabels.y = point[1]-9
elif point[1] < -self.scene.range[1]:
self.scene.center[1] = point[1]+9
self.grid.y = point[1]+9
self.ticklabels.y = point[1]+9
#=========================================
if self.scene.kb.keys:#key trap
s = self.scene.kb.getkey() # obtain keyboard information
if s == 'g' or s == 'G':
self.toggleGrid()
# Scroll scene center right to follow data
if len(ch.x) > 0 and ch.x[-1] > 10:
# scroll center of the scene with x
new_x_center = (ch.x[0]+ch.x[-1])/2.
self.scene.center = (new_x_center,0,0)
self.updateTicks(ch.x,ch.y)
self.grid.x = new_x_center
self.ticklabels.x = new_x_center
# shift curve and append
ch.pos[:-1] = ch.pos[1:]
ch.pos[-1] = point
else:
ch.append(pos=point)
if __name__=='__main__':
st = Strip(2)
ch1 = st.channels[0]
ch2 = st.channels[1]
start = time.clock()
for i in arange(-10,200,.1):
st.plot(ch1,(i,10*sin(i),0))
st.plot(ch2,(i,10*cos(i)+random(),0))
#rate(50)#set maximum frame rate
print "frame rate: ", 1/((time.clock()-start)/210.)
|
This is a very simple implementation aimed to demonstrate the versatility of VPython (http://www.vpython.org). It can be much improved for a real application including interactive keyboard control of frame rate, plot position, etc.
The script naturally requires you to have vpython installed. I also use psyco to speed it up a bit. You can toggle the grid lines on/off by pressing the "g" key.
If the script is ran by itself, it will calculate the maximal frame rate it can churn out. I would appreciate if users could post the frame rates they achieve on their boxes along with information on their GL hardware acceleration:
On my Box: Frame rate: 20.8 fps Hardware: Nvidia Geforce2MX Driver: Nvidia 1.0.6111
This recipe was originally published on my website: http://www.procc.fiocruz.br:8080/procc/Members/flavio/codepy/stripchart/document_view
Frame rate.
Frame Rate.
Frame rate. CPU: 1 Pentium(R) 4 CPU 3.40GHz (hyperthreading on)
GPU: RADEON X300
O/S: WinXP 'pro'
frame rate: 50.2659866666