Welcome, guest | Sign In | My Account | Store | Cart

OpenGL Stripchart plotter for a user defined number of channels.

Python, 152 lines
  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

3 comments

Bill Scherer 18 years, 10 months ago  # | flag

Frame rate.

CPU: 1 Intel(R) Xeon(TM) CPU 3.06GHz (hyperthreading on)
GPU: Nvidia Quadro4 980 XGL
O/S: RH9

Visual-2003-10-05
frame rate:  38.7453874539
Chris Grebeldinger 18 years, 10 months ago  # | flag

Frame Rate.

CPU: 1 Athlon XP 2600+
GPU: ATI Radeon 9600pro
O/S: Windows XP SP2

frame rate:  21.6 (with psyco full)
             22.0 (without psycho)
             22.5 (python -O)
Ed Blake 18 years, 2 months ago  # | flag

Frame rate. CPU: 1 Pentium(R) 4 CPU 3.40GHz (hyperthreading on)

GPU: RADEON X300

O/S: WinXP 'pro'

frame rate: 50.2659866666