diff options
Diffstat (limited to 'gr-wxgui/python/wxgui/plotter')
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/__init__.py | 24 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/bar_plotter.py | 150 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/channel_plotter.py | 249 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/common.py | 149 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/gltext.py | 507 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/grid_plotter_base.py | 458 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/plotter_base.py | 220 | ||||
-rw-r--r-- | gr-wxgui/python/wxgui/plotter/waterfall_plotter.py | 294 |
8 files changed, 0 insertions, 2051 deletions
diff --git a/gr-wxgui/python/wxgui/plotter/__init__.py b/gr-wxgui/python/wxgui/plotter/__init__.py deleted file mode 100644 index 616492a3e6..0000000000 --- a/gr-wxgui/python/wxgui/plotter/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# 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. -# - -from channel_plotter import channel_plotter -from waterfall_plotter import waterfall_plotter -from bar_plotter import bar_plotter diff --git a/gr-wxgui/python/wxgui/plotter/bar_plotter.py b/gr-wxgui/python/wxgui/plotter/bar_plotter.py deleted file mode 100644 index 487db66b64..0000000000 --- a/gr-wxgui/python/wxgui/plotter/bar_plotter.py +++ /dev/null @@ -1,150 +0,0 @@ -# -# Copyright 2009 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 - -LEGEND_TEXT_FONT_SIZE = 8 -LEGEND_BOX_PADDING = 3 -MIN_PADDING = 0, 0, 0, 70 #top, right, bottom, left -#constants for the waveform storage -SAMPLES_KEY = 'samples' -COLOR_SPEC_KEY = 'color_spec' -MARKERY_KEY = 'marker' -TRIG_OFF_KEY = 'trig_off' - -################################################## -# Bar Plotter for histogram waveforms -################################################## -class bar_plotter(grid_plotter_base): - - def __init__(self, parent): - """ - Create a new bar plotter. - """ - #init - grid_plotter_base.__init__(self, parent, MIN_PADDING) - self._bars = list() - self._bar_width = .5 - self._color_spec = (0, 0, 0) - #setup bar cache - self._bar_cache = self.new_gl_cache(self._draw_bars) - #setup bar plotter - self.register_init(self._init_bar_plotter) - - def _init_bar_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def _draw_bars(self): - """ - Draw the vertical bars. - """ - bars = self._bars - num_bars = len(bars) - if num_bars == 0: return - #use scissor to prevent drawing outside grid - GL.glEnable(GL.GL_SCISSOR_TEST) - GL.glScissor( - self.padding_left, - self.padding_bottom+1, - self.width-self.padding_left-self.padding_right-1, - self.height-self.padding_top-self.padding_bottom-1, - ) - #load the points - points = list() - width = self._bar_width/2 - for i, bar in enumerate(bars): - points.extend([ - (i-width, 0), - (i+width, 0), - (i+width, bar), - (i-width, bar), - ] - ) - GL.glColor3f(*self._color_spec) - #matrix transforms - GL.glPushMatrix() - GL.glTranslatef(self.padding_left, self.padding_top, 0) - GL.glScalef( - (self.width-self.padding_left-self.padding_right), - (self.height-self.padding_top-self.padding_bottom), - 1, - ) - GL.glTranslatef(0, 1, 0) - GL.glScalef(1.0/(num_bars-1), -1.0/(self.y_max-self.y_min), 1) - GL.glTranslatef(0, -self.y_min, 0) - #draw the bars - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_QUADS, 0, len(points)) - GL.glPopMatrix() - GL.glDisable(GL.GL_SCISSOR_TEST) - - def _populate_point_label(self, x_val, y_val): - """ - Get the text the will populate the point label. - Give X and Y values for the current point. - Give values for the channel at the X coordinate. - - Args: - x_val: the current x value - y_val: the current y value - - Returns: - a string with newlines - """ - if len(self._bars) == 0: return '' - scalar = float(len(self._bars)-1)/(self.x_max - self.x_min) - #convert x val to bar # - bar_index = scalar*(x_val - self.x_min) - #if abs(bar_index - round(bar_index)) > self._bar_width/2: return '' - bar_index = int(round(bar_index)) - bar_start = (bar_index - self._bar_width/2)/scalar + self.x_min - bar_end = (bar_index + self._bar_width/2)/scalar + self.x_min - bar_value = self._bars[bar_index] - return '%s to %s\n%s: %s'%( - common.eng_format(bar_start, self.x_units), - common.eng_format(bar_end, self.x_units), - self.y_label, common.eng_format(bar_value, self.y_units), - ) - - def set_bars(self, bars, bar_width, color_spec): - """ - Set the bars. - - Args: - bars: a list of bars - bar_width: the fractional width of the bar, between 0 and 1 - color_spec: the color tuple - """ - self.lock() - self._bars = bars - self._bar_width = float(bar_width) - self._color_spec = color_spec - self._bar_cache.changed(True) - self.unlock() - - diff --git a/gr-wxgui/python/wxgui/plotter/channel_plotter.py b/gr-wxgui/python/wxgui/plotter/channel_plotter.py deleted file mode 100644 index db174c7b28..0000000000 --- a/gr-wxgui/python/wxgui/plotter/channel_plotter.py +++ /dev/null @@ -1,249 +0,0 @@ -# -# 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 - -LEGEND_TEXT_FONT_SIZE = 8 -LEGEND_BOX_PADDING = 3 -MIN_PADDING = 35, 10, 0, 0 #top, right, bottom, left -#constants for the waveform storage -SAMPLES_KEY = 'samples' -COLOR_SPEC_KEY = 'color_spec' -MARKERY_KEY = 'marker' -TRIG_OFF_KEY = 'trig_off' - -################################################## -# Channel Plotter for X Y Waveforms -################################################## -class channel_plotter(grid_plotter_base): - - def __init__(self, parent): - """ - Create a new channel plotter. - """ - #init - grid_plotter_base.__init__(self, parent, MIN_PADDING) - self.set_use_persistence(False) - #setup legend cache - self._legend_cache = self.new_gl_cache(self._draw_legend, 50) - self.enable_legend(False) - #setup waveform cache - self._waveform_cache = self.new_gl_cache(self._draw_waveforms, 50) - self._channels = dict() - #init channel plotter - self.register_init(self._init_channel_plotter) - self.callback = None - - def _init_channel_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def enable_legend(self, enable=None): - """ - Enable/disable the legend. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_legend - self.lock() - self._enable_legend = enable - self._legend_cache.changed(True) - self.unlock() - - def _draw_waveforms(self): - """ - Draw the waveforms for each channel. - Scale the waveform data to the grid using gl matrix operations. - """ - #use scissor to prevent drawing outside grid - GL.glEnable(GL.GL_SCISSOR_TEST) - GL.glScissor( - self.padding_left+1, - self.padding_bottom+1, - self.width-self.padding_left-self.padding_right-1, - self.height-self.padding_top-self.padding_bottom-1, - ) - for channel in reversed(sorted(self._channels.keys())): - samples = self._channels[channel][SAMPLES_KEY] - num_samps = len(samples) - if not num_samps: continue - #use opengl to scale the waveform - GL.glPushMatrix() - GL.glTranslatef(self.padding_left, self.padding_top, 0) - GL.glScalef( - (self.width-self.padding_left-self.padding_right), - (self.height-self.padding_top-self.padding_bottom), - 1, - ) - GL.glTranslatef(0, 1, 0) - if isinstance(samples, tuple): - x_scale, x_trans = 1.0/(self.x_max-self.x_min), -self.x_min - points = zip(*samples) - else: - x_scale, x_trans = 1.0/(num_samps-1), -self._channels[channel][TRIG_OFF_KEY] - points = zip(numpy.arange(0, num_samps), samples) - GL.glScalef(x_scale, -1.0/(self.y_max-self.y_min), 1) - GL.glTranslatef(x_trans, -self.y_min, 0) - #draw the points/lines - GL.glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) - marker = self._channels[channel][MARKERY_KEY] - if marker is None: - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_LINE_STRIP, 0, len(points)) - elif isinstance(marker, (int, float)) and marker > 0: - GL.glPointSize(marker) - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_POINTS, 0, len(points)) - GL.glPopMatrix() - GL.glDisable(GL.GL_SCISSOR_TEST) - - def _populate_point_label(self, x_val, y_val): - """ - Get the text the will populate the point label. - Give X and Y values for the current point. - Give values for the channel at the X coordinate. - - Args: - x_val: the current x value - y_val: the current y value - - Returns: - a string with newlines - """ - #create text - label_str = '%s: %s\n%s: %s'%( - self.x_label, common.eng_format(x_val, self.x_units), - self.y_label, common.eng_format(y_val, self.y_units), - ) - for channel in sorted(self._channels.keys()): - samples = self._channels[channel][SAMPLES_KEY] - num_samps = len(samples) - if not num_samps: continue - if isinstance(samples, tuple): continue - #linear interpolation - x_index = (num_samps-1)*(x_val-self.x_min)/(self.x_max-self.x_min) - x_index_low = int(math.floor(x_index)) - x_index_high = int(math.ceil(x_index)) - scale = x_index - x_index_low + self._channels[channel][TRIG_OFF_KEY] - y_value = (samples[x_index_high] - samples[x_index_low])*scale + samples[x_index_low] - if math.isnan(y_value): continue - label_str += '\n%s: %s'%(channel, common.eng_format(y_value, self.y_units)) - return label_str - - 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 legend in the upper right corner. - For each channel, draw a rectangle out of the channel color, - and overlay the channel text on top of the rectangle. - """ - if not self.enable_legend(): return - x_off = self.width - self.padding_right - LEGEND_BOX_PADDING - for i, channel in enumerate(reversed(sorted(self._channels.keys()))): - samples = self._channels[channel][SAMPLES_KEY] - if not len(samples): continue - color_spec = self._channels[channel][COLOR_SPEC_KEY] - txt = gltext.Text(channel, font_size=LEGEND_TEXT_FONT_SIZE) - w, h = txt.get_size() - #draw rect + text - GL.glColor3f(*color_spec) - self._draw_rect( - x_off - w - LEGEND_BOX_PADDING, - self.padding_top/2 - h/2 - LEGEND_BOX_PADDING, - w+2*LEGEND_BOX_PADDING, - h+2*LEGEND_BOX_PADDING, - ) - txt.draw_text(wx.Point(x_off - w, self.padding_top/2 - h/2)) - x_off -= w + 4*LEGEND_BOX_PADDING - - def clear_waveform(self, channel): - """ - Remove a waveform from the list of waveforms. - - Args: - channel: the channel key - """ - self.lock() - if channel in self._channels.keys(): - self._channels.pop(channel) - self._legend_cache.changed(True) - self._waveform_cache.changed(True) - self.unlock() - - def set_waveform(self, channel, samples=[], color_spec=(0, 0, 0), marker=None, trig_off=0): - """ - Set the waveform for a given channel. - - Args: - channel: the channel key - samples: the waveform samples - color_spec: the 3-tuple for line color - marker: None for line - trig_off: fraction of sample for trigger offset - """ - self.lock() - if channel not in self._channels.keys(): self._legend_cache.changed(True) - self._channels[channel] = { - SAMPLES_KEY: samples, - COLOR_SPEC_KEY: color_spec, - MARKERY_KEY: marker, - TRIG_OFF_KEY: trig_off, - } - self._waveform_cache.changed(True) - self.unlock() - -if __name__ == '__main__': - app = wx.PySimpleApp() - frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition) - vbox = wx.BoxSizer(wx.VERTICAL) - - plotter = channel_plotter(frame) - plotter.set_x_grid(-1, 1, .2) - plotter.set_y_grid(-1, 1, .4) - vbox.Add(plotter, 1, wx.EXPAND) - - plotter = channel_plotter(frame) - plotter.set_x_grid(-1, 1, .2) - plotter.set_y_grid(-1, 1, .4) - vbox.Add(plotter, 1, wx.EXPAND) - - frame.SetSizerAndFit(vbox) - frame.SetSize(wx.Size(800, 600)) - frame.Show() - app.MainLoop() diff --git a/gr-wxgui/python/wxgui/plotter/common.py b/gr-wxgui/python/wxgui/plotter/common.py deleted file mode 100644 index c296b1fa79..0000000000 --- a/gr-wxgui/python/wxgui/plotter/common.py +++ /dev/null @@ -1,149 +0,0 @@ -# -# Copyright 2009 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 threading -import time -import math -import wx - -################################################## -# Number formatting -################################################## -def get_exp(num): - """ - Get the exponent of the number in base 10. - - Args: - num: the floating point number - - Returns: - the exponent as an integer - """ - if num == 0: return 0 - return int(math.floor(math.log10(abs(num)))) - -def get_si_components(num): - """ - Get the SI units for the number. - Extract the coeff and exponent of the number. - The exponent will be a multiple of 3. - - Args: - num: the floating point number - - Returns: - the tuple coeff, exp, prefix - """ - num = float(num) - exp = get_exp(num) - exp -= exp%3 - exp = min(max(exp, -24), 24) #bounds on SI table below - prefix = { - 24: 'Y', 21: 'Z', - 18: 'E', 15: 'P', - 12: 'T', 9: 'G', - 6: 'M', 3: 'k', - 0: '', - -3: 'm', -6: 'u', - -9: 'n', -12: 'p', - -15: 'f', -18: 'a', - -21: 'z', -24: 'y', - }[exp] - coeff = num/10**exp - return coeff, exp, prefix - -def sci_format(num): - """ - Format a floating point number into scientific notation. - - Args: - num: the number to format - - Returns: - a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%.3ge%d'%(coeff, exp) - -def eng_format(num, units=''): - """ - Format a floating point number into engineering notation. - - Args: - num: the number to format - units: the units to append - - Returns: - a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%g%s%s%s'%(coeff, units and ' ' or '', prefix, units) - -################################################## -# Interface with thread safe lock/unlock -################################################## -class mutex(object): - _lock = threading.Lock() - def lock(self): self._lock.acquire() - def unlock(self): self._lock.release() - -################################################## -# Periodic update thread for point label -################################################## -class point_label_thread(threading.Thread, mutex): - - def __init__(self, plotter): - self._plotter = plotter - self._coor_queue = list() - #bind plotter mouse events - self._plotter.Bind(wx.EVT_MOTION, lambda evt: self.enqueue(evt.GetPosition())) - self._plotter.Bind(wx.EVT_LEAVE_WINDOW, lambda evt: self.enqueue(None)) - self._plotter.Bind(wx.EVT_RIGHT_DOWN, lambda evt: plotter.enable_point_label(not plotter.enable_point_label())) - self._plotter.Bind(wx.EVT_LEFT_DOWN, lambda evt: plotter.call_freq_callback(evt.GetPosition())) - #start the thread - threading.Thread.__init__(self) - self.start() - - def enqueue(self, coor): - self.lock() - self._coor_queue.append(coor) - self.unlock() - - def run(self): - last_ts = time.time() - last_coor = coor = None - try: - while True: - time.sleep(1.0/30.0) - self.lock() - #get most recent coor change - if self._coor_queue: - coor = self._coor_queue[-1] - self._coor_queue = list() - self.unlock() - #update if coor change, or enough time expired - if last_coor != coor or (time.time() - last_ts) > (1.0/2.0): - self._plotter.set_point_label_coordinate(coor) - last_coor = coor - last_ts = time.time() - except wx.PyDeadObjectError: pass diff --git a/gr-wxgui/python/wxgui/plotter/gltext.py b/gr-wxgui/python/wxgui/plotter/gltext.py deleted file mode 100644 index 55627bceb1..0000000000 --- a/gr-wxgui/python/wxgui/plotter/gltext.py +++ /dev/null @@ -1,507 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -# -# Provides some text display functions for wx + ogl -# Copyright (C) 2007 Christian Brugger, Stefan Hacker -# -# This program 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 2 of the License, or -# (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import wx -from OpenGL.GL import * - -""" -Optimize with psyco if possible, this gains us about 50% speed when -creating our textures in trade for about 4MBytes of additional memory usage for -psyco. If you don't like loosing the memory you have to turn the lines following -"enable psyco" into a comment while uncommenting the line after "Disable psyco". -""" -#Try to enable psyco -try: - import psyco - psyco_optimized = False -except ImportError: - psyco = None - -#Disable psyco -#psyco = None - -class TextElement(object): - """ - A simple class for using system Fonts to display - text in an OpenGL scene - """ - def __init__(self, - text = '', - font = None, - foreground = wx.BLACK, - centered = False): - """ - text (String) - Text - font (wx.Font) - Font to draw with (None = System default) - foreground (wx.Color) - Color of the text - or (wx.Bitmap)- Bitmap to overlay the text with - centered (bool) - Center the text - - Initializes the TextElement - """ - # save given variables - self._text = text - self._lines = text.split('\n') - self._font = font - self._foreground = foreground - self._centered = centered - - # init own variables - self._owner_cnt = 0 #refcounter - self._texture = None #OpenGL texture ID - self._text_size = None #x/y size tuple of the text - self._texture_size= None #x/y Texture size tuple - - # create Texture - self.createTexture() - - - #---Internal helpers - - def _getUpper2Base(self, value): - """ - Returns the lowest value with the power of - 2 greater than 'value' (2^n>value) - """ - base2 = 1 - while base2 < value: - base2 *= 2 - return base2 - - #---Functions - - def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0): - """ - position (wx.Point) - x/y Position to draw in scene - scale (float) - Scale - rotation (int) - Rotation in degree - - Draws the text to the scene - """ - #Enable necessary functions - glColor(1,1,1,1) - glEnable(GL_TEXTURE_2D) - glEnable(GL_ALPHA_TEST) #Enable alpha test - glAlphaFunc(GL_GREATER, 0) - glEnable(GL_BLEND) #Enable blending - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #Bind texture - glBindTexture(GL_TEXTURE_2D, self._texture) - - ow, oh = self._text_size - w , h = self._texture_size - #Perform transformations - glPushMatrix() - glTranslated(position.x, position.y, 0) - glRotate(-rotation, 0, 0, 1) - glScaled(scale, scale, scale) - if self._centered: - glTranslate(-w/2, -oh/2, 0) - #Draw vertices - glBegin(GL_QUADS) - glTexCoord2f(0,0); glVertex2f(0,0) - glTexCoord2f(0,1); glVertex2f(0,h) - glTexCoord2f(1,1); glVertex2f(w,h) - glTexCoord2f(1,0); glVertex2f(w,0) - glEnd() - glPopMatrix() - - #Disable features - glDisable(GL_BLEND) - glDisable(GL_ALPHA_TEST) - glDisable(GL_TEXTURE_2D) - - def createTexture(self): - """ - Creates a texture from the settings saved in TextElement, to be able to use normal - system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets - device contexts don't support alpha at all it is necessary to apply a little hack - to preserve antialiasing without sticking to a fixed background color: - - We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid - color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased - text on any surface. - - To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have - to merge our foreground color with the alpha data we just created and push it all - into a OpenGL texture and we are DONE *inhalesdelpy* - - DRAWBACK of the whole conversion thing is a really long time for creating the - texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!! - """ - # get a memory dc - dc = wx.MemoryDC() - - # Select an empty bitmap into the MemoryDC - otherwise the call to - # GetMultiLineTextExtent() may fail below - dc.SelectObject(wx.EmptyBitmap(1,1)) - - # set our font - dc.SetFont(self._font) - - # Approximate extend to next power of 2 and create our bitmap - # REMARK: You wouldn't believe how much fucking speed this little - # sucker gains compared to sizes not of the power of 2. It's like - # 500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia - # machine there don't seem to occur any losses...bad drivers? - ow, oh = dc.GetMultiLineTextExtent(self._text)[:2] - w, h = self._getUpper2Base(ow), self._getUpper2Base(oh) - - self._text_size = wx.Size(ow,oh) - self._texture_size = wx.Size(w,h) - bmp = wx.EmptyBitmap(w,h) - - - #Draw in b/w mode to bmp so we can use it as alpha channel - dc.SelectObject(bmp) - dc.SetBackground(wx.BLACK_BRUSH) - dc.Clear() - dc.SetTextForeground(wx.WHITE) - x,y = 0,0 - centered = self.centered - for line in self._lines: - if not line: line = ' ' - tw, th = dc.GetTextExtent(line) - if centered: - x = int(round((w-tw)/2)) - dc.DrawText(line, x, y) - x = 0 - y += th - #Release the dc - dc.SelectObject(wx.NullBitmap) - del dc - - #Generate a correct RGBA data string from our bmp - """ - NOTE: You could also use wx.AlphaPixelData to access the pixel data - in 'bmp' directly, but the iterator given by it is much slower than - first converting to an image and using wx.Image.GetData(). - """ - img = wx.ImageFromBitmap(bmp) - alpha = img.GetData() - - if isinstance(self._foreground, wx.Colour): - """ - If we have a static color... - """ - r,g,b = self._foreground.Get() - color = "%c%c%c" % (chr(r), chr(g), chr(b)) - - data = '' - for i in xrange(0, len(alpha)-1, 3): - data += color + alpha[i] - - elif isinstance(self._foreground, wx.Bitmap): - """ - If we have a bitmap... - """ - bg_img = wx.ImageFromBitmap(self._foreground) - bg = bg_img.GetData() - bg_width = self._foreground.GetWidth() - bg_height = self._foreground.GetHeight() - - data = '' - - for y in xrange(0, h): - for x in xrange(0, w): - if (y > (bg_height-1)) or (x > (bg_width-1)): - color = "%c%c%c" % (chr(0),chr(0),chr(0)) - else: - pos = (x+y*bg_width) * 3 - color = bg[pos:pos+3] - data += color + alpha[(x+y*w)*3] - - - # now convert it to ogl texture - self._texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, self._texture) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) - glPixelStorei(GL_UNPACK_ALIGNMENT, 2) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) - - def deleteTexture(self): - """ - Deletes the OpenGL texture object - """ - if self._texture: - if glIsTexture(self._texture): - glDeleteTextures(self._texture) - else: - self._texture = None - - def bind(self): - """ - Increase refcount - """ - self._owner_cnt += 1 - - def release(self): - """ - Decrease refcount - """ - self._owner_cnt -= 1 - - def isBound(self): - """ - Return refcount - """ - return self._owner_cnt - - def __del__(self): - """ - Destructor - """ - self.deleteTexture() - - #---Getters/Setters - - def getText(self): return self._text - def getFont(self): return self._font - def getForeground(self): return self._foreground - def getCentered(self): return self._centered - def getTexture(self): return self._texture - def getTexture_size(self): return self._texture_size - - def getOwner_cnt(self): return self._owner_cnt - def setOwner_cnt(self, value): - self._owner_cnt = value - - #---Properties - - text = property(getText, None, None, "Text of the object") - font = property(getFont, None, None, "Font of the object") - foreground = property(getForeground, None, None, "Color of the text") - centered = property(getCentered, None, None, "Is text centered") - owner_cnt = property(getOwner_cnt, setOwner_cnt, None, "Owner count") - texture = property(getTexture, None, None, "Used texture") - texture_size = property(getTexture_size, None, None, "Size of the used texture") - - -class Text(object): - """ - A simple class for using System Fonts to display text in - an OpenGL scene. The Text adds a global Cache of already - created text elements to TextElement's base functionality - so you can save some memory and increase speed - """ - _texts = [] #Global cache for TextElements - - def __init__(self, - text = 'Text', - font = None, - font_size = 8, - foreground = wx.BLACK, - centered = False, - bold = False): - """ - text (string) - displayed text - font (wx.Font) - if None, system default font will be used with font_size - font_size (int) - font size in points - foreground (wx.Color) - Color of the text - or (wx.Bitmap) - Bitmap to overlay the text with - centered (bool) - should the text drawn centered towards position? - - Initializes the text object - """ - #Init/save variables - self._aloc_text = None - self._text = text - self._font_size = font_size - self._foreground= foreground - self._centered = centered - - #Check if we are offered a font - if not font: - #if not use the system default - self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) - else: - #save it - self._font = font - - if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD) - - #Bind us to our texture - self._initText() - - #---Internal helpers - - def _initText(self): - """ - Initializes/Reinitializes the Text object by binding it - to a TextElement suitable for its current settings - """ - #Check if we already bound to a texture - if self._aloc_text: - #if so release it - self._aloc_text.release() - if not self._aloc_text.isBound(): - self._texts.remove(self._aloc_text) - self._aloc_text = None - - #Adjust our font - self._font.SetPointSize(self._font_size) - - #Search for existing element in our global buffer - for element in self._texts: - if element.text == self._text and\ - element.font == self._font and\ - element.foreground == self._foreground and\ - element.centered == self._centered: - # We already exist in global buffer ;-) - element.bind() - self._aloc_text = element - break - - if not self._aloc_text: - # We are not in the global buffer, let's create ourselves - aloc_text = self._aloc_text = TextElement(self._text, - self._font, - self._foreground, - self._centered) - aloc_text.bind() - self._texts.append(aloc_text) - - def __del__(self): - """ - Destructor - """ - aloc_text = self._aloc_text - aloc_text.release() - if not aloc_text.isBound(): - self._texts.remove(aloc_text) - - #---Functions - - def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0): - """ - position (wx.Point) - x/y Position to draw in scene - scale (float) - Scale - rotation (int) - Rotation in degree - - Draws the text to the scene - """ - - self._aloc_text.draw_text(position, scale, rotation) - - #---Setter/Getter - - def getText(self): return self._text - def setText(self, value, reinit = True): - """ - value (bool) - New Text - reinit (bool) - Create a new texture - - Sets a new text - """ - self._text = value - if reinit: - self._initText() - - def getFont(self): return self._font - def setFont(self, value, reinit = True): - """ - value (bool) - New Font - reinit (bool) - Create a new texture - - Sets a new font - """ - self._font = value - if reinit: - self._initText() - - def getFont_size(self): return self._font_size - def setFont_size(self, value, reinit = True): - """ - value (bool) - New font size - reinit (bool) - Create a new texture - - Sets a new font size - """ - self._font_size = value - if reinit: - self._initText() - - def getForeground(self): return self._foreground - def setForeground(self, value, reinit = True): - """ - value (bool) - New centered value - reinit (bool) - Create a new texture - - Sets a new value for 'centered' - """ - self._foreground = value - if reinit: - self._initText() - - def getCentered(self): return self._centered - def setCentered(self, value, reinit = True): - """ - value (bool) - New centered value - reinit (bool) - Create a new texture - - Sets a new value for 'centered' - """ - self._centered = value - if reinit: - self._initText() - - def get_size(self): - """ - Returns a text size tuple - """ - return self._aloc_text._text_size - - def getTexture_size(self): - """ - Returns a texture size tuple - """ - return self._aloc_text.texture_size - - def getTextElement(self): - """ - Returns the text element bound to the Text class - """ - return self._aloc_text - - def getTexture(self): - """ - Returns the texture of the bound TextElement - """ - return self._aloc_text.texture - - - #---Properties - - text = property(getText, setText, None, "Text of the object") - font = property(getFont, setFont, None, "Font of the object") - font_size = property(getFont_size, setFont_size, None, "Font size") - foreground = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text") - centered = property(getCentered, setCentered, None, "Display the text centered") - texture_size = property(getTexture_size, None, None, "Size of the used texture") - texture = property(getTexture, None, None, "Texture of bound TextElement") - text_element = property(getTextElement,None , None, "TextElement bound to this class") - -#Optimize critical functions -if psyco and not psyco_optimized: - psyco.bind(TextElement.createTexture) - psyco_optimized = True diff --git a/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py b/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py deleted file mode 100644 index bc48ad72f5..0000000000 --- a/gr-wxgui/python/wxgui/plotter/grid_plotter_base.py +++ /dev/null @@ -1,458 +0,0 @@ -# -# Copyright 2009 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 -import wx.glcanvas -from OpenGL import GL -import common -from plotter_base import plotter_base -import gltext -import math - -GRID_LINE_COLOR_SPEC = (.7, .7, .7) #gray -GRID_BORDER_COLOR_SPEC = (0, 0, 0) #black -TICK_TEXT_FONT_SIZE = 9 -TITLE_TEXT_FONT_SIZE = 13 -UNITS_TEXT_FONT_SIZE = 9 -AXIS_LABEL_PADDING = 5 -TICK_LABEL_PADDING = 5 -TITLE_LABEL_PADDING = 7 -POINT_LABEL_FONT_SIZE = 8 -POINT_LABEL_COLOR_SPEC = (1, 1, 0.5, 0.75) -POINT_LABEL_PADDING = 3 -POINT_LABEL_OFFSET = 10 -GRID_LINE_DASH_LEN = 4 - -################################################## -# Grid Plotter Base Class -################################################## -class grid_plotter_base(plotter_base): - - def __init__(self, parent, min_padding=(0, 0, 0, 0)): - plotter_base.__init__(self, parent) - #setup grid cache - self._grid_cache = self.new_gl_cache(self._draw_grid, 25) - self.enable_grid_lines(True) - #setup padding - self.padding_top_min, self.padding_right_min, self.padding_bottom_min, self.padding_left_min = min_padding - #store title and unit strings - self.set_title('Title') - self.set_x_label('X Label') - self.set_y_label('Y Label') - #init the grid to some value - self.set_x_grid(-1, 1, 1) - self.set_y_grid(-1, 1, 1) - #setup point label cache - self._point_label_cache = self.new_gl_cache(self._draw_point_label, 75) - self.enable_point_label(False) - self.enable_grid_aspect_ratio(False) - self.set_point_label_coordinate(None) - common.point_label_thread(self) - #init grid plotter - self.register_init(self._init_grid_plotter) - - def _init_grid_plotter(self): - """ - Run gl initialization tasks. - """ - GL.glEnableClientState(GL.GL_VERTEX_ARRAY) - - def set_point_label_coordinate(self, coor): - """ - Set the point label coordinate. - - Args: - coor: the coordinate x, y tuple or None - """ - self.lock() - self._point_label_coordinate = coor - self._point_label_cache.changed(True) - self.update() - self.unlock() - - def call_freq_callback(self, coor): - try: - x, y = self._point_label_coordinate - except: - return - if x < self.padding_left or x > self.width-self.padding_right: return - if y < self.padding_top or y > self.height-self.padding_bottom: return - #scale to window bounds - x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) - y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) - #scale to grid bounds - x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min - y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min - self._call_callback(x_val, y_val) - - def enable_grid_aspect_ratio(self, enable=None): - """ - Enable/disable the grid aspect ratio. - If enabled, enforce the aspect ratio on the padding: - horizontal_padding:vertical_padding == width:height - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_grid_aspect_ratio - self.lock() - self._enable_grid_aspect_ratio = enable - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def enable_point_label(self, enable=None): - """ - Enable/disable the point label. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_point_label - self.lock() - self._enable_point_label = enable - self._point_label_cache.changed(True) - self.unlock() - - def set_title(self, title): - """ - Set the title. - - Args: - title the title string - """ - self.lock() - self.title = title - self._grid_cache.changed(True) - self.unlock() - - def set_x_label(self, x_label, x_units=''): - """ - Set the x label and units. - - Args: - x_label: the x label string - x_units: the x units string - """ - self.lock() - self.x_label = x_label - self.x_units = x_units - self._grid_cache.changed(True) - self.unlock() - - def set_y_label(self, y_label, y_units=''): - """ - Set the y label and units. - - Args: - y_label: the y label string - y_units: the y units string - """ - self.lock() - self.y_label = y_label - self.y_units = y_units - self._grid_cache.changed(True) - self.unlock() - - def set_x_grid(self, minimum, maximum, step, scale=False): - """ - Set the x grid parameters. - - Args: - minimum: the left-most value - maximum: the right-most value - step: the grid spacing - scale: true to scale the x grid - """ - self.lock() - self.x_min = float(minimum) - self.x_max = float(maximum) - self.x_step = float(step) - if scale: - coeff, exp, prefix = common.get_si_components(max(abs(self.x_min), abs(self.x_max))) - self.x_scalar = 10**(-exp) - self.x_prefix = prefix - else: - self.x_scalar = 1.0 - self.x_prefix = '' - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def set_y_grid(self, minimum, maximum, step, scale=False): - """ - Set the y grid parameters. - - Args: - minimum: the bottom-most value - maximum: the top-most value - step: the grid spacing - scale: true to scale the y grid - """ - self.lock() - self.y_min = float(minimum) - self.y_max = float(maximum) - self.y_step = float(step) - if scale: - coeff, exp, prefix = common.get_si_components(max(abs(self.y_min), abs(self.y_max))) - self.y_scalar = 10**(-exp) - self.y_prefix = prefix - else: - self.y_scalar = 1.0 - self.y_prefix = '' - for cache in self._gl_caches: cache.changed(True) - self.unlock() - - def _draw_grid(self): - """ - Create the x, y, tick, and title labels. - Resize the padding for the labels. - Draw the border, grid, title, and labels. - """ - ################################################## - # Create GL text labels - ################################################## - #create x tick labels - x_tick_labels = [(tick, self._get_tick_label(tick, self.x_units)) - for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar)] - #create x tick labels - y_tick_labels = [(tick, self._get_tick_label(tick, self.y_units)) - for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar)] - #create x label - x_label_str = self.x_units and "%s (%s%s)"%(self.x_label, self.x_prefix, self.x_units) or self.x_label - x_label = gltext.Text(x_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - #create y label - y_label_str = self.y_units and "%s (%s%s)"%(self.y_label, self.y_prefix, self.y_units) or self.y_label - y_label = gltext.Text(y_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - #create title - title_label = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) - ################################################## - # Resize the padding - ################################################## - self.padding_top = max(2*TITLE_LABEL_PADDING + title_label.get_size()[1], self.padding_top_min) - self.padding_right = max(2*TICK_LABEL_PADDING, self.padding_right_min) - self.padding_bottom = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + x_label.get_size()[1] + max([label.get_size()[1] for tick, label in x_tick_labels]), self.padding_bottom_min) - self.padding_left = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + y_label.get_size()[1] + max([label.get_size()[0] for tick, label in y_tick_labels]), self.padding_left_min) - #enforce padding aspect ratio if enabled - if self.enable_grid_aspect_ratio(): - w_over_h_ratio = float(self.width)/float(self.height) - horizontal_padding = float(self.padding_right + self.padding_left) - veritical_padding = float(self.padding_top + self.padding_bottom) - if w_over_h_ratio > horizontal_padding/veritical_padding: - #increase the horizontal padding - new_padding = veritical_padding*w_over_h_ratio - horizontal_padding - #distribute the padding to left and right - self.padding_left += int(round(new_padding/2)) - self.padding_right += int(round(new_padding/2)) - else: - #increase the vertical padding - new_padding = horizontal_padding/w_over_h_ratio - veritical_padding - #distribute the padding to top and bottom - self.padding_top += int(round(new_padding/2)) - self.padding_bottom += int(round(new_padding/2)) - ################################################## - # Draw Grid X - ################################################## - for tick, label in x_tick_labels: - scaled_tick = (self.width-self.padding_left-self.padding_right)*\ - (tick/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + self.padding_left - self._draw_grid_line( - (scaled_tick, self.padding_top), - (scaled_tick, self.height-self.padding_bottom), - ) - w, h = label.get_size() - label.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) - ################################################## - # Draw Grid Y - ################################################## - for tick, label in y_tick_labels: - scaled_tick = (self.height-self.padding_top-self.padding_bottom)*\ - (1 - (tick/self.y_scalar-self.y_min)/(self.y_max-self.y_min)) + self.padding_top - self._draw_grid_line( - (self.padding_left, scaled_tick), - (self.width-self.padding_right, scaled_tick), - ) - w, h = label.get_size() - label.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) - ################################################## - # Draw Border - ################################################## - GL.glColor3f(*GRID_BORDER_COLOR_SPEC) - self._draw_rect( - self.padding_left, - self.padding_top, - self.width - self.padding_right - self.padding_left, - self.height - self.padding_top - self.padding_bottom, - fill=False, - ) - ################################################## - # Draw Labels - ################################################## - #draw title label - title_label.draw_text(wx.Point(self.width/2.0, TITLE_LABEL_PADDING + title_label.get_size()[1]/2)) - #draw x labels - x_label.draw_text(wx.Point( - (self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left, - self.height-(AXIS_LABEL_PADDING + x_label.get_size()[1]/2), - ) - ) - #draw y labels - y_label.draw_text(wx.Point( - AXIS_LABEL_PADDING + y_label.get_size()[1]/2, - (self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top, - ), rotation=90, - ) - - def _get_tick_label(self, tick, unit): - """ - Format the tick value and create a gl text. - - Args: - tick: the floating point tick value - unit: the axis unit - - Returns: - the tick label text - """ - if unit: tick_str = common.sci_format(tick) - else: tick_str = common.eng_format(tick) - return gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE) - - def _get_ticks(self, min, max, step, scalar): - """ - Determine the positions for the ticks. - - Args: - min: the lower bound - max: the upper bound - step: the grid spacing - scalar: the grid scaling - - Returns: - a list of tick positions between min and max - """ - #cast to float - min = float(min) - max = float(max) - step = float(step) - #check for valid numbers - try: - assert step > 0 - assert max > min - assert max - min > step - except AssertionError: return [-1, 1] - #determine the start and stop value - start = int(math.ceil(min/step)) - stop = int(math.floor(max/step)) - return [i*step*scalar for i in range(start, stop+1)] - - def enable_grid_lines(self, enable=None): - """ - Enable/disable the grid lines. - - Args: - enable: true to enable - - Returns: - the enable state when None - """ - if enable is None: return self._enable_grid_lines - self.lock() - self._enable_grid_lines = enable - self._grid_cache.changed(True) - self.unlock() - - def _draw_grid_line(self, coor1, coor2): - """ - Draw a dashed line from coor1 to coor2. - - Args: - corr1: a tuple of x, y - corr2: a tuple of x, y - """ - if not self.enable_grid_lines(): return - length = math.sqrt((coor1[0] - coor2[0])**2 + (coor1[1] - coor2[1])**2) - num_points = int(length/GRID_LINE_DASH_LEN) - #calculate points array - points = [( - coor1[0] + i*(coor2[0]-coor1[0])/(num_points - 1), - coor1[1] + i*(coor2[1]-coor1[1])/(num_points - 1) - ) for i in range(num_points)] - #set color and draw - GL.glColor3f(*GRID_LINE_COLOR_SPEC) - GL.glVertexPointerf(points) - GL.glDrawArrays(GL.GL_LINES, 0, len(points)) - - def _draw_rect(self, x, y, width, height, fill=True): - """ - Draw a rectangle on the x, y plane. - X and Y are the top-left corner. - - Args: - x: the left position of the rectangle - y: the top position of the rectangle - width: the width of the rectangle - height: the height of the rectangle - fill: true to color inside of rectangle - """ - GL.glBegin(fill and GL.GL_QUADS or GL.GL_LINE_LOOP) - GL.glVertex2f(x, y) - GL.glVertex2f(x+width, y) - GL.glVertex2f(x+width, y+height) - GL.glVertex2f(x, y+height) - GL.glEnd() - - def _draw_point_label(self): - """ - Draw the point label for the last mouse motion coordinate. - The mouse coordinate must be an X, Y tuple. - The label will be drawn at the X, Y coordinate. - The values of the X, Y coordinate will be scaled to the current X, Y bounds. - """ - if not self.enable_point_label(): return - if not self._point_label_coordinate: return - x, y = self._point_label_coordinate - if x < self.padding_left or x > self.width-self.padding_right: return - if y < self.padding_top or y > self.height-self.padding_bottom: return - #scale to window bounds - x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) - y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) - #scale to grid bounds - x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min - y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min - #create text - label_str = self._populate_point_label(x_val, y_val) - if not label_str: return - txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) - w, h = txt.get_size() - #enable transparency - GL.glEnable(GL.GL_BLEND) - GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - #draw rect + text - GL.glColor4f(*POINT_LABEL_COLOR_SPEC) - if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + POINT_LABEL_OFFSET - else: x += POINT_LABEL_OFFSET - self._draw_rect(x, y-h-2*POINT_LABEL_PADDING, w+2*POINT_LABEL_PADDING, h+2*POINT_LABEL_PADDING) - txt.draw_text(wx.Point(x+POINT_LABEL_PADDING, y-h-POINT_LABEL_PADDING)) diff --git a/gr-wxgui/python/wxgui/plotter/plotter_base.py b/gr-wxgui/python/wxgui/plotter/plotter_base.py deleted file mode 100644 index ca904908ec..0000000000 --- a/gr-wxgui/python/wxgui/plotter/plotter_base.py +++ /dev/null @@ -1,220 +0,0 @@ -# -# 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 -import wx.glcanvas -from OpenGL import GL -import common - -BACKGROUND_COLOR_SPEC = (1, 0.976, 1, 1) #creamy white - -################################################## -# GL caching interface -################################################## -class gl_cache(object): - """ - Cache a set of gl drawing routines in a compiled list. - """ - - def __init__(self, draw): - """ - Create a new cache. - - Args: - draw: a function to draw gl stuff - """ - self.changed(True) - self._draw = draw - - def init(self): - """ - To be called when gl initializes. - Create a new compiled list. - """ - self._grid_compiled_list_id = GL.glGenLists(1) - - def draw(self): - """ - Draw the gl stuff using a compiled list. - If changed, reload the compiled list. - """ - if self.changed(): - GL.glNewList(self._grid_compiled_list_id, GL.GL_COMPILE) - self._draw() - GL.glEndList() - self.changed(False) - #draw the grid - GL.glCallList(self._grid_compiled_list_id) - - def changed(self, state=None): - """ - Set the changed flag if state is not None. - Otherwise return the changed flag. - """ - if state is None: return self._changed - self._changed = state - -################################################## -# OpenGL WX Plotter Canvas -################################################## -class plotter_base(wx.glcanvas.GLCanvas, common.mutex): - """ - Plotter base class for all plot types. - """ - - def __init__(self, parent): - """ - Create a new plotter base. - Initialize the GLCanvas with double buffering. - Initialize various plotter flags. - Bind the paint and size events. - - Args: - parent: the parent widgit - """ - attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) - wx.glcanvas.GLCanvas.__init__(self, parent, wx.ID_ANY, attribList=attribList); # Specifically use the CTOR which does NOT create an implicit GL context - self._gl_ctx = wx.glcanvas.GLContext(self) # Create the explicit GL context - self.use_persistence=False - self.persist_alpha=2.0/15 - self.clear_accum=True - self._gl_init_flag = False - self._resized_flag = True - self._init_fcns = list() - self._draw_fcns = list() - self._gl_caches = list() - self.Bind(wx.EVT_PAINT, self._on_paint) - self.Bind(wx.EVT_SIZE, self._on_size) - self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) - - def set_use_persistence(self,enable): - self.use_persistence=enable - self.clear_accum=True - - def set_persist_alpha(self,analog_alpha): - self.persist_alpha=analog_alpha - - def new_gl_cache(self, draw_fcn, draw_pri=50): - """ - Create a new gl cache. - Register its draw and init function. - - Returns: - the new cache object - """ - cache = gl_cache(draw_fcn) - self.register_init(cache.init) - self.register_draw(cache.draw, draw_pri) - self._gl_caches.append(cache) - return cache - - def register_init(self, init_fcn): - self._init_fcns.append(init_fcn) - - def register_draw(self, draw_fcn, draw_pri=50): - """ - Register a draw function with a layer priority. - Large pri values are drawn last. - Small pri values are drawn first. - """ - for i in range(len(self._draw_fcns)): - if draw_pri < self._draw_fcns[i][0]: - self._draw_fcns.insert(i, (draw_pri, draw_fcn)) - return - self._draw_fcns.append((draw_pri, draw_fcn)) - - def _on_size(self, event): - """ - Flag the resize event. - The paint event will handle the actual resizing. - """ - self.lock() - self._resized_flag = True - self.clear_accum=True - self.unlock() - - def _on_paint(self, event): - """ - Respond to paint events. - Initialize GL if this is the first paint event. - Resize the view port if the width or height changed. - Redraw the screen, calling the draw functions. - """ - if not self.IsShownOnScreen(): # Cannot realise a GL context on OS X if window is not yet shown - return - # create device context (needed on Windows, noop on X) - dc = None - if event.GetEventObject(): # Only create DC if paint triggered by WM message (for OS X) - dc = wx.PaintDC(self) - self.lock() - self.SetCurrent(self._gl_ctx) # Real the explicit GL context - - # check if gl was initialized - if not self._gl_init_flag: - GL.glClearColor(*BACKGROUND_COLOR_SPEC) - for fcn in self._init_fcns: fcn() - self._gl_init_flag = True - - # check for a change in window size - if self._resized_flag: - self.width, self.height = self.GetSize() - GL.glMatrixMode(GL.GL_PROJECTION) - GL.glLoadIdentity() - GL.glOrtho(0, self.width, self.height, 0, 1, 0) - GL.glMatrixMode(GL.GL_MODELVIEW) - GL.glLoadIdentity() - GL.glViewport(0, 0, self.width, self.height) - for cache in self._gl_caches: cache.changed(True) - self._resized_flag = False - - # clear buffer if needed - if self.clear_accum or not self.use_persistence: - GL.glClear(GL.GL_COLOR_BUFFER_BIT) - self.clear_accum=False - - # apply fading - if self.use_persistence: - GL.glEnable(GL.GL_BLEND) - GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - - GL.glBegin(GL.GL_QUADS) - GL.glColor4f(1,1,1,self.persist_alpha) - GL.glVertex2f(0, self.height) - GL.glVertex2f(self.width, self.height) - GL.glVertex2f(self.width, 0) - GL.glVertex2f(0, 0) - GL.glEnd() - - GL.glDisable(GL.GL_BLEND) - - # draw functions - for fcn in self._draw_fcns: fcn[1]() - - # show result - self.SwapBuffers() - self.unlock() - - def update(self): - """ - Force a paint event. - """ - if not self._gl_init_flag: return - wx.PostEvent(self, wx.PaintEvent()) diff --git a/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py b/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py deleted file mode 100644 index a5601e25bc..0000000000 --- a/gr-wxgui/python/wxgui/plotter/waterfall_plotter.py +++ /dev/null @@ -1,294 +0,0 @@ -# -# 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() |