]> git.gag.com Git - debian/gnuradio/blob - gr-wxgui/src/python/plotter/waterfall_plotter.py
bug fix for waterfall plotter,
[debian/gnuradio] / gr-wxgui / src / python / plotter / waterfall_plotter.py
1 #
2 # Copyright 2008, 2009 Free Software Foundation, Inc.
3 #
4 # This file is part of GNU Radio
5 #
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10 #
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20 #
21
22 import wx
23 from grid_plotter_base import grid_plotter_base
24 from OpenGL import GL
25 import common
26 import numpy
27 import gltext
28 import math
29 import struct
30
31 LEGEND_LEFT_PAD = 7
32 LEGEND_NUM_BLOCKS = 256
33 LEGEND_NUM_LABELS = 9
34 LEGEND_WIDTH = 8
35 LEGEND_FONT_SIZE = 8
36 LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black
37 MIN_PADDING = 0, 60, 0, 0 #top, right, bottom, left
38
39 ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2)))
40
41 pack_color   = lambda x: struct.pack('BBBB', *x)
42 unpack_color = lambda s: struct.unpack('BBBB', s)
43
44 def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]):
45         """
46         Get an array of 256 rgba values where each index maps to a color.
47         The scaling for red, green, blue, alpha are specified in piece-wise functions.
48         The piece-wise functions consist of a set of x, y coordinates.
49         The x and y values of the coordinates range from 0 to 1.
50         The coordinates must be specified so that x increases with the index value.
51         Resulting values are calculated along the line formed between 2 coordinates.
52         @param *_pts an array of x,y coordinates for each color element
53         @return array of rbga values (4 bytes) each
54         """
55         def _fcn(x, pw):
56                 for (x1, y1), (x2, y2) in zip(pw, pw[1:]):
57                         #linear interpolation
58                         if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1
59                 raise Exception
60         return [pack_color(map(
61                         lambda pw: int(255*_fcn(i/255.0, pw)),
62                         (red_pts, green_pts, blue_pts, alpha_pts),
63                 )) for i in range(0, 256)
64         ]
65
66 COLORS = {
67         'rgb1': _get_rbga( #http://www.ks.uiuc.edu/Research/vmd/vmd-1.7.1/ug/img47.gif
68                 red_pts = [(0, 0), (.5, 0), (1, 1)],
69                 green_pts = [(0, 0), (.5, 1), (1, 0)],
70                 blue_pts = [(0, 1), (.5, 0), (1, 0)],
71         ),
72         'rgb2': _get_rbga( #http://xtide.ldeo.columbia.edu/~krahmann/coledit/screen.jpg
73                 red_pts = [(0, 0), (3.0/8, 0), (5.0/8, 1), (7.0/8, 1), (1, .5)],
74                 green_pts = [(0, 0), (1.0/8, 0), (3.0/8, 1), (5.0/8, 1), (7.0/8, 0), (1, 0)],
75                 blue_pts = [(0, .5), (1.0/8, 1), (3.0/8, 1), (5.0/8, 0), (1, 0)],
76         ),
77         'rgb3': _get_rbga(
78                 red_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 0), (1, 1)],
79                 green_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 1), (1, 0)],
80                 blue_pts = [(0, 0), (1.0/3.0, 1), (2.0/3.0, 0), (1, 0)],
81         ),
82         'gray': _get_rbga(
83                 red_pts = [(0, 0), (1, 1)],
84                 green_pts = [(0, 0), (1, 1)],
85                 blue_pts = [(0, 0), (1, 1)],
86         ),
87 }
88
89 ##################################################
90 # Waterfall Plotter
91 ##################################################
92 class waterfall_plotter(grid_plotter_base):
93         def __init__(self, parent):
94                 """
95                 Create a new channel plotter.
96                 """
97                 #init
98                 grid_plotter_base.__init__(self, parent, MIN_PADDING)
99                 #setup legend cache
100                 self._legend_cache = self.new_gl_cache(self._draw_legend)
101                 #setup waterfall cache
102                 self._waterfall_cache = self.new_gl_cache(self._draw_waterfall, 50)
103                 #setup waterfall plotter
104                 self.register_init(self._init_waterfall)
105                 self._resize_texture(False)
106                 self._minimum = 0
107                 self._maximum = 0
108                 self._fft_size = 1
109                 self._buffer = list()
110                 self._pointer = 0
111                 self._counter = 0
112                 self.set_num_lines(0)
113                 self.set_color_mode(COLORS.keys()[0])
114
115         def _init_waterfall(self):
116                 """
117                 Run gl initialization tasks.
118                 """
119                 self._waterfall_texture = GL.glGenTextures(1)
120
121         def _draw_waterfall(self):
122                 """
123                 Draw the waterfall from the texture.
124                 The texture is circularly filled and will wrap around.
125                 Use matrix modeling to shift and scale the texture onto the coordinate plane.
126                 """
127                 #resize texture
128                 self._resize_texture()
129                 #setup texture
130                 GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture)
131                 GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
132                 GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
133                 GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT)
134                 GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE)
135                 #write the buffer to the texture
136                 while self._buffer:
137                         GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, self._buffer.pop(0))
138                         self._pointer = (self._pointer + 1)%self._num_lines
139                 #begin drawing
140                 GL.glEnable(GL.GL_TEXTURE_2D)
141                 GL.glPushMatrix()
142                 #matrix scaling
143                 GL.glTranslatef(self.padding_left, self.padding_top, 0)
144                 GL.glScalef(
145                         float(self.width-self.padding_left-self.padding_right),
146                         float(self.height-self.padding_top-self.padding_bottom),
147                         1.0,
148                 )
149                 #draw texture with wrapping
150                 GL.glBegin(GL.GL_QUADS)
151                 prop_y = float(self._pointer)/(self._num_lines-1)
152                 prop_x = float(self._fft_size)/ceil_log2(self._fft_size)
153                 off = 1.0/(self._num_lines-1)
154                 GL.glTexCoord2f(0, prop_y+1-off)
155                 GL.glVertex2f(0, 1)
156                 GL.glTexCoord2f(prop_x, prop_y+1-off)
157                 GL.glVertex2f(1, 1)
158                 GL.glTexCoord2f(prop_x, prop_y)
159                 GL.glVertex2f(1, 0)
160                 GL.glTexCoord2f(0, prop_y)
161                 GL.glVertex2f(0, 0)
162                 GL.glEnd()
163                 GL.glPopMatrix()
164                 GL.glDisable(GL.GL_TEXTURE_2D)
165
166         def _populate_point_label(self, x_val, y_val):
167                 """
168                 Get the text the will populate the point label.
169                 Give the X value for the current point.
170                 @param x_val the current x value
171                 @param y_val the current y value
172                 @return a value string with units
173                 """
174                 return '%s: %s'%(self.x_label, common.eng_format(x_val, self.x_units))
175
176         def _draw_legend(self):
177                 """
178                 Draw the color scale legend.
179                 """
180                 if not self._color_mode: return
181                 legend_height = self.height-self.padding_top-self.padding_bottom
182                 #draw each legend block
183                 block_height = float(legend_height)/LEGEND_NUM_BLOCKS
184                 x = self.width - self.padding_right + LEGEND_LEFT_PAD
185                 for i in range(LEGEND_NUM_BLOCKS):
186                         color = unpack_color(COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))])
187                         GL.glColor4f(*numpy.array(color)/255.0)
188                         y = self.height - (i+1)*block_height - self.padding_bottom
189                         self._draw_rect(x, y, LEGEND_WIDTH, block_height)
190                 #draw rectangle around color scale border
191                 GL.glColor3f(*LEGEND_BORDER_COLOR_SPEC)
192                 self._draw_rect(x, self.padding_top, LEGEND_WIDTH, legend_height, fill=False)
193                 #draw each legend label
194                 label_spacing = float(legend_height)/(LEGEND_NUM_LABELS-1)
195                 x = self.width - (self.padding_right - LEGEND_LEFT_PAD - LEGEND_WIDTH)/2
196                 for i in range(LEGEND_NUM_LABELS):
197                         proportion = i/float(LEGEND_NUM_LABELS-1)
198                         dB = proportion*(self._maximum - self._minimum) + self._minimum
199                         y = self.height - i*label_spacing - self.padding_bottom
200                         txt = gltext.Text('%ddB'%int(dB), font_size=LEGEND_FONT_SIZE, centered=True)
201                         txt.draw_text(wx.Point(x, y))
202
203         def _resize_texture(self, flag=None):
204                 """
205                 Create the texture to fit the fft_size X num_lines.
206                 @param flag the set/unset or update flag
207                 """
208                 if flag is not None: 
209                         self._resize_texture_flag = flag
210                         return
211                 if not self._resize_texture_flag: return
212                 self._buffer = list()
213                 self._pointer = 0
214                 if self._num_lines and self._fft_size:
215                         GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture)
216                         data = numpy.zeros(self._num_lines*ceil_log2(self._fft_size)*4, numpy.uint8).tostring()
217                         GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, data)
218                 self._resize_texture_flag = False
219
220         def set_color_mode(self, color_mode):
221                 """
222                 Set the color mode.
223                 New samples will be converted to the new color mode.
224                 Old samples will not be recolorized.
225                 @param color_mode the new color mode string
226                 """
227                 self.lock()
228                 if color_mode in COLORS.keys():
229                         self._color_mode = color_mode
230                         self._legend_cache.changed(True)
231                 self.update()
232                 self.unlock()
233
234         def set_num_lines(self, num_lines):
235                 """
236                 Set number of lines.
237                 Powers of two only.
238                 @param num_lines the new number of lines
239                 """
240                 self.lock()
241                 self._num_lines = num_lines
242                 self._resize_texture(True)
243                 self.update()
244                 self.unlock()
245
246         def set_samples(self, samples, minimum, maximum):
247                 """
248                 Set the samples to the waterfall.
249                 Convert the samples to color data.
250                 @param samples the array of floats
251                 @param minimum the minimum value to scale
252                 @param maximum the maximum value to scale
253                 """
254                 self.lock()
255                 #set the min, max values
256                 if self._minimum != minimum or self._maximum != maximum:
257                         self._minimum = minimum
258                         self._maximum = maximum
259                         self._legend_cache.changed(True)
260                 if self._fft_size != len(samples):
261                         self._fft_size = len(samples)
262                         self._resize_texture(True)
263                 #normalize the samples to min/max
264                 samples = (samples - minimum)*float(255/(maximum-minimum))
265                 samples = numpy.clip(samples, 0, 255) #clip
266                 samples = numpy.array(samples, numpy.uint8)
267                 #convert the samples to RGBA data
268                 data = ''.join([COLORS[self._color_mode][sample] for sample in samples])
269                 self._buffer.append(data)
270                 self._waterfall_cache.changed(True)
271                 self.unlock()