[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Commit-gnuradio] r8814 - gnuradio/branches/features/experimental-gui
From: |
jblum |
Subject: |
[Commit-gnuradio] r8814 - gnuradio/branches/features/experimental-gui |
Date: |
Sun, 6 Jul 2008 16:27:25 -0600 (MDT) |
Author: jblum
Date: 2008-07-06 16:27:24 -0600 (Sun, 06 Jul 2008)
New Revision: 8814
Added:
gnuradio/branches/features/experimental-gui/common.py
gnuradio/branches/features/experimental-gui/constsink.py
gnuradio/branches/features/experimental-gui/fftsink.py
gnuradio/branches/features/experimental-gui/gltext.py
gnuradio/branches/features/experimental-gui/plotter.py
gnuradio/branches/features/experimental-gui/scopesink.py
Modified:
gnuradio/branches/features/experimental-gui/README
Log:
moved sinks and plotter to features branch
Modified: gnuradio/branches/features/experimental-gui/README
===================================================================
--- gnuradio/branches/features/experimental-gui/README 2008-07-06 21:42:27 UTC
(rev 8813)
+++ gnuradio/branches/features/experimental-gui/README 2008-07-06 22:27:24 UTC
(rev 8814)
@@ -30,6 +30,30 @@
Utility class for simplifying USRP operation. May eventually make it into
blks2.
+* gltext.py:
+
+Utility class for plotting text to screen with wx + opengl
+
+* common.py:
+
+Classes and functions shared by the opengl displays
+
+* plotter.py:
+
+Interface to wx + opengl plotting widgit
+
+* fftsink.py:
+
+wxgui window for fft plots
+
+* scopesink.py:
+
+wxgui window for scope plots
+
+* constsink.py:
+
+wxgui window for constellation plots
+
STATUS:
Currently, the top block and controller are written, but the GUI is not. There
Copied: gnuradio/branches/features/experimental-gui/common.py (from rev 8813,
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/common.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/common.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/common.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,181 @@
+#
+# 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 threading
+import numpy
+import math
+import wx
+
+##################################################
+# Input Watcher Thread
+##################################################
+class input_watcher(threading.Thread):
+ """!
+ Input watcher thread runs forever.
+ Read messages from the message queue.
+ Forward messages to the message handler.
+ """
+
+ def __init__ (self, msgq, handle_msg):
+ threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.msgq = msgq
+ self.handle_msg = handle_msg
+ self.keep_running = True
+ self.start()
+
+ def run(self):
+ while self.keep_running:
self.handle_msg(self.msgq.delete_head())
+
+##################################################
+# WX Shared Classes
+##################################################
+class LabelText(wx.StaticText):
+ """!
+ Label text to give the wx plots a uniform look.
+ Get the default label text and set the font bold.
+ """
+
+ def __init__(self, parent, label):
+ wx.StaticText.__init__(self, parent, -1, label)
+ font = self.GetFont()
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
+ self.SetFont(font)
+
+class IncrDecrButtons(wx.BoxSizer):
+ """!
+ A horizontal box sizer with a increment and a decrement button.
+ """
+
+ def __init__(self, parent, on_incr, on_decr):
+ """!
+ @param parent the parent window
+ @param on_incr the event handler for increment
+ @param on_decr the event handler for decrement
+ """
+ wx.BoxSizer.__init__(self, wx.HORIZONTAL)
+ self._incr_button = wx.Button(parent, -1, '+',
style=wx.BU_EXACTFIT)
+ self._incr_button.Bind(wx.EVT_BUTTON, on_incr)
+ self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL)
+ self._decr_button = wx.Button(parent, -1, ' - ',
style=wx.BU_EXACTFIT)
+ self._decr_button.Bind(wx.EVT_BUTTON, on_decr)
+ self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ def Enable(self):
+ self._incr_button.Enable()
+ self._decr_button.Enable()
+
+ def Disable(self):
+ self._incr_button.Disable()
+ self._decr_button.Disable()
+
+ def _set_slider_value(self, real_value):
+ """!
+ Translate the real numerical value into a slider value and,
+ write the value to the slider.
+ @param real_value the numeric value the slider should represent
+ """
+ slider_value = (float(real_value) -
self.min)*self.num_steps/(self.max - self.min)
+ self.slider.SetValue(slider_value)
+
+ def _handle_scroll(self, event=None):
+ """!
+ A scroll event is detected. Read the slider, call the callback.
+ """
+ slider_value = self.slider.GetValue()
+ new_value = slider_value*(self.max - self.min)/self.num_steps +
self.min
+ self.text_box.SetValue(str(new_value))
+ self._value = new_value
+ self.callback(self._value)
+
+ def _handle_enter(self, event=None):
+ """!
+ An enter key was pressed. Read the text box, call the callback.
+ """
+ new_value = float(self.text_box.GetValue())
+ self._set_slider_value(new_value)
+ self._value = new_value
+ self.callback(self._value)
+
+##################################################
+# Shared Functions
+##################################################
+def get_exp(num):
+ """!
+ Get the exponent of the number in base 10.
+ @param num the floating point number
+ @return the exponent as an integer
+ """
+ if num == 0: return 0
+ return int(math.floor(math.log10(abs(num))))
+
+def get_clean_num(num):
+ """!
+ Get the closest clean number match to num with bases 1, 2, 5.
+ @param num the number
+ @return the closest number
+ """
+ exp = get_exp(num)
+ nums = numpy.array((1, 2, 5, 10))*(10**exp)
+ return nums[numpy.argmin(numpy.abs(nums - num))]
+
+def get_clean_incr(num):
+ """!
+ Get the next higher clean number with bases 1, 2, 5.
+ @param num the number
+ @return the next higher number
+ """
+ exp = get_exp(num)
+ num = get_clean_num(num)
+ base = int(num/10**exp)
+ return {
+ 1: 2,
+ 2: 5,
+ 5: 10,
+ }[base]*(10**exp)
+
+def get_clean_decr(num):
+ """!
+ Get the next lower clean number with bases 1, 2, 5.
+ @param num the number
+ @return the next lower number
+ """
+ exp = get_exp(num)
+ num = get_clean_num(num)
+ base = int(num/10**exp)
+ return {
+ 1: .5,
+ 2: 1,
+ 5: 2,
+ }[base]*(10**exp)
+
+def get_min_max(samples):
+ """!
+ Get the minimum and maximum bounds for an array of samples.
+ @param samples the array of real values
+ @return a tuple of min, max
+ """
+ scale_factor = 3
+ mean = numpy.average(samples)
+ rms = scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5)
+ min = mean - rms
+ max = mean + rms
+ return min, max
Copied: gnuradio/branches/features/experimental-gui/constsink.py (from rev
8813, gnuradio/branches/developers/jblum/gr-wxglgui/src/python/constsink.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/constsink.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/constsink.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,308 @@
+#
+# 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.
+#
+
+##################################################
+# Imports
+##################################################
+from gnuradio import gr, blks2
+import plotter
+import common
+import wx
+import numpy
+import math
+
+##################################################
+# Constants
+##################################################
+SLIDER_STEPS = 100
+ALPHA_SLIDER_MIN, ALPHA_SLIDER_MAX = -6, -0.7
+GAIN_MU_SLIDER_MIN, GAIN_MU_SLIDER_MAX = -6, -0.7
+DEFAULT_FRAME_RATE = 30
+DEFAULT_WIN_SIZE = (500, 400)
+DEFAULT_CONST_SIZE = 1024
+CONST_PLOT_COLOR_SPEC = (0, 0, 1)
+MARKER_TYPES = (
+ ('Line', None),
+ ('Plus', '+'),
+ ('Dot', '.'),
+)
+
+##################################################
+# Constellation window control panel
+##################################################
+class control_panel(wx.Panel):
+ """!
+ A control panel with wx widgits to control the plotter.
+ """
+ def __init__(self, parent):
+ """!
+ Create a new control panel.
+ @param parent the wx parent window
+ """
+ self.parent = parent
+ wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
+ control_box = wx.BoxSizer(wx.VERTICAL)
+
+ #begin control box
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Options'), 0,
wx.ALIGN_CENTER)
+ control_box.AddSpacer(2)
+ #alpha
+ control_box.AddStretchSpacer()
+ self.alpha_label = wx.StaticText(self, -1, '')
+ control_box.Add(self.alpha_label, 0, wx.EXPAND)
+ self.alpha_slider = wx.Slider(self, -1, 0, 0, SLIDER_STEPS,
style=wx.SL_HORIZONTAL)
+ self.alpha_slider.Bind(wx.EVT_SLIDER, self._on_alpha)
+ control_box.Add(self.alpha_slider, 0, wx.EXPAND)
+ #gain_mu
+ control_box.AddStretchSpacer()
+ self.gain_mu_label = wx.StaticText(self, -1, '0'*15)
+ control_box.Add(self.gain_mu_label, 0, wx.EXPAND)
+ self.gain_mu_slider = wx.Slider(self, -1, 0, 0, SLIDER_STEPS,
style=wx.SL_HORIZONTAL)
+ self.gain_mu_slider.Bind(wx.EVT_SLIDER, self._on_gain_mu)
+ control_box.Add(self.gain_mu_slider, 0, wx.EXPAND)
+ #run/stop
+ control_box.AddStretchSpacer()
+ self.run_button = wx.Button(self, -1, '', style=wx.BU_EXACTFIT)
+ self.run_button.Bind(wx.EVT_BUTTON, self._on_run)
+ control_box.Add(self.run_button, 0, wx.EXPAND)
+
+ #set sizer
+ self.SetSizerAndFit(control_box)
+ #update
+ self.update()
+
+ def update(self):
+ #update mpsk params
+ self.alpha_label.SetLabel('Alpha: %.3g'%self.parent.alpha)
+ slider_value =
SLIDER_STEPS*(math.log10(self.parent.alpha)-ALPHA_SLIDER_MIN)/(ALPHA_SLIDER_MAX-ALPHA_SLIDER_MIN)
+ if abs(slider_value - self.alpha_slider.GetValue()) > 1:
+ self.alpha_slider.SetValue(slider_value)
+ self.gain_mu_label.SetLabel('Gain Mu: %.3g'%self.parent.gain_mu)
+ slider_value =
SLIDER_STEPS*(math.log10(self.parent.gain_mu)-GAIN_MU_SLIDER_MIN)/(GAIN_MU_SLIDER_MAX-GAIN_MU_SLIDER_MIN)
+ if abs(slider_value - self.gain_mu_slider.GetValue()) > 1:
+ self.gain_mu_slider.SetValue(slider_value)
+ #update the run/stop button
+ if self.parent.running: self.run_button.SetLabel('Stop')
+ else: self.run_button.SetLabel('Run')
+
+ ##################################################
+ # Event handlers
+ ##################################################
+ def _on_run(self, event): self.parent.set_run(not self.parent.running)
+ def _on_alpha(self, event):
+
self.parent.set_alpha(10**(float(ALPHA_SLIDER_MAX-ALPHA_SLIDER_MIN)*self.alpha_slider.GetValue()/SLIDER_STEPS
+ ALPHA_SLIDER_MIN))
+ def _on_gain_mu(self, event):
+
self.parent.set_gain_mu(10**(float(GAIN_MU_SLIDER_MAX-GAIN_MU_SLIDER_MIN)*self.gain_mu_slider.GetValue()/SLIDER_STEPS
+ GAIN_MU_SLIDER_MIN))
+
+##################################################
+# Constellation window with plotter and control panel
+##################################################
+class const_window(wx.Panel):
+ def __init__(
+ self,
+ parent,
+ size,
+ title,
+ alpha,
+ gain_mu,
+ #callbacks to mpsk recv
+ ext_set_alpha,
+ ext_set_beta,
+ ext_set_gain_mu,
+ ext_set_gain_omega,
+ ):
+ #setup
+ self.running = True
+ self.x_divs = 8
+ self.y_divs = 8
+ #mspk settings
+ self.alpha = alpha
+ self.gain_mu = gain_mu
+ #callbacks to mpsk recv
+ self.ext_set_alpha = ext_set_alpha
+ self.ext_set_beta = ext_set_beta
+ self.ext_set_gain_mu = ext_set_gain_mu
+ self.ext_set_gain_omega = ext_set_gain_omega
+ #init panel and plot
+ wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
+ self.plotter = plotter.grid_plotter(self)
+ self.plotter.SetSize(wx.Size(*size))
+ self.plotter.set_title(title)
+ self.plotter.set_x_units('Inphase')
+ self.plotter.set_y_units('Quadrature')
+ #setup the box with plot and controls
+ self.control_panel = control_panel(self)
+ main_box = wx.BoxSizer(wx.HORIZONTAL)
+ main_box.Add(self.plotter, 1, wx.EXPAND)
+ main_box.Add(self.control_panel, 0, wx.EXPAND)
+ self.SetSizerAndFit(main_box)
+ #update
+ self.update()
+
+ def plot(self, samples):
+ """!
+ Plot the samples onto the complex grid.
+ @param samples the array of complex samples
+ """
+ if not self.running: return
+ real = numpy.real(samples)
+ imag = numpy.imag(samples)
+ #plot
+ self.plotter.set_waveform(
+ channel=0,
+ samples=(real, imag),
+ color_spec=CONST_PLOT_COLOR_SPEC,
+ marker='.',
+ )
+ #update the plotter
+ self.plotter.update()
+
+ def update(self):
+ #update mpsk
+ self.ext_set_alpha(self.alpha)
+ self.ext_set_beta(.25*self.alpha**2)
+ self.ext_set_gain_mu(self.gain_mu)
+ self.ext_set_gain_omega(.25*self.gain_mu**2)
+ #update the x axis
+ x_max = 1.2
+ self.plotter.set_x_grid(-x_max, x_max,
common.get_clean_num(2.0*x_max/self.x_divs))
+ #update the y axis
+ y_max = 1.2
+ self.plotter.set_y_grid(-y_max, y_max,
common.get_clean_num(2.0*y_max/self.y_divs))
+ #update control panel and plotter
+ self.control_panel.update()
+ self.plotter.update()
+
+ ##################################################
+ # Set parameters on-the-fly
+ ##################################################
+ def set_alpha(self, alpha):
+ self.alpha = alpha
+ self.update()
+ def set_gain_mu(self, gain_mu):
+ self.gain_mu = gain_mu
+ self.update()
+ def set_run(self, running):
+ self.running = running
+ self.update()
+
+##################################################
+# Constellation sink block
+##################################################
+class const_sink_c(gr.hier_block2):
+ """!
+ A constellation block with a gui window.
+ """
+
+ def __init__(
+ self,
+ parent,
+ title='',
+ sample_rate=1,
+ size=DEFAULT_WIN_SIZE,
+ frame_rate=DEFAULT_FRAME_RATE,
+ const_size=DEFAULT_CONST_SIZE,
+ #mpsk recv params
+ M=4,
+ theta=0,
+ alpha=0.005,
+ fmax=0.06,
+ mu=0.5,
+ gain_mu=0.005,
+ symbol_rate=1,
+ omega_rel=0.005,
+ ):
+ #init
+ gr.hier_block2.__init__(
+ self,
+ "const_sink",
+ gr.io_signature(1, 1, gr.sizeof_gr_complex),
+ gr.io_signature(0, 0, 0),
+ )
+ #blocks
+ self.sd = blks2.stream_to_vector_decimator(
+ item_size=gr.sizeof_gr_complex,
+ sample_rate=sample_rate,
+ vec_rate=frame_rate,
+ vec_len=const_size,
+ )
+ beta = .25*alpha**2 #redundant, will be updated
+ fmin = -fmax
+ gain_omega = .25*gain_mu**2 #redundant, will be updated
+ omega = 1 #set_sample_rate will update this
+ self.symbol_rate = symbol_rate
+ self.sync = gr.mpsk_receiver_cc(
+ M, #psk order
+ theta,
+ alpha,
+ beta,
+ fmin,
+ fmax,
+ mu,
+ gain_mu,
+ omega,
+ gain_omega,
+ omega_rel,
+ )
+ agc = gr.feedforward_agc_cc(16, 1)
+ msgq = gr.msg_queue(2)
+ sink = gr.message_sink(gr.sizeof_gr_complex*const_size, msgq,
True)
+ #connect
+ self.connect(self, self.sync, agc, self.sd, sink)
+ #create window
+ self.win = const_window(
+ parent=parent,
+ size=size,
+ title=title,
+ alpha=alpha,
+ gain_mu=gain_mu,
+ #callbacks to mpsk recv
+ ext_set_alpha=self.sync.set_alpha,
+ ext_set_beta=self.sync.set_beta,
+ ext_set_gain_mu=self.sync.set_gain_mu,
+ ext_set_gain_omega=self.sync.set_gain_omega,
+ )
+ #register callbacks from window for external use
+
+ #setup the input watcher
+ common.input_watcher(msgq, self._handle_msg)
+ #update
+ self.set_sample_rate(sample_rate)
+
+ def set_sample_rate(self, sample_rate):
+ self.sample_rate = sample_rate
+ self.omega = float(self.sample_rate)/self.symbol_rate
+ self.sd.set_sample_rate(self.sample_rate)
+ self.sync.set_omega(self.omega)
+
+ def _handle_msg(self, msg):
+ itemsize = int(msg.arg1())
+ nitems = int(msg.arg2())
+ s = msg.to_string()
+ # There may be more than one frame in the message.
+ # If so, we take only the last one
+ if nitems > 1:
+ start = itemsize * (nitems - 1)
+ s = s[start:start+itemsize]
+ #convert to complex floating point numbers
+ samples = numpy.fromstring(s, numpy.complex64)
+ self.win.plot(samples)
Copied: gnuradio/branches/features/experimental-gui/fftsink.py (from rev 8813,
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/fftsink.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/fftsink.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/fftsink.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,406 @@
+#
+# 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.
+#
+
+##################################################
+# Imports
+##################################################
+from gnuradio import gr, blks2
+import plotter
+import common
+import wx
+import numpy
+import math
+
+##################################################
+# Constants
+##################################################
+SLIDER_STEPS = 100
+AVG_ALPHA_SLIDER_MIN, AVG_ALPHA_SLIDER_MAX = -4, 0
+DEFAULT_FRAME_RATE = 30
+DEFAULT_WIN_SIZE = (640, 240)
+DIV_LEVELS = (1, 2, 5, 10, 20)
+FFT_PLOT_COLOR_SPEC = (0, 0, 1)
+PEAK_VALS_COLOR_SPEC = (0, 1, 0)
+
+##################################################
+# FFT window control panel
+##################################################
+class control_panel(wx.Panel):
+ """!
+ A control panel with wx widgits to control the plotter and fft block
chain.
+ """
+
+ def __init__(self, parent):
+ """!
+ Create a new control panel.
+ @param parent the wx parent window
+ """
+ self.parent = parent
+ wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
+ control_box = wx.BoxSizer(wx.VERTICAL)
+
+ #checkboxes for average and peak hold
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Options'), 0,
wx.ALIGN_CENTER)
+ self.average_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="Average")
+ self.average_check_box.Bind(wx.EVT_CHECKBOX, self._on_average)
+ control_box.Add(self.average_check_box, 0, wx.EXPAND)
+ self.peak_hold_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="Peak Hold")
+ self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX,
self._on_peak_hold)
+ control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND)
+ control_box.AddSpacer(2)
+ self.avg_alpha_label = wx.StaticText(self, -1, '0'*15)
+ control_box.Add(self.avg_alpha_label, 0, wx.EXPAND)
+ self.avg_alpha_slider = wx.Slider(self, -1, 0, 0, SLIDER_STEPS,
style=wx.SL_HORIZONTAL)
+ self.avg_alpha_slider.Bind(wx.EVT_SLIDER, self._on_avg_alpha)
+ control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND)
+
+ #radio buttons for div size
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Set dB/div'), 0,
wx.ALIGN_CENTER)
+ radio_box = wx.BoxSizer(wx.VERTICAL)
+ self.radio_buttons = list()
+ for y_per_div in DIV_LEVELS:
+ radio_button = wx.RadioButton(self, -1, "%d
dB/div"%y_per_div)
+ radio_button.Bind(wx.EVT_RADIOBUTTON,
self._on_radio_button_change)
+ self.radio_buttons.append(radio_button)
+ radio_box.Add(radio_button, 0, wx.ALIGN_LEFT)
+ control_box.Add(radio_box, 0, wx.EXPAND)
+
+ #ref lvl buttons
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Adj Ref Lvl'), 0,
wx.ALIGN_CENTER)
+ control_box.AddSpacer(2)
+ self._ref_lvl_buttons = common.IncrDecrButtons(self,
self._on_incr_ref_level, self._on_decr_ref_level)
+ control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER)
+ control_box.AddStretchSpacer()
+
+ #run/stop
+ control_box.AddStretchSpacer()
+ self.run_button = wx.Button(self, -1, '', style=wx.BU_EXACTFIT)
+ self.run_button.Bind(wx.EVT_BUTTON, self._on_run)
+ control_box.Add(self.run_button, 0, wx.EXPAND)
+
+ #set sizer
+ self.SetSizerAndFit(control_box)
+ #update
+ self.update()
+
+ def update(self):
+ """!
+ Read the state of the fft plot settings and update the control
panel.
+ """
+ #update the run/stop button
+ if self.parent.running: self.run_button.SetLabel('Stop')
+ else: self.run_button.SetLabel('Run')
+ #update checkboxes
+ self.average_check_box.SetValue(self.parent.average)
+ self.peak_hold_check_box.SetValue(self.parent.peak_hold)
+ #update avg alpha
+ self.avg_alpha_label.SetLabel('Avg Alpha:
%.3g'%self.parent.avg_alpha)
+ slider_value =
SLIDER_STEPS*(math.log10(self.parent.avg_alpha)-AVG_ALPHA_SLIDER_MIN)/(AVG_ALPHA_SLIDER_MAX-AVG_ALPHA_SLIDER_MIN)
+ if abs(slider_value - self.avg_alpha_slider.GetValue()) > 1:
+ self.avg_alpha_slider.SetValue(slider_value)
+ if self.parent.average:
+ self.avg_alpha_label.Enable()
+ self.avg_alpha_slider.Enable()
+ else:
+ self.avg_alpha_label.Disable()
+ self.avg_alpha_slider.Disable()
+ #update radio buttons
+ try:
+ index = list(DIV_LEVELS).index(self.parent.y_per_div)
+ self.radio_buttons[index].SetValue(True)
+ except: pass
+
+ ##################################################
+ # Event handlers
+ ##################################################
+ def _on_radio_button_change(self, event):
+ selected_radio_button = filter(lambda rb: rb.GetValue(),
self.radio_buttons)[0]
+ index = self.radio_buttons.index(selected_radio_button)
+ self.parent.set_y_per_div(DIV_LEVELS[index])
+ def _on_average(self, event): self.parent.set_average(event.IsChecked())
+ def _on_peak_hold(self, event):
self.parent.set_peak_hold(event.IsChecked())
+ def _on_incr_ref_level(self, event): self.parent.incr_ref_level()
+ def _on_decr_ref_level(self, event): self.parent.decr_ref_level()
+ def _on_avg_alpha(self, event):
+
self.parent.set_avg_alpha(10**(float(AVG_ALPHA_SLIDER_MAX-AVG_ALPHA_SLIDER_MIN)*self.avg_alpha_slider.GetValue()/SLIDER_STEPS
+ AVG_ALPHA_SLIDER_MIN))
+ def _on_run(self, event): self.parent.set_run(not self.parent.running)
+
+##################################################
+# FFT window with plotter and control panel
+##################################################
+class fft_window(wx.Panel):
+ def __init__(
+ self,
+ parent,
+ size,
+ title,
+ real,
+ baseband_freq,
+ sample_rate,
+ y_per_div,
+ y_divs,
+ ref_level,
+ average,
+ avg_alpha,
+ peak_hold,
+ ext_set_average,
+ ext_set_avg_alpha,
+ ext_set_sample_rate,
+ ):
+ #ensure y_per_div
+ if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0]
+ #setup
+ self.running = True
+ self.real = real
+ self.baseband_freq = baseband_freq
+ self.sample_rate = sample_rate
+ self.x_divs = 8.0 #approximate
+ self.y_per_div = y_per_div
+ self.y_divs = y_divs
+ self.ref_level = ref_level
+ self.average = average
+ self.avg_alpha = avg_alpha
+ self.peak_hold = peak_hold
+ self.ext_set_average = ext_set_average
+ self.ext_set_avg_alpha = ext_set_avg_alpha
+ self.ext_set_sample_rate = ext_set_sample_rate
+ self.peak_vals = []
+ #init panel and plot
+ wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
+ self.plotter = plotter.grid_plotter(self)
+ self.plotter.SetSize(wx.Size(*size))
+ self.plotter.set_title(title)
+ self.plotter.set_y_units('Amplitude (dB)')
+ #setup the box with plot and controls
+ self.control_panel = control_panel(self)
+ main_box = wx.BoxSizer(wx.HORIZONTAL)
+ main_box.Add(self.plotter, 1, wx.EXPAND)
+ main_box.Add(self.control_panel, 0, wx.EXPAND)
+ self.SetSizerAndFit(main_box)
+ #update
+ self.update()
+
+ def plot(self, samples):
+ """!
+ Plot the samples onto the grid as channel 1.
+ If peak hold is enabled, plot peak vals as channel 2.
+ @param samples the fft array
+ """
+ if not self.running: return
+ #peak hold calculation
+ if self.peak_hold:
+ if len(self.peak_vals) != len(samples): self.peak_vals
= samples
+ self.peak_vals = numpy.maximum(samples, self.peak_vals)
+ #plot the fft
+ self.plotter.set_waveform(
+ channel=1,
+ samples=samples,
+ color_spec=FFT_PLOT_COLOR_SPEC,
+ )
+ #plot the peak hold
+ self.plotter.set_waveform(
+ channel=2,
+ samples=self.peak_vals,
+ color_spec=PEAK_VALS_COLOR_SPEC,
+ )
+ #update the plotter
+ self.plotter.update()
+
+ def update(self):
+ #update fft block
+ self.ext_set_average(self.average)
+ self.ext_set_avg_alpha(self.avg_alpha)
+ self.ext_set_sample_rate(self.sample_rate)
+ #update peak hold
+ if not self.peak_hold: self.peak_vals = []
+ #determine best fitting x_per_div
+ if self.real: x_width = self.sample_rate/2.0
+ else: x_width = self.sample_rate
+ x_per_div = common.get_clean_num(x_width/self.x_divs)
+ exp = common.get_exp(x_per_div)
+ #calculate units and scalar
+ if exp > 7: x_units, scalar = 'GHz', 1e-9
+ elif exp > 4: x_units, scalar = 'MHz', 1e-6
+ elif exp > 1: x_units, scalar = 'KHz', 1e-3
+ else: x_units, scalar = 'Hz', 1e-0
+ #update the x grid
+ if self.real:
+ self.plotter.set_x_grid(
+ scalar*self.baseband_freq,
+ scalar*self.baseband_freq +
scalar*self.sample_rate/2.0,
+ scalar*x_per_div,
+ )
+ else:
+ self.plotter.set_x_grid(
+ scalar*self.baseband_freq -
scalar*self.sample_rate/2.0,
+ scalar*self.baseband_freq +
scalar*self.sample_rate/2.0,
+ scalar*x_per_div,
+ )
+ #update x units
+ self.plotter.set_x_units('Frequency (%s)'%x_units)
+ #update y grid
+
self.plotter.set_y_grid(self.ref_level-self.y_per_div*self.y_divs,
self.ref_level, self.y_per_div)
+ #update control panel and plotter
+ self.control_panel.update()
+ self.plotter.update()
+
+ ##################################################
+ # Set parameters on-the-fly
+ ##################################################
+ def set_sample_rate(self, sample_rate):
+ self.sample_rate = sample_rate
+ self.update()
+ def set_baseband_freq(self, baseband_freq):
+ self.baseband_freq = baseband_freq
+ self.update()
+ def set_average(self, average):
+ self.average = average
+ self.update()
+ def set_avg_alpha(self, avg_alpha):
+ self.avg_alpha = avg_alpha
+ self.update()
+ def set_peak_hold(self, peak_hold):
+ self.peak_hold = peak_hold
+ self.update()
+ def set_y_per_div(self, y_per_div):
+ self.y_per_div = y_per_div
+ self.update()
+ def set_ref_level(self, ref_level):
+ self.ref_level = ref_level
+ self.update()
+ def incr_ref_level(self): self.set_ref_level(self.ref_level +
self.y_per_div)
+ def decr_ref_level(self): self.set_ref_level(self.ref_level -
self.y_per_div)
+ def set_run(self, running):
+ self.running = running
+ self.update()
+
+##################################################
+# FFT sink base block for real and complex types
+##################################################
+class _fft_sink_base(gr.hier_block2):
+ """!
+ An fft block with real/complex inputs and a gui window.
+ """
+
+ def __init__(
+ self,
+ parent,
+ baseband_freq=0,
+ ref_scale=2.0,
+ y_per_div=10,
+ y_divs=8,
+ ref_level=50,
+ sample_rate=1,
+ fft_size=512,
+ frame_rate=DEFAULT_FRAME_RATE,
+ average=False,
+ avg_alpha=None,
+ title='',
+ size=DEFAULT_WIN_SIZE,
+ peak_hold=False,
+ ):
+ #ensure avg alpha
+ if avg_alpha is None: avg_alpha = 2.0/frame_rate
+ #init
+ gr.hier_block2.__init__(
+ self,
+ "fft_sink",
+ gr.io_signature(1, 1, self.item_size),
+ gr.io_signature(0, 0, 0),
+ )
+ #blocks
+ copy = gr.kludge_copy(self.item_size)
+ fft = self.fft_chain(
+ sample_rate=sample_rate,
+ fft_size=fft_size,
+ frame_rate=frame_rate,
+ ref_scale=ref_scale,
+ avg_alpha=avg_alpha,
+ average=average,
+ )
+ msgq = gr.msg_queue(2)
+ sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True)
+ #connect
+ self.connect(self, copy, fft, sink)
+ #create window
+ self.win = fft_window(
+ parent=parent,
+ size=size,
+ title=title,
+ real = self.real,
+ baseband_freq=baseband_freq,
+ sample_rate=sample_rate,
+ y_per_div=y_per_div,
+ y_divs=y_divs,
+ ref_level=ref_level,
+ average=average,
+ avg_alpha=avg_alpha,
+ peak_hold=peak_hold,
+ ext_set_average=fft.set_average,
+ ext_set_avg_alpha=fft.set_avg_alpha,
+ ext_set_sample_rate=fft.set_sample_rate,
+ )
+ #register callbacks from window for external use
+ self.set_baseband_freq = self.win.set_baseband_freq
+ self.set_average = self.win.set_average
+ self.set_peak_hold = self.win.set_peak_hold
+ self.set_y_per_div = self.win.set_y_per_div
+ self.set_ref_level = self.win.set_ref_level
+ self.set_avg_alpha = self.win.set_avg_alpha
+ self.set_sample_rate = self.win.set_sample_rate
+ #setup the input watcher
+ common.input_watcher(msgq, self._handle_msg)
+
+ def _handle_msg(self, msg):
+ """!
+ Handle the message from the fft sink message queue.
+ If complex, reorder the fft samples so the negative bins come
first.
+ If real, keep take only the positive bins.
+ @param msg the fft array as a character array
+ """
+ itemsize = int(msg.arg1())
+ nitems = int(msg.arg2())
+ s = msg.to_string()
+ # There may be more than one frame in the message.
+ # If so, we take only the last one
+ if nitems > 1:
+ start = itemsize * (nitems - 1)
+ s = s[start:start+itemsize]
+ #convert to floating point numbers
+ samples = numpy.fromstring(s, numpy.float32)
+ num_samps = len(samples)
+ #reorder fft
+ if self.real: samples = samples[:num_samps/2]
+ else: samples = numpy.concatenate((samples[num_samps/2+1:],
samples[:num_samps/2]))
+ #plot
+ self.win.plot(samples)
+
+class fft_sink_f(_fft_sink_base):
+ fft_chain = blks2.logpwrfft_f
+ item_size = gr.sizeof_float
+ real = True
+class fft_sink_c(_fft_sink_base):
+ fft_chain = blks2.logpwrfft_c
+ item_size = gr.sizeof_gr_complex
+ real = False
Copied: gnuradio/branches/features/experimental-gui/gltext.py (from rev 8813,
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/gltext.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/gltext.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/gltext.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,494 @@
+#!/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()
+
+ # 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.Color):
+ """
+ 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):
+ """
+ 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
+
+ #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 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
Copied: gnuradio/branches/features/experimental-gui/plotter.py (from rev 8813,
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/plotter.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/plotter.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/plotter.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,436 @@
+#
+# 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
+import wx.glcanvas
+
+from OpenGL.GL import *
+
+import threading
+import gltext
+import common
+import math
+import numpy
+
+BACKGROUND_COLOR_SPEC = (1, 0.976, 1, 1) #creamy white
+GRID_LINE_COLOR_SPEC = (0, 0, 0) #black
+TICK_TEXT_FONT_SIZE = 9
+TITLE_TEXT_FONT_SIZE = 13
+UNITS_TEXT_FONT_SIZE = 9
+LEGEND_TEXT_FONT_SIZE = 8
+LEGEND_BOX_WIDTH, LEGEND_BOX_HEIGHT = 26, 20
+PADDING = 35, 15, 40, 60 #top, right, bottom, left
+
+class _plotter_base(wx.glcanvas.GLCanvas):
+ """!
+ Plotter base class for all plot types.
+ """
+
+ def __init__(self, parent):
+ """!
+ Create a new plotter base.
+ Initialize GL and register events.
+ @param parent the parent widgit
+ """
+ wx.glcanvas.GLCanvas.__init__(self, parent, -1)
+ self._gl_init_flag = False
+ self._resized_flag = True
+ wx.EVT_PAINT(self, self.OnPaint)
+ wx.EVT_SIZE(self, self.OnSize)
+
+ def OnSize(self, event):
+ """!
+ Flag the resize event.
+ The paint event will handle the actual resizing.
+ """
+ self._resized_flag = True
+
+ def OnPaint(self, event):
+ """!
+ Respond to paint events, call update.
+ Initialize GL if this is the first paint event.
+ """
+ self.SetCurrent()
+ #check if gl was initialized
+ if not self._gl_init_flag:
+ glClearColor(*BACKGROUND_COLOR_SPEC)
+ glEnableClientState(GL_VERTEX_ARRAY)
+ self._gl_init_flag = True
+ #check for a change in window size
+ if self._resized_flag:
+ self.width, self.height = self.GetSize()
+ glViewport(0, 0, self.width, self.height)
+ glMatrixMode(GL_PROJECTION)
+ glLoadIdentity()
+ glOrtho(0, self.width, self.height, 0, 1, 0)
+ self._resized_flag = False
+ self._changed = True
+ self.draw()
+
+ def update(self): wx.PostEvent(self, wx.PaintEvent())
+
+ def clear(self): glClear(GL_COLOR_BUFFER_BIT)
+
+class grid_plotter(_plotter_base):
+
+ def __init__(self, parent):
+ """!
+ Create a new grid plotter.
+ """
+ self.semaphore = threading.Semaphore(1)
+ self.grid_compiled_list_id = wx.NewId()
+ self.channels = dict()
+ #store title and unit strings
+ self.set_legend(False)
+ self.set_title('Title')
+ self.set_x_units('X Units (xx)')
+ self.set_y_units('Y Units (yy)')
+ #store padding
+ self.padding_top, self.padding_right, self.padding_bottom,
self.padding_left = PADDING
+ #init the grid to some value
+ self.set_x_grid(-1, 1, 1)
+ self.set_y_grid(-1, 1, 1)
+ _plotter_base.__init__(self, parent)
+
+ def set_legend(self, legend):
+ """!
+ Set the legend on/off.
+ @param legend true to turn on
+ """
+ self.semaphore.acquire(True)
+ self.legend = legend
+ self._changed = True
+ self.semaphore.release()
+
+ def set_title(self, title):
+ """!
+ Set the title.
+ @param title the title string
+ """
+ self.semaphore.acquire(True)
+ self.title = title
+ self._changed = True
+ self.semaphore.release()
+
+ def set_x_units(self, x_units):
+ """!
+ Set the x_units.
+ @param x_units the x_units string
+ """
+ self.semaphore.acquire(True)
+ self.x_units = x_units
+ self._changed = True
+ self.semaphore.release()
+
+ def set_y_units(self, y_units):
+ """!
+ Set the y_units.
+ @param y_units the y_units string
+ """
+ self.semaphore.acquire(True)
+ self.y_units = y_units
+ self._changed = True
+ self.semaphore.release()
+
+ def set_x_grid(self, x_min, x_max, x_step):
+ """!
+ Set the x grid parameters.
+ @param x_min the left-most value
+ @param x_max the right-most value
+ @param x_step the grid spacing
+ """
+ self.semaphore.acquire(True)
+ self.x_min = float(x_min)
+ self.x_max = float(x_max)
+ self.x_step = float(x_step)
+ self._changed = True
+ self.semaphore.release()
+
+ def set_y_grid(self, y_min, y_max, y_step):
+ """!
+ Set the y grid parameters.
+ @param y_min the bottom-most value
+ @param y_max the top-most value
+ @param y_step the grid spacing
+ """
+ self.semaphore.acquire(True)
+ self.y_min = float(y_min)
+ self.y_max = float(y_max)
+ self.y_step = float(y_step)
+ self._changed = True
+ self.semaphore.release()
+
+ def draw(self):
+ """!
+ Draw the grid and waveforms.
+ """
+ self.semaphore.acquire(True)
+ self.clear()
+ #store the grid drawing operations
+ if self._changed:
+ glNewList(self.grid_compiled_list_id, GL_COMPILE)
+ self._draw_grid()
+ glEndList()
+ self._changed = False
+ #draw the grid
+ glCallList(self.grid_compiled_list_id)
+ #use scissor to prevent drawing outside grid
+ glEnable(GL_SCISSOR_TEST)
+ 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,
+ )
+ #draw the waveforms
+ self._draw_waveforms()
+ glDisable(GL_SCISSOR_TEST)
+ #swap buffer into display
+ self.SwapBuffers()
+ self.semaphore.release()
+
+ def _draw_waveforms(self):
+ """!
+ Draw the waveforms for each channel.
+ Scale the waveform data to the grid using numpy (saves CPU).
+ """
+ ##################################################
+ # Draw Waveforms
+ ##################################################
+ for channel in reversed(sorted(self.channels.keys())):
+ samples, color_spec, marker = self.channels[channel]
+ num_samps = len(samples)
+ #use opengl to scale the waveform
+ glPushMatrix()
+ glTranslatef(self.padding_left, self.padding_top, 0)
+ glScalef(
+
(self.width-self.padding_left-self.padding_right),
+
(self.height-self.padding_top-self.padding_bottom),
+ 1,
+ )
+ 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), 0
+ points = zip(numpy.arange(0, num_samps),
samples)
+ glScalef(x_scale, -1.0/(self.y_max-self.y_min), 1)
+ glTranslatef(x_trans, -self.y_min, 0)
+ #draw the points/lines
+ glColor3f(*color_spec)
+ if marker == '+': #TODO slow, slow, slow
+ points_4 = numpy.array(zip(points, points,
points, points))
+ points_4 = points_4 + .01*numpy.array([
+ ((self.x_max-self.x_min), 0),
+ (-(self.x_max-self.x_min), 0),
+ (0, (self.y_max-self.y_min)),
+ (0, -(self.y_max-self.y_min)),
+ ]
+ )
+ points = points_4.reshape(len(points)*4, 2)
+ glVertexPointer(2, GL_FLOAT, 0, points)
+ glDrawArrays({None: GL_LINE_STRIP, '.': GL_POINTS, '+':
GL_LINES}[marker], 0, len(points))
+ glPopMatrix()
+
+ def _draw_grid(self):
+ """!
+ Draw the border, grid, title, and units.
+ Save the commands to a compiled list.
+ """
+ ##################################################
+ # Draw Border
+ ##################################################
+ glColor3f(*GRID_LINE_COLOR_SPEC)
+ glBegin(GL_LINE_LOOP)
+ glVertex3f(self.padding_left, self.padding_top, 0)
+ glVertex3f(self.width - self.padding_right, self.padding_top, 0)
+ glVertex3f(self.width - self.padding_right, self.height -
self.padding_bottom, 0)
+ glVertex3f(self.padding_left, self.height -
self.padding_bottom, 0)
+ glEnd()
+
+ ##################################################
+ # Draw Grid X
+ ##################################################
+ for tick in self._get_ticks(self.x_min, self.x_max,
self.x_step):
+ scaled_tick =
(self.width-self.padding_left-self.padding_right)*(tick-self.x_min)/(self.x_max-self.x_min)
+ self.padding_left
+
+ glColor3f(*GRID_LINE_COLOR_SPEC)
+ self._draw_line(
+ (scaled_tick, self.padding_top, 0),
+ (scaled_tick, self.height-self.padding_bottom,
0),
+ )
+ self._draw_tick_label(tick, (scaled_tick,
self.height-.75*self.padding_bottom))
+
+ ##################################################
+ # Draw Grid Y
+ ##################################################
+ for tick in self._get_ticks(self.y_min, self.y_max,
self.y_step):
+ scaled_tick =
(self.height-self.padding_top-self.padding_bottom)*(1 -
(tick-self.y_min)/(self.y_max-self.y_min)) + self.padding_top
+
+ glColor3f(*GRID_LINE_COLOR_SPEC)
+ self._draw_line(
+ (self.padding_left, scaled_tick, 0),
+ (self.width-self.padding_right, scaled_tick, 0),
+ )
+ self._draw_tick_label(tick, (.75*self.padding_left,
scaled_tick))
+
+ ##################################################
+ # Draw Title
+ ##################################################
+ font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
+ #draw x units
+ txt = gltext.Text(self.title, font=font,
font_size=TITLE_TEXT_FONT_SIZE, centered=True)
+ txt.draw_text(wx.Point(self.width/2.0, .5*self.padding_top))
+
+ ##################################################
+ # Draw Units
+ ##################################################
+ font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
+ #draw x units
+ txt = gltext.Text(self.x_units, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
+ txt.draw_text(wx.Point(
+
(self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left,
+ self.height-.25*self.padding_bottom,
+ )
+ )
+ #draw y units
+ txt = gltext.Text(self.y_units, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
+ txt.draw_text(wx.Point(
+ .25*self.padding_left,
+
(self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top,
+ ), rotation=90,
+ )
+
+ ##################################################
+ # Draw Legend
+ ##################################################
+ if self.legend:
+ for i, channel in
enumerate(sorted(self.channels.keys())):
+ x_off =
1.1*LEGEND_BOX_WIDTH*(len(self.channels) - i - 1) + LEGEND_BOX_WIDTH/2
+ color_spec = self.channels[channel][1]
+ #draw colored rectangle
+ glColor3f(*color_spec)
+ self._draw_rect(
+ self.width - self.padding_right - x_off
- LEGEND_BOX_WIDTH/2,
+
(self.padding_top-LEGEND_BOX_HEIGHT)/2.0,
+ LEGEND_BOX_WIDTH,
+ LEGEND_BOX_HEIGHT,
+ )
+ #draw label text
+ txt = gltext.Text('Ch%s'%channel,
font_size=LEGEND_TEXT_FONT_SIZE, centered=True)
+ txt.draw_text(wx.Point(self.width -
self.padding_right - x_off, self.padding_top/2.0))
+
+ def _draw_tick_label(self, tick, coor):
+ """!
+ Format the tick value and draw it at the coordinate.
+ Intelligently switch between decimal representations.
+ @param tick the floating point tick value
+ @param coor the x, y coordinate
+ """
+ #format
+ exp = common.get_exp(tick)
+ base = int(tick/10**exp)
+ if abs(exp) >= 3: tick_str = '%de%d'%(base, exp)
+ else: tick_str = '%g'%tick
+ #draw
+ txt = gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE,
centered=True)
+ txt.draw_text(wx.Point(*coor))
+
+ def _get_ticks(self, min, max, step):
+ """!
+ Determine the positions for the ticks.
+ @param min the lower bound
+ @param max the upper bound
+ @param step the grid spacing
+ @return 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
+ assert step > 0
+ assert max > min
+ assert max - min > step
+ #determine the start and stop value
+ start = int(math.ceil(min/step))
+ stop = int(math.floor(max/step))
+ return [i*step for i in range(start, stop+1)]
+
+ def _draw_line(self, coor1, coor2):
+ """!
+ Draw a line from coor1 to coor2.
+ @param corr1 a tuple of x, y, z
+ @param corr2 a tuple of x, y, z
+ """
+ glBegin(GL_LINES)
+ glVertex3f(*coor1)
+ glVertex3f(*coor2)
+ glEnd()
+
+ def _draw_rect(self, x, y, width, height):
+ """!
+ Draw a rectangle on the x, y plane.
+ X and Y are the top-left corner.
+ @param x the left position of the rectangle
+ @param y the top position of the rectangle
+ @param width the width of the rectangle
+ @param height the height of the rectangle
+ """
+ glBegin(GL_QUADS)
+ glVertex3f(x, y, 0)
+ glVertex3f(x+width, y, 0)
+ glVertex3f(x+width, y+height, 0)
+ glVertex3f(x, y+height, 0)
+ glEnd()
+
+ def set_waveform(self, channel, samples, color_spec, marker=None):
+ """!
+ Set the waveform for a given channel.
+ @param channel the channel number
+ @param samples the waveform samples
+ @param color_spec the 3-tuple for line color
+ @param marker None for line
+ """
+ self.channels[channel] = samples, color_spec, marker
+
+if __name__ == '__main__':
+ app = wx.PySimpleApp()
+ frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition)
+ vbox = wx.BoxSizer(wx.VERTICAL)
+
+ plotter = grid_plotter(frame)
+ plotter.set_x_grid(-1, 1, .2)
+ plotter.set_y_grid(-1, 1, .4)
+ vbox.Add(plotter, 1, wx.EXPAND)
+
+ plotter = grid_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()
Copied: gnuradio/branches/features/experimental-gui/scopesink.py (from rev
8813, gnuradio/branches/developers/jblum/gr-wxglgui/src/python/scopesink.py)
===================================================================
--- gnuradio/branches/features/experimental-gui/scopesink.py
(rev 0)
+++ gnuradio/branches/features/experimental-gui/scopesink.py 2008-07-06
22:27:24 UTC (rev 8814)
@@ -0,0 +1,426 @@
+#
+# 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.
+#
+
+##################################################
+# Imports
+##################################################
+from gnuradio import gr
+import plotter
+import common
+import wx
+import numpy
+import time
+
+##################################################
+# Constants
+##################################################
+DEFAULT_FRAME_RATE = 30
+DEFAULT_WIN_SIZE = (640, 240)
+DEFAULT_V_SCALE = 1000
+TRIGGER_MODES = (
+ ('Auto', gr.gr_TRIG_AUTO),
+ ('Neg', gr.gr_TRIG_NEG_SLOPE),
+ ('Pos', gr.gr_TRIG_POS_SLOPE),
+)
+TRIGGER_LEVELS = (
+ ('Auto', None),
+ ('+High', 0.75),
+ ('+Med', 0.5),
+ ('+Low', 0.25),
+ ('Zero', 0.0),
+ ('-Low', -0.25),
+ ('-Med', -0.5),
+ ('-High', -0.75),
+)
+CHANNEL_COLOR_SPECS = (
+ (0, 0, 1),
+ (0, 1, 0),
+ (1, 0, 0),
+ (1, 0, 1),
+)
+AUTORANGE_UPDATE_RATE = 0.5 #sec
+
+##################################################
+# Scope window control panel
+##################################################
+class control_panel(wx.Panel):
+ """!
+ A control panel with wx widgits to control the plotter and scope block.
+ """
+ def __init__(self, parent):
+ """!
+ Create a new control panel.
+ @param parent the wx parent window
+ """
+ self.parent = parent
+ wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
+ control_box = wx.BoxSizer(wx.VERTICAL)
+
+ #trigger options
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Trigger Options'), 0,
wx.ALIGN_CENTER)
+ control_box.AddSpacer(2)
+ #trigger mode
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Channel '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.trigger_channel_chooser = wx.Choice(self, -1,
choices=["Ch%d"%ch for ch in range(1, parent.num_inputs+1)])
+ self.trigger_channel_chooser.Bind(wx.EVT_CHOICE,
self._on_trigger_channel)
+ hbox.Add(self.trigger_channel_chooser, 0,
wx.ALIGN_CENTER_VERTICAL)
+ #trigger mode
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Mode '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.trigger_mode_chooser = wx.Choice(self, -1, choices=[tm[0]
for tm in TRIGGER_MODES])
+ self.trigger_mode_chooser.Bind(wx.EVT_CHOICE,
self._on_trigger_mode)
+ hbox.Add(self.trigger_mode_chooser, 0, wx.ALIGN_CENTER_VERTICAL)
+ #trigger level
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Level '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.trigger_level_chooser = wx.Choice(self, -1, choices=[tl[0]
for tl in TRIGGER_LEVELS])
+ self.trigger_level_chooser.Bind(wx.EVT_CHOICE,
self._on_trigger_level)
+ hbox.Add(self.trigger_level_chooser, 0,
wx.ALIGN_CENTER_VERTICAL)
+
+ #axes options
+ SPACING = 15
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Axes Options'), 0,
wx.ALIGN_CENTER)
+ #x axis divs
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.x_buttons = common.IncrDecrButtons(self,
self._on_incr_x_divs, self._on_decr_x_divs)
+ hbox.Add(self.x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
+ hbox.AddSpacer(SPACING)
+ #y axis divs
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.y_buttons = common.IncrDecrButtons(self,
self._on_incr_y_divs, self._on_decr_y_divs)
+ hbox.Add(self.y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
+ hbox.AddSpacer(SPACING)
+ #y axis ref lvl
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ control_box.Add(hbox, 0, wx.EXPAND)
+ hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1,
wx.ALIGN_CENTER_VERTICAL)
+ self.y_off_buttons = common.IncrDecrButtons(self,
self._on_incr_y_off, self._on_decr_y_off)
+ hbox.Add(self.y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
+ hbox.AddSpacer(SPACING)
+
+ #misc options
+ control_box.AddStretchSpacer()
+ control_box.Add(common.LabelText(self, 'Range Options'), 0,
wx.ALIGN_CENTER)
+ #ac couple check box
+ self.ac_couple_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="AC Couple")
+ self.ac_couple_check_box.Bind(wx.EVT_CHECKBOX,
self._on_ac_couple)
+ control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT)
+ #autorange check box
+ self.autorange_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="Autorange")
+ self.autorange_check_box.Bind(wx.EVT_CHECKBOX,
self._on_autorange)
+ control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
+ #Run button
+ control_box.AddStretchSpacer()
+ self.run_button = wx.Button(self, -1, '', style=wx.BU_EXACTFIT)
+ self.run_button.Bind(wx.EVT_BUTTON, self._on_run)
+ control_box.Add(self.run_button, 0, wx.EXPAND)
+
+ #set sizer
+ self.SetSizerAndFit(control_box)
+ #update
+ self.update()
+
+ def update(self):
+ """!
+ Read the state of the scope plot settings and update the
control panel.
+ """
+ #update the ac couple check box
+ self.ac_couple_check_box.SetValue(self.parent.ac_couple)
+ #update the autorange check box
+ self.autorange_check_box.SetValue(self.parent.autorange)
+ #update trigger menu
+
self.trigger_channel_chooser.SetSelection(self.parent.trigger_channel_index)
+
self.trigger_mode_chooser.SetSelection(self.parent.trigger_mode_index)
+
self.trigger_level_chooser.SetSelection(self.parent.trigger_level_index)
+ #update the run/stop button
+ if self.parent.running: self.run_button.SetLabel('Stop')
+ else: self.run_button.SetLabel('Run')
+ #update the y adj buttons
+ if self.parent.autorange:
+ self.y_buttons.Disable()
+ self.y_off_buttons.Disable()
+ else:
+ self.y_buttons.Enable()
+ self.y_off_buttons.Enable()
+
+ ##################################################
+ # Event handlers
+ ##################################################
+ def _on_channel(self, event):
self.parent.set_input_index(self.channel_chooser.GetSelection())
+ def _on_ac_couple(self, event):
self.parent.set_ac_couple(event.IsChecked())
+ def _on_autorange(self, event):
self.parent.set_autorange(event.IsChecked())
+ def _on_trigger_channel(self, event):
self.parent.set_trigger_channel_index(self.trigger_channel_chooser.GetSelection())
+ def _on_trigger_mode(self, event):
self.parent.set_trigger_mode_index(self.trigger_mode_chooser.GetSelection())
+ def _on_trigger_level(self, event):
self.parent.set_trigger_level_index(self.trigger_level_chooser.GetSelection())
+ def _on_incr_x_divs(self, event):
self.parent.set_x_per_div(common.get_clean_incr(self.parent.x_per_div))
+ def _on_decr_x_divs(self, event):
self.parent.set_x_per_div(common.get_clean_decr(self.parent.x_per_div))
+ def _on_incr_y_divs(self, event):
self.parent.set_y_per_div(common.get_clean_incr(self.parent.y_per_div))
+ def _on_decr_y_divs(self, event):
self.parent.set_y_per_div(common.get_clean_decr(self.parent.y_per_div))
+ def _on_incr_y_off(self, event):
self.parent.set_y_off(self.parent.y_off + self.parent.y_per_div)
+ def _on_decr_y_off(self, event):
self.parent.set_y_off(self.parent.y_off - self.parent.y_per_div)
+ def _on_run(self, event): self.parent.set_run(not self.parent.running)
+
+##################################################
+# Scope window with plotter and control panel
+##################################################
+class scope_window(wx.Panel):
+ def __init__(
+ self,
+ parent,
+ size,
+ title,
+ scope,
+ frame_rate,
+ num_inputs,
+ sample_rate,
+ x_per_div,
+ y_per_div,
+ ac_couple,
+ ):
+ #check num inputs
+ assert num_inputs <= len(CHANNEL_COLOR_SPECS)
+ #setup
+ self.running = True
+ self.num_inputs = num_inputs
+ self.sample_rate = sample_rate
+ self.ac_couple = ac_couple
+ self.autorange = y_per_div is None
+ self.autorange_ts = 0
+ self.input_index = 0
+ #triggering
+ self.trigger_channel_index = 0
+ self.trigger_mode_index = 1
+ self.trigger_level_index = 0
+ #x grid settings
+ self.x_divs = 8
+ self.x_per_div = x_per_div
+ self.x_off = 0
+ #y grid settings
+ self.y_divs = 8
+ if y_per_div is None: self.y_per_div = 1
+ else: self.y_per_div = y_per_div
+ self.y_off = 0
+ self.scope = scope
+ self.frame_rate = frame_rate
+ self.frame_counter = 0
+ self._init = False #HACK
+ #init panel and plot
+ wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
+ self.plotter = plotter.grid_plotter(self)
+ self.plotter.SetSize(wx.Size(*size))
+ self.plotter.set_title(title)
+ self.plotter.set_legend(self.num_inputs > 1)
+ #setup the box with plot and controls
+ self.control_panel = control_panel(self)
+ main_box = wx.BoxSizer(wx.HORIZONTAL)
+ main_box.Add(self.plotter, 1, wx.EXPAND)
+ main_box.Add(self.control_panel, 0, wx.EXPAND)
+ self.SetSizerAndFit(main_box)
+ #update
+ self.update()
+
+ def plot(self, sampleses):
+ """!
+ Plot the list of arrays of samples onto the grid.
+ Each samples array gets its own channel.
+ @param sampleses the array of arrays
+ """
+ if not self._init: #HACK
+ self._init = True
+ self.update()
+ if not self.running: return
+ if self.frame_counter == 0: #decimate
+ #trigger level (must do before ac coupling)
+ samples = sampleses[self.trigger_level_index]
+ trigger_level =
TRIGGER_LEVELS[self.trigger_level_index][1]
+ if trigger_level is None:
self.scope.set_trigger_level_auto()
+ else:
self.scope.set_trigger_level(trigger_level*(numpy.max(samples)-numpy.min(samples))/2
+ numpy.average(samples))
+ #ac coupling
+ if self.ac_couple:
+ sampleses = [samples - numpy.average(samples)
for samples in sampleses]
+ #autorange
+ if self.autorange and time.time() - self.autorange_ts >
AUTORANGE_UPDATE_RATE:
+ bounds = [common.get_min_max(samples) for
samples in sampleses]
+ y_min = min(*[bound[0] for bound in bounds])
+ y_max = max(*[bound[1] for bound in bounds])
+ #adjust the y per div
+ y_per_div =
common.get_clean_num((y_max-y_min)/self.y_divs)
+ if self.y_per_div != y_per_div:
self.set_y_per_div(y_per_div)
+ #adjust the y offset
+ y_off =
self.y_per_div*round((y_max+y_min)/2/self.y_per_div)
+ if self.y_off != y_off: self.set_y_off(y_off)
+ self.autorange_ts = time.time()
+ #plot each waveform
+ for i, samples in enumerate(sampleses):
+ #number of samples to scale to the screen
+ num_samps =
int(self.x_per_div*self.x_divs*self.sample_rate)
+ #handle num samps out of bounds
+ while num_samps > len(samples): samples =
numpy.concatenate((samples, samples))
+ if num_samps < 2: num_samps = 0
+ #plot samples
+ self.plotter.set_waveform(
+ channel=i+1,
+ samples=samples[:num_samps],
+ color_spec=CHANNEL_COLOR_SPECS[i],
+ )
+ #update the plotter
+ self.plotter.update()
+ self.frame_counter = (self.frame_counter + 1)%self.decim
+
+ def update(self):
+ #update the frame decimation
+ self.decim = max(1,
int(self.sample_rate/self.scope.get_samples_per_output_record()/self.frame_rate))
+ #update the scope
+ if self._init: #HACK avoid segfaults, only set after a sample
has arrived
+
self.scope.set_trigger_channel(self.trigger_channel_index)
+
self.scope.set_trigger_mode(TRIGGER_MODES[self.trigger_mode_index][1])
+ self.scope.set_sample_rate(self.sample_rate)
+ #update the x axis
+ exp = common.get_exp(self.x_per_div)
+ if exp > -2: x_units, scalar = 's', 1e0
+ elif exp > -5: x_units, scalar = 'ms', 1e3
+ elif exp > -8: x_units, scalar = 'us', 1e6
+ else: x_units, scalar = 'ns', 1e9
+ self.plotter.set_x_units('Time (%s)'%x_units)
+ self.plotter.set_x_grid(
+ scalar*self.x_off,
+ scalar*self.x_per_div*self.x_divs + scalar*self.x_off,
+ scalar*self.x_per_div,
+ )
+ #update the y axis
+ self.plotter.set_y_units('Units')
+ self.plotter.set_y_grid(
+ -1*self.y_per_div*self.y_divs/2.0 + self.y_off,
+ self.y_per_div*self.y_divs/2.0 + self.y_off,
+ self.y_per_div,
+ )
+ #update control panel and plotter
+ self.control_panel.update()
+ self.plotter.update()
+
+ ##################################################
+ # Set parameters on-the-fly
+ ##################################################
+ def set_x_per_div(self, x_per_div):
+ self.x_per_div = x_per_div
+ self.update()
+ def set_x_off(self, x_off):
+ self.x_off = x_off
+ self.update()
+ def set_y_per_div(self, y_per_div):
+ self.y_per_div = y_per_div
+ self.update()
+ def set_y_off(self, y_off):
+ self.y_off = y_off
+ self.update()
+ def set_trigger_channel_index(self, trigger_channel_index):
+ self.trigger_channel_index = trigger_channel_index
+ self.update()
+ def set_trigger_mode_index(self, trigger_mode_index):
+ self.trigger_mode_index = trigger_mode_index
+ self.update()
+ def set_trigger_level_index(self, trigger_level_index):
+ self.trigger_level_index = trigger_level_index
+ self.update()
+ def set_trigger_index(self, trigger_index):
+ self.trigger_index = trigger_index
+ self.update()
+ def set_run(self, running):
+ self.running = running
+ self.update()
+ def set_ac_couple(self, ac_couple):
+ self.ac_couple = ac_couple
+ self.update()
+ def set_autorange(self, autorange):
+ self.autorange = autorange
+ self.update()
+ def set_sample_rate(self, sample_rate):
+ self.sample_rate = sample_rate
+ self.update()
+
+##################################################
+# Scope sink block
+##################################################
+class scope_sink_f(gr.hier_block2):
+ """!
+ A scope block with a gui window.
+ """
+
+ def __init__(
+ self,
+ parent,
+ title='',
+ sample_rate=1,
+ size=DEFAULT_WIN_SIZE,
+ frame_rate=DEFAULT_FRAME_RATE,
+ v_scale=DEFAULT_V_SCALE,
+ t_scale=None,
+ num_inputs=1,
+ ac_couple=False,
+ ):
+ #init
+ gr.hier_block2.__init__(
+ self,
+ "scope_sink",
+ gr.io_signature(num_inputs, num_inputs,
gr.sizeof_float),
+ gr.io_signature(0, 0, 0),
+ )
+ #scope
+ msgq = gr.msg_queue(2)
+ scope = gr.oscope_sink_f(sample_rate, msgq)
+ #connect
+ for i in range(num_inputs):
+ self.connect((self, i), (scope, i))
+ #create window
+ self.win = scope_window(
+ parent=parent,
+ size=size,
+ title=title,
+ scope=scope,
+ frame_rate=frame_rate,
+ num_inputs=num_inputs,
+ sample_rate=sample_rate,
+ x_per_div=t_scale,
+ y_per_div=v_scale,
+ ac_couple=ac_couple,
+ )
+ #register callbacks from window for external use
+ self.set_sample_rate = self.win.set_sample_rate
+ #setup the input watcher
+ common.input_watcher(msgq, self._handle_msg)
+
+ def _handle_msg(self, msg):
+ nchan = int(msg.arg1()) # number of channels of data in msg
+ nsamples = int(msg.arg2()) # number of samples in each channel
+ s = msg.to_string() # get the body of the msg as a string
+ samples = numpy.fromstring(s, numpy.float32)
+ samps_per_ch = len(samples)/nchan
+ self.win.plot([samples[samps_per_ch*i:samps_per_ch*(i+1)] for i
in range(nchan)])
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Commit-gnuradio] r8814 - gnuradio/branches/features/experimental-gui,
jblum <=