summaryrefslogtreecommitdiff
path: root/gr-wxgui/src/python/plotter/waterfall_plotter.py
diff options
context:
space:
mode:
authorjcorgan2008-08-14 18:43:15 +0000
committerjcorgan2008-08-14 18:43:15 +0000
commit36649d4e472172fe840444ac0268c7b6b4da94b4 (patch)
treea3f8b35980e18f7faee2e82e079746f71b0ee02c /gr-wxgui/src/python/plotter/waterfall_plotter.py
parent5b09804605cd41bbc3fdcb917eda3f69a7598af9 (diff)
downloadgnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.tar.gz
gnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.tar.bz2
gnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.zip
Merged changeset r9241:9289 from jblum/glwxgui into trunk. Adds OpenGL versions of fftsink, waterfallsink, and scopesink, and new constsink. See README.gl for use. (Josh Blum)
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9290 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gr-wxgui/src/python/plotter/waterfall_plotter.py')
-rw-r--r--gr-wxgui/src/python/plotter/waterfall_plotter.py282
1 files changed, 282 insertions, 0 deletions
diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py
new file mode 100644
index 000000000..88e2b4dc1
--- /dev/null
+++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py
@@ -0,0 +1,282 @@
+#
+# Copyright 2008 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+import wx
+from plotter_base import grid_plotter_base
+from OpenGL.GL import *
+from gnuradio.wxgui import common
+import numpy
+import gltext
+import math
+
+LEGEND_LEFT_PAD = 7
+LEGEND_NUM_BLOCKS = 256
+LEGEND_NUM_LABELS = 9
+LEGEND_WIDTH = 8
+LEGEND_FONT_SIZE = 8
+LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black
+PADDING = 35, 60, 40, 60 #top, right, bottom, left
+
+ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2)))
+
+def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]):
+ """!
+ Get an array of 256 rgba values where each index maps to a color.
+ The scaling for red, green, blue, alpha are specified in piece-wise functions.
+ The piece-wise functions consist of a set of x, y coordinates.
+ The x and y values of the coordinates range from 0 to 1.
+ The coordinates must be specified so that x increases with the index value.
+ Resulting values are calculated along the line formed between 2 coordinates.
+ @param *_pts an array of x,y coordinates for each color element
+ @return array of rbga values (4 bytes) each
+ """
+ def _fcn(x, pw):
+ for (x1, y1), (x2, y2) in zip(pw, pw[1:]):
+ #linear interpolation
+ if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1
+ raise Exception
+ return [numpy.array(map(
+ lambda pw: int(255*_fcn(i/255.0, pw)),
+ (red_pts, green_pts, blue_pts, alpha_pts),
+ ), numpy.uint8).tostring() for i in range(0, 256)
+ ]
+
+COLORS = {
+ 'rgb1': _get_rbga( #http://www.ks.uiuc.edu/Research/vmd/vmd-1.7.1/ug/img47.gif
+ red_pts = [(0, 0), (.5, 0), (1, 1)],
+ green_pts = [(0, 0), (.5, 1), (1, 0)],
+ blue_pts = [(0, 1), (.5, 0), (1, 0)],
+ ),
+ 'rgb2': _get_rbga( #http://xtide.ldeo.columbia.edu/~krahmann/coledit/screen.jpg
+ red_pts = [(0, 0), (3.0/8, 0), (5.0/8, 1), (7.0/8, 1), (1, .5)],
+ green_pts = [(0, 0), (1.0/8, 0), (3.0/8, 1), (5.0/8, 1), (7.0/8, 0), (1, 0)],
+ blue_pts = [(0, .5), (1.0/8, 1), (3.0/8, 1), (5.0/8, 0), (1, 0)],
+ ),
+ 'rgb3': _get_rbga(
+ red_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 0), (1, 1)],
+ green_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 1), (1, 0)],
+ blue_pts = [(0, 0), (1.0/3.0, 1), (2.0/3.0, 0), (1, 0)],
+ ),
+ 'gray': _get_rbga(
+ red_pts = [(0, 0), (1, 1)],
+ green_pts = [(0, 0), (1, 1)],
+ blue_pts = [(0, 0), (1, 1)],
+ ),
+}
+
+##################################################
+# Waterfall Plotter
+##################################################
+class waterfall_plotter(grid_plotter_base):
+ def __init__(self, parent):
+ """!
+ Create a new channel plotter.
+ """
+ #init
+ grid_plotter_base.__init__(self, parent, PADDING)
+ self._resize_texture(False)
+ self._minimum = 0
+ self._maximum = 0
+ self._fft_size = 1
+ self._buffer = list()
+ self._pointer = 0
+ self._counter = 0
+ self.set_num_lines(0)
+ self.set_color_mode(COLORS.keys()[0])
+
+ def _gl_init(self):
+ """!
+ Run gl initialization tasks.
+ """
+ self._grid_compiled_list_id = glGenLists(1)
+ self._waterfall_texture = glGenTextures(1)
+
+ def draw(self):
+ """!
+ Draw the grid and waveforms.
+ """
+ self.lock()
+ #resize texture
+ self._resize_texture()
+ #store the grid drawing operations
+ if self.changed():
+ glNewList(self._grid_compiled_list_id, GL_COMPILE)
+ self._draw_grid()
+ self._draw_legend()
+ glEndList()
+ self.changed(False)
+ self.clear()
+ #draw the grid
+ glCallList(self._grid_compiled_list_id)
+ self._draw_waterfall()
+ self._draw_point_label()
+ #swap buffer into display
+ self.SwapBuffers()
+ self.unlock()
+
+ def _draw_waterfall(self):
+ """!
+ Draw the waterfall from the texture.
+ The texture is circularly filled and will wrap around.
+ Use matrix modeling to shift and scale the texture onto the coordinate plane.
+ """
+ #setup texture
+ glBindTexture(GL_TEXTURE_2D, self._waterfall_texture)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
+ #write the buffer to the texture
+ while self._buffer:
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL_RGBA, GL_UNSIGNED_BYTE, self._buffer.pop(0))
+ self._pointer = (self._pointer + 1)%self._num_lines
+ #begin drawing
+ glEnable(GL_TEXTURE_2D)
+ glPushMatrix()
+ #matrix scaling
+ glTranslatef(self.padding_left+1, self.padding_top, 0)
+ glScalef(
+ float(self.width-self.padding_left-self.padding_right-1),
+ float(self.height-self.padding_top-self.padding_bottom-1),
+ 1.0,
+ )
+ #draw texture with wrapping
+ glBegin(GL_QUADS)
+ prop_y = float(self._pointer)/(self._num_lines-1)
+ prop_x = float(self._fft_size)/ceil_log2(self._fft_size)
+ off = 1.0/(self._num_lines-1)
+ glTexCoord2f(0, prop_y+1-off)
+ glVertex2f(0, 1)
+ glTexCoord2f(prop_x, prop_y+1-off)
+ glVertex2f(1, 1)
+ glTexCoord2f(prop_x, prop_y)
+ glVertex2f(1, 0)
+ glTexCoord2f(0, prop_y)
+ glVertex2f(0, 0)
+ glEnd()
+ glPopMatrix()
+ glDisable(GL_TEXTURE_2D)
+
+ def _populate_point_label(self, x_val, y_val):
+ """!
+ Get the text the will populate the point label.
+ Give the X value for the current point.
+ @param x_val the current x value
+ @param y_val the current y value
+ @return a value string with units
+ """
+ return '%s: %s %s'%(self.x_label, common.label_format(x_val), self.x_units)
+
+ def _draw_legend(self):
+ """!
+ Draw the color scale legend.
+ """
+ if not self._color_mode: return
+ legend_height = self.height-self.padding_top-self.padding_bottom
+ #draw each legend block
+ block_height = float(legend_height)/LEGEND_NUM_BLOCKS
+ x = self.width - self.padding_right + LEGEND_LEFT_PAD
+ for i in range(LEGEND_NUM_BLOCKS):
+ color = COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))]
+ glColor4f(*map(lambda c: ord(c)/255.0, color))
+ y = self.height - (i+1)*block_height - self.padding_bottom
+ self._draw_rect(x, y, LEGEND_WIDTH, block_height)
+ #draw rectangle around color scale border
+ glColor3f(*LEGEND_BORDER_COLOR_SPEC)
+ self._draw_rect(x, self.padding_top, LEGEND_WIDTH, legend_height, fill=False)
+ #draw each legend label
+ label_spacing = float(legend_height)/(LEGEND_NUM_LABELS-1)
+ x = self.width - (self.padding_right - LEGEND_LEFT_PAD - LEGEND_WIDTH)/2
+ for i in range(LEGEND_NUM_LABELS):
+ proportion = i/float(LEGEND_NUM_LABELS-1)
+ dB = proportion*(self._maximum - self._minimum) + self._minimum
+ y = self.height - i*label_spacing - self.padding_bottom
+ txt = gltext.Text('%ddB'%int(dB), font_size=LEGEND_FONT_SIZE, centered=True)
+ txt.draw_text(wx.Point(x, y))
+
+ def _resize_texture(self, flag=None):
+ """!
+ Create the texture to fit the fft_size X num_lines.
+ @param flag the set/unset or update flag
+ """
+ if flag is not None:
+ self._resize_texture_flag = flag
+ return
+ if not self._resize_texture_flag: return
+ self._buffer = list()
+ self._pointer = 0
+ if self._num_lines and self._fft_size:
+ glBindTexture(GL_TEXTURE_2D, self._waterfall_texture)
+ data = numpy.zeros(self._num_lines*self._fft_size*4, numpy.uint8).tostring()
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
+ self._resize_texture_flag = False
+
+ def set_color_mode(self, color_mode):
+ """!
+ Set the color mode.
+ New samples will be converted to the new color mode.
+ Old samples will not be recolorized.
+ @param color_mode the new color mode string
+ """
+ self.lock()
+ if color_mode in COLORS.keys():
+ self._color_mode = color_mode
+ self.changed(True)
+ self.update()
+ self.unlock()
+
+ def set_num_lines(self, num_lines):
+ """!
+ Set number of lines.
+ Powers of two only.
+ @param num_lines the new number of lines
+ """
+ self.lock()
+ self._num_lines = num_lines
+ self._resize_texture(True)
+ self.update()
+ self.unlock()
+
+ def set_samples(self, samples, minimum, maximum):
+ """!
+ Set the samples to the waterfall.
+ Convert the samples to color data.
+ @param samples the array of floats
+ @param minimum the minimum value to scale
+ @param maximum the maximum value to scale
+ """
+ self.lock()
+ #set the min, max values
+ if self._minimum != minimum or self._maximum != maximum:
+ self._minimum = minimum
+ self._maximum = maximum
+ self.changed(True)
+ if self._fft_size != len(samples):
+ self._fft_size = len(samples)
+ self._resize_texture(True)
+ #normalize the samples to min/max
+ samples = (samples - minimum)*float(255/(maximum-minimum))
+ samples = numpy.clip(samples, 0, 255) #clip
+ samples = numpy.array(samples, numpy.uint8)
+ #convert the samples to RGBA data
+ data = numpy.choose(samples, COLORS[self._color_mode]).tostring()
+ self._buffer.append(data)
+ self.unlock()