From a80c0f4969a994655ccb18c55f8ce936abcc61f7 Mon Sep 17 00:00:00 2001
From: Ben Reynwar <ben@reynwar.net>
Date: Fri, 8 Mar 2013 20:30:47 -0700
Subject: wxgui: Enabling uninstalled python imports.

---
 gr-wxgui/python/wxgui/plotter/waterfall_plotter.py | 294 +++++++++++++++++++++
 1 file changed, 294 insertions(+)
 create mode 100644 gr-wxgui/python/wxgui/plotter/waterfall_plotter.py

(limited to 'gr-wxgui/python/wxgui/plotter/waterfall_plotter.py')

diff --git a/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py b/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py
new file mode 100644
index 0000000000..02ec67ee4c
--- /dev/null
+++ b/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py
@@ -0,0 +1,294 @@
+#
+# Copyright 2008, 2009, 2010 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 grid_plotter_base import grid_plotter_base
+from OpenGL import GL
+import common
+import numpy
+import gltext
+import math
+import struct
+
+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
+MIN_PADDING = 0, 60, 0, 0 #top, right, bottom, left
+
+ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2)))
+
+pack_color   = lambda x: struct.unpack('I', struct.pack('BBBB', *x))[0]
+unpack_color = lambda x: struct.unpack('BBBB', struct.pack('I', int(x)))
+
+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.
+
+        Args:
+	    red_pts, green_pts, blue_pts, alpha_pts: an array of x,y coordinates for each color element
+
+        Returns:
+	    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([pack_color(map(
+		lambda pw: int(255*_fcn(i/255.0, pw)),
+		(red_pts, green_pts, blue_pts, alpha_pts),
+	)) for i in range(0, 256)], numpy.uint32)
+
+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, MIN_PADDING)
+		#setup legend cache
+		self._legend_cache = self.new_gl_cache(self._draw_legend)
+		#setup waterfall cache
+		self._waterfall_cache = self.new_gl_cache(self._draw_waterfall, 50)
+		#setup waterfall plotter
+		self.register_init(self._init_waterfall)
+		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])
+		self.callback = None
+
+	def _init_waterfall(self):
+		"""
+		Run gl initialization tasks.
+		"""
+		self._waterfall_texture = GL.glGenTextures(1)
+
+	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.
+		"""
+		#resize texture
+		self._resize_texture()
+		#setup texture
+		GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture)
+		GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
+		GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
+		GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT)
+		GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE)
+		#write the buffer to the texture
+		while self._buffer:
+			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))
+			self._pointer = (self._pointer + 1)%self._num_lines
+		#begin drawing
+		GL.glEnable(GL.GL_TEXTURE_2D)
+		GL.glPushMatrix()
+		#matrix scaling
+		GL.glTranslatef(self.padding_left, self.padding_top, 0)
+		GL.glScalef(
+			float(self.width-self.padding_left-self.padding_right),
+			float(self.height-self.padding_top-self.padding_bottom),
+			1.0,
+		)
+		#draw texture with wrapping
+		GL.glBegin(GL.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)
+		GL.glTexCoord2f(0, prop_y+1-off)
+		GL.glVertex2f(0, 1)
+		GL.glTexCoord2f(prop_x, prop_y+1-off)
+		GL.glVertex2f(1, 1)
+		GL.glTexCoord2f(prop_x, prop_y)
+		GL.glVertex2f(1, 0)
+		GL.glTexCoord2f(0, prop_y)
+		GL.glVertex2f(0, 0)
+		GL.glEnd()
+		GL.glPopMatrix()
+		GL.glDisable(GL.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.
+
+                Args:
+		    x_val: the current x value
+		    y_val: the current y value
+
+                Returns:
+		    a value string with units
+		"""
+		return '%s: %s'%(self.x_label, common.eng_format(x_val, self.x_units))
+
+	def _call_callback(self, x_val, y_val):
+		if self.callback != None:
+			self.callback(x_val,y_val)
+	
+	def set_callback(self,callback):
+		self.callback = callback
+		
+	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 = unpack_color(COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))])
+			GL.glColor4f(*numpy.array(color)/255.0)
+			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
+		GL.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.
+
+                Args:
+		    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:
+			GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture)
+			data = numpy.zeros(self._num_lines*ceil_log2(self._fft_size)*4, numpy.uint8).tostring()
+			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)
+		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.
+
+                Args:
+		    color_mode: the new color mode string
+		"""
+		self.lock()
+		if color_mode in COLORS.keys():
+			self._color_mode = color_mode
+			self._legend_cache.changed(True)
+		self.update()
+		self.unlock()
+
+	def set_num_lines(self, num_lines):
+		"""
+		Set number of lines.
+		Powers of two only.
+
+                Args:
+		    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.
+
+                Args:
+		    samples: the array of floats
+		    minimum: the minimum value to scale
+		    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._legend_cache.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 = COLORS[self._color_mode][samples].tostring()
+		self._buffer.append(data)
+		self._waterfall_cache.changed(True)
+		self.unlock()
-- 
cgit v1.2.3