From 5d69a524f81f234b3fbc41d49ba18d6f6886baba Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 3 Aug 2006 04:51:51 +0000 Subject: Houston, we have a trunk. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3122 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 40 + gr-wxgui/src/python/__init__.py | 1 + gr-wxgui/src/python/fftsink.py | 488 ++++++++++ gr-wxgui/src/python/form.py | 391 ++++++++ gr-wxgui/src/python/plot.py | 1744 ++++++++++++++++++++++++++++++++++ gr-wxgui/src/python/powermate.py | 437 +++++++++ gr-wxgui/src/python/scopesink.py | 650 +++++++++++++ gr-wxgui/src/python/slider.py | 49 + gr-wxgui/src/python/stdgui.py | 90 ++ gr-wxgui/src/python/waterfallsink.py | 469 +++++++++ 10 files changed, 4359 insertions(+) create mode 100644 gr-wxgui/src/python/Makefile.am create mode 100644 gr-wxgui/src/python/__init__.py create mode 100755 gr-wxgui/src/python/fftsink.py create mode 100755 gr-wxgui/src/python/form.py create mode 100644 gr-wxgui/src/python/plot.py create mode 100755 gr-wxgui/src/python/powermate.py create mode 100755 gr-wxgui/src/python/scopesink.py create mode 100755 gr-wxgui/src/python/slider.py create mode 100644 gr-wxgui/src/python/stdgui.py create mode 100755 gr-wxgui/src/python/waterfallsink.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am new file mode 100644 index 000000000..2d25de05b --- /dev/null +++ b/gr-wxgui/src/python/Makefile.am @@ -0,0 +1,40 @@ +# +# Copyright 2004,2005 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.wxgui module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio/wxgui + +ourpythondir = $(grpythondir)/wxgui +ourlibdir = $(grpyexecdir)/wxgui + +ourpython_PYTHON = \ + __init__.py \ + form.py \ + fftsink.py \ + plot.py \ + powermate.py \ + scopesink.py \ + waterfallsink.py \ + slider.py \ + stdgui.py diff --git a/gr-wxgui/src/python/__init__.py b/gr-wxgui/src/python/__init__.py new file mode 100644 index 000000000..027150db1 --- /dev/null +++ b/gr-wxgui/src/python/__init__.py @@ -0,0 +1 @@ +# make this directory a package diff --git a/gr-wxgui/src/python/fftsink.py b/gr-wxgui/src/python/fftsink.py new file mode 100755 index 000000000..8796a1b92 --- /dev/null +++ b/gr-wxgui/src/python/fftsink.py @@ -0,0 +1,488 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2006 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import threading +import math + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class fft_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, ref_level=50, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title='', peak_hold=False): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.y_divs = 8 + self.y_per_div=y_per_div + self.ref_level = ref_level + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.peak_hold = peak_hold + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages + + def set_y_per_div(self, y_per_div): + self.y_per_div = y_per_div + + def set_ref_level(self, ref_level): + self.ref_level = ref_level + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + self.set_peak_hold(False) + else: + self.avg.set_taps(1.0) + + def set_peak_hold(self, enable): + self.peak_hold = enable + if enable: + self.set_average(False) + self.win.set_peak_hold(enable) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + +class fft_sink_f(gr.hier_block, fft_sink_base): + def __init__(self, fg, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + y_per_div=y_per_div, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + fft = gr.fft_vfc(self.fft_size, True, mywindow) + power = 0 + for tap in mywindow: + power += tap*tap + + c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + + # FIXME We need to add 3dB to all bins but the DC bin + log = gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + + fg.connect (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) + gr.hier_block.__init__(self, fg, s2p, sink) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + + +class fft_sink_c(gr.hier_block, fft_sink_base): + def __init__(self, fg, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + y_per_div=y_per_div, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + mywindow = window.blackmanharris(self.fft_size) + power = 0 + for tap in mywindow: + power += tap*tap + + fft = gr.fft_vcc(self.fft_size, True, mywindow) + c2mag = gr.complex_to_mag(fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, fft_size) + log = gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + sink = gr.message_sink(gr.sizeof_float * fft_size, self.msgq, True) + + fg.connect(s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) + gr.hier_block.__init__(self, fg, s2p, sink) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = Numeric.fromstring (s, Numeric.Float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + + +class fft_window (plot.PlotCanvas): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + + self.y_range = None + self.fftsink = fftsink + self.peak_hold = False + self.peak_vals = None + + self.SetEnableGrid (True) + # self.SetEnableZoom (True) + # self.SetBackgroundColour ('black') + + self.build_popup_menu() + + EVT_DATA_EVENT (self, self.set_data) + wx.EVT_CLOSE (self, self.on_close_window) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + + + def on_close_window (self, event): + print "fft_window:on_close_window" + self.keep_running = False + + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + if self.peak_hold: + if self.peak_vals is None: + self.peak_vals = dB + else: + self.peak_vals = Numeric.maximum(dB, self.peak_vals) + dB = self.peak_vals + + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + sf = 1e-9 + units = "GHz" + elif x >= 1e6: + sf = 1e-6 + units = "MHz" + else: + sf = 1e-3 + units = "kHz" + + if self.fftsink.input_is_real: # only plot 1/2 the points + x_vals = ((Numeric.arrayrange (L/2) + * (self.fftsink.sample_rate * sf / L)) + + self.fftsink.baseband_freq * sf) + points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points[:,0] = x_vals + points[:,1] = dB[0:L/2] + else: + # the "negative freqs" are in the second half of the array + x_vals = ((Numeric.arrayrange (-L/2, L/2) + * (self.fftsink.sample_rate * sf / L)) + + self.fftsink.baseband_freq * sf) + points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points[:,0] = x_vals + points[:,1] = Numeric.concatenate ((dB[L/2:], dB[0:L/2])) + + + lines = plot.PolyLine (points, colour='BLUE') + + graphics = plot.PlotGraphics ([lines], + title=self.fftsink.title, + xLabel = units, yLabel = "dB") + + self.Draw (graphics, xAxis=None, yAxis=self.y_range) + self.update_y_range () + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.fftsink.ref_level + ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.fftsink.set_peak_hold(evt.IsChecked()) + + def on_incr_ref_level(self, evt): + # print "on_incr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + + self.fftsink.y_per_div) + + def on_decr_ref_level(self, evt): + # print "on_decr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + - self.fftsink.y_per_div) + + def on_incr_y_per_div(self, evt): + # print "on_incr_y_per_div" + self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, (1,2,5,10,20))) + + def on_decr_y_per_div(self, evt): + # print "on_decr_y_per_div" + self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, (1,2,5,10,20))) + + def on_y_per_div(self, evt): + # print "on_y_per_div" + Id = evt.GetId() + if Id == self.id_y_per_div_1: + self.fftsink.set_y_per_div(1) + elif Id == self.id_y_per_div_2: + self.fftsink.set_y_per_div(2) + elif Id == self.id_y_per_div_5: + self.fftsink.set_y_per_div(5) + elif Id == self.id_y_per_div_10: + self.fftsink.set_y_per_div(10) + elif Id == self.id_y_per_div_20: + self.fftsink.set_y_per_div(20) + + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + self.id_peak_hold = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") + menu.Append(self.id_incr_ref_level, "Incr Ref Level") + menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average, + self.id_peak_hold : lambda : self.fftsink.peak_hold, + self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Deprecated interfaces +# ---------------------------------------------------------------- + +# returns (block, win). +# block requires a single input stream of float +# win is a subclass of wxWindow + +def make_fft_sink_f(fg, parent, title, fft_size, input_rate, ymin = 0, ymax=50): + + block = fft_sink_f(fg, parent, title=title, fft_size=fft_size, sample_rate=input_rate, + y_per_div=(ymax - ymin)/8, ref_level=ymax) + return (block, block.win) + +# returns (block, win). +# block requires a single input stream of gr_complex +# win is a subclass of wxWindow + +def make_fft_sink_c(fg, parent, title, fft_size, input_rate, ymin=0, ymax=50): + block = fft_sink_c(fg, parent, title=title, fft_size=fft_size, sample_rate=input_rate, + y_per_div=(ymax - ymin)/8, ref_level=ymax) + return (block, block.win) + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_flow_graph (stdgui.gui_flow_graph): + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + fft_size = 256 + + # build our flow graph + input_rate = 20.48e3 + + # Generate a complex sinusoid + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = fft_sink_c (self, panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20) + vbox.Add (sink1.win, 1, wx.EXPAND) + self.connect (src1, thr1, sink1) + + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = fft_sink_f (self, panel, title="Real Data", fft_size=fft_size*2, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20) + vbox.Add (sink2.win, 1, wx.EXPAND) + self.connect (src2, thr2, sink2) + +def main (): + app = stdgui.stdapp (test_app_flow_graph, + "FFT Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py new file mode 100755 index 000000000..bb41817ca --- /dev/null +++ b/gr-wxgui/src/python/form.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# +# Copyright 2005 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +import wx +from gnuradio import eng_notation + +# ---------------------------------------------------------------- +# Wrappers for certain widgets +# ---------------------------------------------------------------- + +def button_with_callback(parent, label, callback): + new_id = wx.NewId() + btn = wx.Button(parent, new_id, label) + wx.EVT_BUTTON(parent, new_id, lambda evt: callback()) + return btn + + +# ---------------------------------------------------------------- +# Format converters +# ---------------------------------------------------------------- + +class abstract_converter(object): + def value_to_prim(self, v): + """ + Convert from user specified value to value acceptable to underlying primitive. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def prim_to_value(self, s): + """ + Convert from underlying primitive value to user specified value. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def help(self): + return "Any string is acceptable" + +class identity_converter(abstract_converter): + def value_to_prim(self,v): + return v + def prim_to_value(self, s): + return s + +class int_converter(abstract_converter): + def value_to_prim(self, v): + return str(v) + def prim_to_value(self, s): + return int(s, 0) + def help(self): + return "Enter an integer. Leading 0x indicates hex" + +class float_converter(abstract_converter): + def value_to_prim(self, v): + return eng_notation.num_to_str(v) + def prim_to_value(self, s): + return eng_notation.str_to_num(s) + def help(self): + return "Enter a float with optional scale suffix. E.g., 100.1M" + + +# ---------------------------------------------------------------- +# Various types of data entry fields +# ---------------------------------------------------------------- + +class field(object): + """ + A field in a form. + """ + def __init__(self, converter, value): + self.converter = converter + if value is not None: + self.set_value(value) + + def set_value(self, v): + self._set_prim_value(self.converter.value_to_prim(v)) + + def get_value(self): + return self.converter.prim_to_value(self._get_prim_value()) + + def get_value_with_check(self): + """ + Returns (value, error_msg), where error_msg is not None if there was problem + """ + try: + return (self.get_value(), None) + except: + return (None, self._error_msg()) + + def _set_prim_value(self, v): + raise NotImplementedError + + def _get_prim_value(self): + raise NotImplementedError + + def _pair_with_label(self, widget, parent=None, sizer=None, label=None, weight=1): + self.label = label + if label is None: + sizer.Add (widget, weight, wx.EXPAND) + return widget + elif 0: + hbox = wx.BoxSizer(wx.HORIZONTAL) + label_widget = wx.StaticText(parent, -1, label + ': ') + hbox.Add(label_widget, 0, wx.EXPAND) + hbox.Add(widget, 1, wx.EXPAND) + sizer.Add(hbox, weight, wx.EXPAND) + return widget + else: + label_widget = wx.StaticText(parent, -1, label + ': ') + sizer.Add(label_widget, 0, wx.EXPAND) + sizer.Add(widget, weight, wx.EXPAND) + return widget + + def _error_msg(self): + prefix = '' + if self.label: + prefix = self.label + ': ' + return "%s%s is invalid. %s" % (prefix, self._get_prim_value(), + self.converter.help()) + +# static (display-only) text fields + +class static_text_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), weight=0): + self.f = self._pair_with_label(wx.StaticText(parent, -1, ""), + parent=parent, sizer=sizer, label=label, weight=weight) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetLabel() + + def _set_prim_value(self, v): + self.f.SetLabel(v) + + +class static_int_field(static_text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): + static_text_field.__init__(self, parent, sizer, label, value, int_converter(), weight) + +class static_float_field(static_text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, weight=0): + static_text_field.__init__(self, parent, sizer, label, value, float_converter(), weight) + + +# editable text fields + +class text_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, weight=1): + style = 0 + if callback: + style = wx.TE_PROCESS_ENTER + + new_id = wx.NewId() + w = wx.TextCtrl(parent, new_id, "", style=style) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_TEXT_ENTER(w, new_id, lambda evt: callback()) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(v) + + +class int_field(text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + callback=None, weight=1): + text_field.__init__(self, parent, sizer, label, value, int_converter(), callback, weight) + +class float_field(text_field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + callback=None, weight=1): + text_field.__init__(self, parent, sizer, label, value, float_converter(), callback, weight) + +# other fields + +class slider_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, min=0, max=100, weight=1): + new_id = wx.NewId() + w = wx.Slider(parent, new_id, (max+min)/2, min, max, + size=wx.Size(250, -1), style=wx.SL_HORIZONTAL | wx.SL_LABELS) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_COMMAND_SCROLL(w, new_id, lambda evt: callback(evt.GetInt())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(int(v)) + +class quantized_slider_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, range=None, weight=1): + if not isinstance(range, (tuple, list)) or len(range) != 3: + raise ValueError, range + + self.min = range[0] + self.max = range[1] + self.step_size = float(range[2]) + nsteps = int((self.max-self.min)/self.step_size) + + new_id = wx.NewId() + w = wx.Slider(parent, new_id, 0, 0, nsteps, + size=wx.Size(250, -1), style=wx.SL_HORIZONTAL) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_COMMAND_SCROLL(w, new_id, + lambda evt: callback(self._map_out(evt.GetInt()))) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self._map_out(self.f.GetValue()) + + def _set_prim_value(self, v): + self.f.SetValue(self._map_in(v)) + + def _map_in(self, x): + return int((x-self.min) / self.step_size) + + def _map_out(self, x): + return x * self.step_size + self.min + +class checkbox_field(field): + def __init__(self, parent=None, sizer=None, label=None, value=None, + converter=identity_converter(), callback=None, weight=1): + new_id = wx.NewId() + w = wx.CheckBox(parent, new_id, label, style=wx.CHK_2STATE) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) + if callback: + wx.EVT_CHECKBOX(w, new_id, lambda evt: callback(evt.GetInt())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetValue() + + def _set_prim_value(self, v): + self.f.SetValue(int(v)) + + +class radiobox_field(field): + def __init__(self, parent=None, sizer=None, label="", value=None, + converter=identity_converter(), callback=None, weight=1, + choices=None, major_dimension=1, specify_rows=False): + new_id = wx.NewId() + + if specify_rows: + style=wx.RA_SPECIFY_ROWS | wx.RA_HORIZONTAL + else: + style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL + + w = wx.RadioBox(parent, new_id, label, style=style, majorDimension=major_dimension, + choices=choices) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + if callback: + wx.EVT_RADIOBOX(w, new_id, lambda evt: callback(evt.GetString())) + field.__init__(self, converter, value) + + def _get_prim_value(self): + return self.f.GetStringSelection() + + def _set_prim_value(self, v): + self.f.SetStringSelection(str(v)) + +# ---------------------------------------------------------------- +# the form class +# ---------------------------------------------------------------- + +class form(dict): + def __init__(self): + dict.__init__(self) + + def check_input_for_errors(self): + """ + Returns list of error messages if there's trouble, + else empty list. + """ + vals = [f.get_value_with_check() for f in self.values()] + return [t[1] for t in vals if t[1] is not None] + + def get_key_vals(self): + d = {} + for (key, f) in self.items(): + d[key] = f.get_value() + return d + + + def _nop(*args): pass + + def check_input_and_call(self, callback, status_handler=_nop): + """ + Return a function that checks the form for errors, and then if it's OK, + invokes the user specified callback, passing it the form key/value dictionary. + status_handler is called with a string indicating results. + """ + def doit_callback(*ignore): + errors = self.check_input_for_errors() + if errors: + status_handler(errors[0]) + #print '\n'.join(tuple(errors)) + else: + kv = self.get_key_vals() + if callback(kv): + status_handler("OK") + else: + status_handler("Failed") + + return doit_callback + + + +# ---------------------------------------------------------------- +# Stand-alone example code +# ---------------------------------------------------------------- + +import sys +from gnuradio.wxgui import stdgui + +class demo_app_flow_graph (stdgui.gui_flow_graph): + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + self.frame = frame + self.panel = panel + + def _print_kv(kv): + print "kv =", kv + return True + + self.form = form() + + self.form['static1'] = \ + static_text_field(parent=panel, sizer=vbox, + label="Static Text", + value="The Static Value") + + self.form['text1'] = \ + text_field(parent=panel, sizer=vbox, + label="TextCtrl", + value="The Editable Value") + + self.form['int1'] = \ + int_field(parent=panel, sizer=vbox, + label="Int Field", + value=1234) + + self.form['float1'] = \ + float_field(parent=panel, sizer=vbox, + label="Float Field", + value=3.14159) + + self.doit = button_with_callback( + panel, "Do It!", + self.form.check_input_and_call(_print_kv, self._set_status_msg)) + + vbox.Add(self.doit, 0, wx.CENTER) + + def _set_status_msg(self, msg): + self.frame.GetStatusBar().SetStatusText(msg, 0) + + +def main (): + app = stdgui.stdapp (demo_app_flow_graph, "wxgui form demo", nstatus=1) + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py new file mode 100644 index 000000000..d902d417c --- /dev/null +++ b/gr-wxgui/src/python/plot.py @@ -0,0 +1,1744 @@ +#----------------------------------------------------------------------------- +# Name: wx.lib.plot.py +# Purpose: Line, Bar and Scatter Graphs +# +# Author: Gordon Williams +# +# Created: 2003/11/03 +# RCS-ID: $Id$ +# Copyright: (c) 2002 +# Licence: Use as you wish. +#----------------------------------------------------------------------------- +# 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o 2.5 compatability update. +# o Renamed to plot.py in the wx.lib directory. +# o Reworked test frame to work with wx demo framework. This saves a bit +# of tedious cut and paste, and the test app is excellent. +# +# 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o wxScrolledMessageDialog -> ScrolledMessageDialog +# +# Oct 6, 2004 Gordon Williams (g_will@cyberus.ca) +# - Added bar graph demo +# - Modified line end shape from round to square. +# - Removed FloatDCWrapper for conversion to ints and ints in arguments +# +# Oct 15, 2004 Gordon Williams (g_will@cyberus.ca) +# - Imported modules given leading underscore to name. +# - Added Cursor Line Tracking and User Point Labels. +# - Demo for Cursor Line Tracking and Point Labels. +# - Size of plot preview frame adjusted to show page better. +# - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas. +# - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve) +# can be in either user coords or screen coords. +# +# + +""" +This is a simple light weight plotting module that can be used with +Boa or easily integrated into your own wxPython application. The +emphasis is on small size and fast plotting for large data sets. It +has a reasonable number of features to do line and scatter graphs +easily as well as simple bar graphs. It is not as sophisticated or +as powerful as SciPy Plt or Chaco. Both of these are great packages +but consume huge amounts of computer resources for simple plots. +They can be found at http://scipy.com + +This file contains two parts; first the re-usable library stuff, then, +after a "if __name__=='__main__'" test, a simple frame and a few default +plots for examples and testing. + +Based on wxPlotCanvas +Written by K.Hinsen, R. Srinivasan; +Ported to wxPython Harm van der Heijden, feb 1999 + +Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca) + -More style options + -Zooming using mouse 'rubber band' + -Scroll left, right + -Grid(graticule) + -Printing, preview, and page set up (margins) + -Axis and title labels + -Cursor xy axis values + -Doc strings and lots of comments + -Optimizations for large number of points + -Legends + +Did a lot of work here to speed markers up. Only a factor of 4 +improvement though. Lines are much faster than markers, especially +filled markers. Stay away from circles and triangles unless you +only have a few thousand points. + +Times for 25,000 points +Line - 0.078 sec +Markers +Square - 0.22 sec +dot - 0.10 +circle - 0.87 +cross,plus - 0.28 +triangle, triangle_down - 0.90 + +Thanks to Chris Barker for getting this version working on Linux. + +Zooming controls with mouse (when enabled): + Left mouse drag - Zoom box. + Left mouse double click - reset zoom. + Right mouse click - zoom out centred on click location. +""" + +import string as _string +import time as _time +import wx + +# Needs Numeric or numarray +try: + import Numeric as _Numeric +except: + try: + import numarray as _Numeric #if numarray is used it is renamed Numeric + except: + msg= """ + This module requires the Numeric or numarray module, + which could not be imported. It probably is not installed + (it's not part of the standard Python distribution). See the + Python site (http://www.python.org) for information on + downloading source or binaries.""" + raise ImportError, "Numeric or numarray not found. \n" + msg + + + +# +# Plotting classes... +# +class PolyPoints: + """Base Class for lines and markers + - All methods are private. + """ + + def __init__(self, points, attr): + self.points = _Numeric.array(points) + self.currentScale= (1,1) + self.currentShift= (0,0) + self.scaled = self.points + self.attributes = {} + self.attributes.update(self._attributes) + for name, value in attr.items(): + if name not in self._attributes.keys(): + raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys() + self.attributes[name] = value + + def boundingBox(self): + if len(self.points) == 0: + # no curves to draw + # defaults to (-1,-1) and (1,1) but axis can be set in Draw + minXY= _Numeric.array([-1,-1]) + maxXY= _Numeric.array([ 1, 1]) + else: + minXY= _Numeric.minimum.reduce(self.points) + maxXY= _Numeric.maximum.reduce(self.points) + return minXY, maxXY + + def scaleAndShift(self, scale=(1,1), shift=(0,0)): + if len(self.points) == 0: + # no curves to draw + return + if (scale is not self.currentScale) or (shift is not self.currentShift): + # update point scaling + self.scaled = scale*self.points+shift + self.currentScale= scale + self.currentShift= shift + # else unchanged use the current scaling + + def getLegend(self): + return self.attributes['legend'] + + def getClosestPoint(self, pntXY, pointScaled= True): + """Returns the index of closest point on the curve, pointXY, scaledXY, distance + x, y in user coords + if pointScaled == True based on screen coords + if pointScaled == False based on user coords + """ + if pointScaled == True: + #Using screen coords + p = self.scaled + pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift + else: + #Using user coords + p = self.points + pxy = _Numeric.array(pntXY) + #determine distance for each point + d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2) + pntIndex = _Numeric.argmin(d) + dist = d[pntIndex] + return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist] + + +class PolyLine(PolyPoints): + """Class to define line type and style + - All methods except __init__ are private. + """ + + _attributes = {'colour': 'black', + 'width': 1, + 'style': wx.SOLID, + 'legend': ''} + + def __init__(self, points, **attr): + """Creates PolyLine object + points - sequence (array, tuple or list) of (x,y) points making up line + **attr - key word attributes + Defaults: + 'colour'= 'black', - wx.Pen Colour any wx.NamedColour + 'width'= 1, - Pen width + 'style'= wx.SOLID, - wx.Pen style + 'legend'= '' - Line Legend to display + """ + PolyPoints.__init__(self, points, attr) + + def draw(self, dc, printerScale, coord= None): + colour = self.attributes['colour'] + width = self.attributes['width'] * printerScale + style= self.attributes['style'] + pen = wx.Pen(wx.NamedColour(colour), width, style) + pen.SetCap(wx.CAP_BUTT) + dc.SetPen(pen) + if coord == None: + dc.DrawLines(self.scaled) + else: + dc.DrawLines(coord) # draw legend line + + def getSymExtent(self, printerScale): + """Width and Height of Marker""" + h= self.attributes['width'] * printerScale + w= 5 * h + return (w,h) + + +class PolyMarker(PolyPoints): + """Class to define marker type and style + - All methods except __init__ are private. + """ + + _attributes = {'colour': 'black', + 'width': 1, + 'size': 2, + 'fillcolour': None, + 'fillstyle': wx.SOLID, + 'marker': 'circle', + 'legend': ''} + + def __init__(self, points, **attr): + """Creates PolyMarker object + points - sequence (array, tuple or list) of (x,y) points + **attr - key word attributes + Defaults: + 'colour'= 'black', - wx.Pen Colour any wx.NamedColour + 'width'= 1, - Pen width + 'size'= 2, - Marker size + 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour + 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill) + 'marker'= 'circle' - Marker shape + 'legend'= '' - Marker Legend to display + + Marker Shapes: + - 'circle' + - 'dot' + - 'square' + - 'triangle' + - 'triangle_down' + - 'cross' + - 'plus' + """ + + PolyPoints.__init__(self, points, attr) + + def draw(self, dc, printerScale, coord= None): + colour = self.attributes['colour'] + width = self.attributes['width'] * printerScale + size = self.attributes['size'] * printerScale + fillcolour = self.attributes['fillcolour'] + fillstyle = self.attributes['fillstyle'] + marker = self.attributes['marker'] + + dc.SetPen(wx.Pen(wx.NamedColour(colour), width)) + if fillcolour: + dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle)) + else: + dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle)) + if coord == None: + self._drawmarkers(dc, self.scaled, marker, size) + else: + self._drawmarkers(dc, coord, marker, size) # draw legend marker + + def getSymExtent(self, printerScale): + """Width and Height of Marker""" + s= 5*self.attributes['size'] * printerScale + return (s,s) + + def _drawmarkers(self, dc, coords, marker,size=1): + f = eval('self._' +marker) + f(dc, coords, size) + + def _circle(self, dc, coords, size=1): + fact= 2.5*size + wh= 5.0*size + rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh] + rect[:,0:2]= coords-[fact,fact] + dc.DrawEllipseList(rect.astype(_Numeric.Int32)) + + def _dot(self, dc, coords, size=1): + dc.DrawPointList(coords) + + def _square(self, dc, coords, size=1): + fact= 2.5*size + wh= 5.0*size + rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh] + rect[:,0:2]= coords-[fact,fact] + dc.DrawRectangleList(rect.astype(_Numeric.Int32)) + + def _triangle(self, dc, coords, size=1): + shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)] + poly= _Numeric.repeat(coords,3) + poly.shape= (len(coords),3,2) + poly += shape + dc.DrawPolygonList(poly.astype(_Numeric.Int32)) + + def _triangle_down(self, dc, coords, size=1): + shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)] + poly= _Numeric.repeat(coords,3) + poly.shape= (len(coords),3,2) + poly += shape + dc.DrawPolygonList(poly.astype(_Numeric.Int32)) + + def _cross(self, dc, coords, size=1): + fact= 2.5*size + for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]: + lines= _Numeric.concatenate((coords,coords),axis=1)+f + dc.DrawLineList(lines.astype(_Numeric.Int32)) + + def _plus(self, dc, coords, size=1): + fact= 2.5*size + for f in [[-fact,0,fact,0],[0,-fact,0,fact]]: + lines= _Numeric.concatenate((coords,coords),axis=1)+f + dc.DrawLineList(lines.astype(_Numeric.Int32)) + +class PlotGraphics: + """Container to hold PolyXXX objects and graph labels + - All methods except __init__ are private. + """ + + def __init__(self, objects, title='', xLabel='', yLabel= ''): + """Creates PlotGraphics object + objects - list of PolyXXX objects to make graph + title - title shown at top of graph + xLabel - label shown on x-axis + yLabel - label shown on y-axis + """ + if type(objects) not in [list,tuple]: + raise TypeError, "objects argument should be list or tuple" + self.objects = objects + self.title= title + self.xLabel= xLabel + self.yLabel= yLabel + + def boundingBox(self): + p1, p2 = self.objects[0].boundingBox() + for o in self.objects[1:]: + p1o, p2o = o.boundingBox() + p1 = _Numeric.minimum(p1, p1o) + p2 = _Numeric.maximum(p2, p2o) + return p1, p2 + + def scaleAndShift(self, scale=(1,1), shift=(0,0)): + for o in self.objects: + o.scaleAndShift(scale, shift) + + def setPrinterScale(self, scale): + """Thickens up lines and markers only for printing""" + self.printerScale= scale + + def setXLabel(self, xLabel= ''): + """Set the X axis label on the graph""" + self.xLabel= xLabel + + def setYLabel(self, yLabel= ''): + """Set the Y axis label on the graph""" + self.yLabel= yLabel + + def setTitle(self, title= ''): + """Set the title at the top of graph""" + self.title= title + + def getXLabel(self): + """Get x axis label string""" + return self.xLabel + + def getYLabel(self): + """Get y axis label string""" + return self.yLabel + + def getTitle(self, title= ''): + """Get the title at the top of graph""" + return self.title + + def draw(self, dc): + for o in self.objects: + #t=_time.clock() # profile info + o.draw(dc, self.printerScale) + #dt= _time.clock()-t + #print o, "time=", dt + + def getSymExtent(self, printerScale): + """Get max width and height of lines and markers symbols for legend""" + symExt = self.objects[0].getSymExtent(printerScale) + for o in self.objects[1:]: + oSymExt = o.getSymExtent(printerScale) + symExt = _Numeric.maximum(symExt, oSymExt) + return symExt + + def getLegendNames(self): + """Returns list of legend names""" + lst = [None]*len(self) + for i in range(len(self)): + lst[i]= self.objects[i].getLegend() + return lst + + def __len__(self): + return len(self.objects) + + def __getitem__(self, item): + return self.objects[item] + + +#------------------------------------------------------------------------------- +# Main window that you will want to import into your application. + +class PlotCanvas(wx.Window): + """Subclass of a wx.Window to allow simple general plotting + of data with zoom, labels, and automatic axis scaling.""" + + def __init__(self, parent, id = -1, pos=wx.DefaultPosition, + size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""): + """Constucts a window, which can be a child of a frame, dialog or + any other non-control window""" + + wx.Window.__init__(self, parent, id, pos, size, style, name) + self.border = (1,1) + + self.SetBackgroundColour("white") + + # Create some mouse events for zooming + self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick) + self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown) + + # set curser as cross-hairs + self.SetCursor(wx.CROSS_CURSOR) + + # Things for printing + self.print_data = wx.PrintData() + self.print_data.SetPaperId(wx.PAPER_LETTER) + self.print_data.SetOrientation(wx.LANDSCAPE) + self.pageSetupData= wx.PageSetupDialogData() + self.pageSetupData.SetMarginBottomRight((25,25)) + self.pageSetupData.SetMarginTopLeft((25,25)) + self.pageSetupData.SetPrintData(self.print_data) + self.printerScale = 1 + self.parent= parent + + # Zooming variables + self._zoomInFactor = 0.5 + self._zoomOutFactor = 2 + self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner + self._zoomCorner2= _Numeric.array([0.0, 0.0]) # left mouse up corner + self._zoomEnabled= False + self._hasDragged= False + + # Drawing Variables + self.last_draw = None + self._pointScale= 1 + self._pointShift= 0 + self._xSpec= 'auto' + self._ySpec= 'auto' + self._gridEnabled= False + self._legendEnabled= False + self._xUseScopeTicks= False + + # Fonts + self._fontCache = {} + self._fontSizeAxis= 10 + self._fontSizeTitle= 15 + self._fontSizeLegend= 7 + + # pointLabels + self._pointLabelEnabled= False + self.last_PointLabel= None + self._pointLabelFunc= None + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_SIZE, self.OnSize) + # OnSize called to make sure the buffer is initialized. + # This might result in OnSize getting called twice on some + # platforms at initialization, but little harm done. + self.OnSize(None) # sets the initial size based on client size + # UNCONDITIONAL, needed to create self._Buffer + + # SaveFile + def SaveFile(self, fileName= ''): + """Saves the file to the type specified in the extension. If no file + name is specified a dialog box is provided. Returns True if sucessful, + otherwise False. + + .bmp Save a Windows bitmap file. + .xbm Save an X bitmap file. + .xpm Save an XPM bitmap file. + .png Save a Portable Network Graphics file. + .jpg Save a Joint Photographic Experts Group file. + """ + if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']: + dlg1 = wx.FileDialog( + self, + "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "", + "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg", + wx.SAVE|wx.OVERWRITE_PROMPT + ) + try: + while 1: + if dlg1.ShowModal() == wx.ID_OK: + fileName = dlg1.GetPath() + # Check for proper exension + if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']: + dlg2 = wx.MessageDialog(self, 'File name extension\n' + 'must be one of\n' + 'bmp, xbm, xpm, png, or jpg', + 'File Name Error', wx.OK | wx.ICON_ERROR) + try: + dlg2.ShowModal() + finally: + dlg2.Destroy() + else: + break # now save file + else: # exit without saving + return False + finally: + dlg1.Destroy() + + # File name has required extension + fType = _string.lower(fileName[-3:]) + if fType == "bmp": + tp= wx.BITMAP_TYPE_BMP # Save a Windows bitmap file. + elif fType == "xbm": + tp= wx.BITMAP_TYPE_XBM # Save an X bitmap file. + elif fType == "xpm": + tp= wx.BITMAP_TYPE_XPM # Save an XPM bitmap file. + elif fType == "jpg": + tp= wx.BITMAP_TYPE_JPEG # Save a JPG file. + else: + tp= wx.BITMAP_TYPE_PNG # Save a PNG file. + # Save Bitmap + res= self._Buffer.SaveFile(fileName, tp) + return res + + def PageSetup(self): + """Brings up the page setup dialog""" + data = self.pageSetupData + data.SetPrintData(self.print_data) + dlg = wx.PageSetupDialog(self.parent, data) + try: + if dlg.ShowModal() == wx.ID_OK: + data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData + # updates page parameters from dialog + self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight()) + self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft()) + self.pageSetupData.SetPrintData(data.GetPrintData()) + self.print_data=data.GetPrintData() # updates print_data + finally: + dlg.Destroy() + + def Printout(self, paper=None): + """Print current plot.""" + if paper != None: + self.print_data.SetPaperId(paper) + pdd = wx.PrintDialogData() + pdd.SetPrintData(self.print_data) + printer = wx.Printer(pdd) + out = PlotPrintout(self) + print_ok = printer.Print(self.parent, out) + if print_ok: + self.print_data = printer.GetPrintDialogData().GetPrintData() + out.Destroy() + + def PrintPreview(self): + """Print-preview current plot.""" + printout = PlotPrintout(self) + printout2 = PlotPrintout(self) + self.preview = wx.PrintPreview(printout, printout2, self.print_data) + if not self.preview.Ok(): + wx.MessageDialog(self, "Print Preview failed.\n" \ + "Check that default printer is configured\n", \ + "Print error", wx.OK|wx.CENTRE).ShowModal() + self.preview.SetZoom(40) + # search up tree to find frame instance + frameInst= self + while not isinstance(frameInst, wx.Frame): + frameInst= frameInst.GetParent() + frame = wx.PreviewFrame(self.preview, frameInst, "Preview") + frame.Initialize() + frame.SetPosition(self.GetPosition()) + frame.SetSize((600,550)) + frame.Centre(wx.BOTH) + frame.Show(True) + + def SetFontSizeAxis(self, point= 10): + """Set the tick and axis label font size (default is 10 point)""" + self._fontSizeAxis= point + + def GetFontSizeAxis(self): + """Get current tick and axis label font size in points""" + return self._fontSizeAxis + + def SetFontSizeTitle(self, point= 15): + """Set Title font size (default is 15 point)""" + self._fontSizeTitle= point + + def GetFontSizeTitle(self): + """Get current Title font size in points""" + return self._fontSizeTitle + + def SetFontSizeLegend(self, point= 7): + """Set Legend font size (default is 7 point)""" + self._fontSizeLegend= point + + def GetFontSizeLegend(self): + """Get current Legend font size in points""" + return self._fontSizeLegend + + def SetEnableZoom(self, value): + """Set True to enable zooming.""" + if value not in [True,False]: + raise TypeError, "Value should be True or False" + self._zoomEnabled= value + + def GetEnableZoom(self): + """True if zooming enabled.""" + return self._zoomEnabled + + def SetEnableGrid(self, value): + """Set True to enable grid.""" + if value not in [True,False]: + raise TypeError, "Value should be True or False" + self._gridEnabled= value + self.Redraw() + + def GetEnableGrid(self): + """True if grid enabled.""" + return self._gridEnabled + + def SetEnableLegend(self, value): + """Set True to enable legend.""" + if value not in [True,False]: + raise TypeError, "Value should be True or False" + self._legendEnabled= value + self.Redraw() + + def GetEnableLegend(self): + """True if Legend enabled.""" + return self._legendEnabled + + def SetEnablePointLabel(self, value): + """Set True to enable pointLabel.""" + if value not in [True,False]: + raise TypeError, "Value should be True or False" + self._pointLabelEnabled= value + self.Redraw() #will erase existing pointLabel if present + self.last_PointLabel = None + + def GetEnablePointLabel(self): + """True if pointLabel enabled.""" + return self._pointLabelEnabled + + def SetPointLabelFunc(self, func): + """Sets the function with custom code for pointLabel drawing + ******** more info needed *************** + """ + self._pointLabelFunc= func + + def GetPointLabelFunc(self): + """Returns pointLabel Drawing Function""" + return self._pointLabelFunc + + def Reset(self): + """Unzoom the plot.""" + self.last_PointLabel = None #reset pointLabel + if self.last_draw is not None: + self.Draw(self.last_draw[0]) + + def ScrollRight(self, units): + """Move view right number of axis units.""" + self.last_PointLabel = None #reset pointLabel + if self.last_draw is not None: + graphics, xAxis, yAxis= self.last_draw + xAxis= (xAxis[0]+units, xAxis[1]+units) + self.Draw(graphics,xAxis,yAxis) + + def ScrollUp(self, units): + """Move view up number of axis units.""" + self.last_PointLabel = None #reset pointLabel + if self.last_draw is not None: + graphics, xAxis, yAxis= self.last_draw + yAxis= (yAxis[0]+units, yAxis[1]+units) + self.Draw(graphics,xAxis,yAxis) + + def GetXY(self,event): + """Takes a mouse event and returns the XY user axis values.""" + x,y= self.PositionScreenToUser(event.GetPosition()) + return x,y + + def PositionUserToScreen(self, pntXY): + """Converts User position to Screen Coordinates""" + userPos= _Numeric.array(pntXY) + x,y= userPos * self._pointScale + self._pointShift + return x,y + + def PositionScreenToUser(self, pntXY): + """Converts Screen position to User Coordinates""" + screenPos= _Numeric.array(pntXY) + x,y= (screenPos-self._pointShift)/self._pointScale + return x,y + + def SetXSpec(self, type= 'auto'): + """xSpec- defines x axis type. Can be 'none', 'min' or 'auto' + where: + 'none' - shows no axis or tick mark values + 'min' - shows min bounding box values + 'auto' - rounds axis range to sensible values + """ + self._xSpec= type + + def SetYSpec(self, type= 'auto'): + """ySpec- defines x axis type. Can be 'none', 'min' or 'auto' + where: + 'none' - shows no axis or tick mark values + 'min' - shows min bounding box values + 'auto' - rounds axis range to sensible values + """ + self._ySpec= type + + def GetXSpec(self): + """Returns current XSpec for axis""" + return self._xSpec + + def GetYSpec(self): + """Returns current YSpec for axis""" + return self._ySpec + + def GetXMaxRange(self): + """Returns (minX, maxX) x-axis range for displayed graph""" + graphics= self.last_draw[0] + p1, p2 = graphics.boundingBox() # min, max points of graphics + xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units + return xAxis + + def GetYMaxRange(self): + """Returns (minY, maxY) y-axis range for displayed graph""" + graphics= self.last_draw[0] + p1, p2 = graphics.boundingBox() # min, max points of graphics + yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) + return yAxis + + def GetXCurrentRange(self): + """Returns (minX, maxX) x-axis for currently displayed portion of graph""" + return self.last_draw[1] + + def GetYCurrentRange(self): + """Returns (minY, maxY) y-axis for currently displayed portion of graph""" + return self.last_draw[2] + + def SetXUseScopeTicks(self, v=False): + """Always 10 divisions, no labels""" + self._xUseScopeTicks = v + + def GetXUseScopeTicks(self): + return self._xUseScopeTicks + + def Draw(self, graphics, xAxis = None, yAxis = None, dc = None): + """Draw objects in graphics with specified x and y axis. + graphics- instance of PlotGraphics with list of PolyXXX objects + xAxis - tuple with (min, max) axis range to view + yAxis - same as xAxis + dc - drawing context - doesn't have to be specified. + If it's not, the offscreen buffer is used + """ + # check Axis is either tuple or none + if type(xAxis) not in [type(None),tuple]: + raise TypeError, "xAxis should be None or (minX,maxX)" + if type(yAxis) not in [type(None),tuple]: + raise TypeError, "yAxis should be None or (minY,maxY)" + + # check case for axis = (a,b) where a==b caused by improper zooms + if xAxis != None: + if xAxis[0] == xAxis[1]: + return + if yAxis != None: + if yAxis[0] == yAxis[1]: + return + + if dc == None: + # sets new dc and clears it + dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) + dc.Clear() + + dc.BeginDrawing() + # dc.Clear() + + # set font size for every thing but title and legend + dc.SetFont(self._getFont(self._fontSizeAxis)) + + # sizes axis to axis type, create lower left and upper right corners of plot + if xAxis == None or yAxis == None: + # One or both axis not specified in Draw + p1, p2 = graphics.boundingBox() # min, max points of graphics + if xAxis == None: + xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units + if yAxis == None: + yAxis = self._axisInterval(self._ySpec, p1[1], p2[1]) + # Adjust bounding box for axis spec + p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin) + p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax) + else: + # Both axis specified in Draw + p1= _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin) + p2= _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax) + + self.last_draw = (graphics, xAxis, yAxis) # saves most recient values + + # Get ticks and textExtents for axis if required + if self._xSpec is not 'none': + if self._xUseScopeTicks: + xticks = self._scope_ticks(xAxis[0], xAxis[1]) + else: + xticks = self._ticks(xAxis[0], xAxis[1]) + xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis + else: + xticks = None + xTextExtent= (0,0) # No text for ticks + if self._ySpec is not 'none': + yticks = self._ticks(yAxis[0], yAxis[1]) + yTextExtentBottom= dc.GetTextExtent(yticks[0][1]) + yTextExtentTop = dc.GetTextExtent(yticks[-1][1]) + yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]), + max(yTextExtentBottom[1],yTextExtentTop[1])) + else: + yticks = None + yTextExtent= (0,0) # No text for ticks + + # TextExtents for Title and Axis Labels + titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics) + + # TextExtents for Legend + legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics) + + # room around graph area + rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width + lhsW= yTextExtent[0]+ yLabelWH[1] + bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1] + topH= yTextExtent[1]/2. + titleWH[1] + textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size + textSize_shift= _Numeric.array([lhsW, bottomH]) # shift plot area by this amount + + # drawing title and labels text + dc.SetFont(self._getFont(self._fontSizeTitle)) + titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2., + self.plotbox_origin[1]- self.plotbox_size[1]) + dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1]) + dc.SetFont(self._getFont(self._fontSizeAxis)) + xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2., + self.plotbox_origin[1]- xLabelWH[1]) + dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1]) + yLabelPos= (self.plotbox_origin[0], + self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.) + if graphics.getYLabel(): # bug fix for Linux + dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90) + + # drawing legend makers and text + if self._legendEnabled: + self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt) + + # allow for scaling and shifting plotted points + scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1)) + shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1)) + self._pointScale= scale # make available for mouse events + self._pointShift= shift + self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) + + graphics.scaleAndShift(scale, shift) + graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing + + # set clipping area so drawing does not occur outside axis box + ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) + dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight) + # Draw the lines and markers + #start = _time.clock() + graphics.draw(dc) + # print "entire graphics drawing took: %f second"%(_time.clock() - start) + # remove the clipping region + dc.DestroyClippingRegion() + dc.EndDrawing() + + def Redraw(self, dc= None): + """Redraw the existing plot.""" + if self.last_draw is not None: + graphics, xAxis, yAxis= self.last_draw + self.Draw(graphics,xAxis,yAxis,dc) + + def Clear(self): + """Erase the window.""" + self.last_PointLabel = None #reset pointLabel + dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) + dc.Clear() + self.last_draw = None + + def Zoom(self, Center, Ratio): + """ Zoom on the plot + Centers on the X,Y coords given in Center + Zooms by the Ratio = (Xratio, Yratio) given + """ + self.last_PointLabel = None #reset maker + x,y = Center + if self.last_draw != None: + (graphics, xAxis, yAxis) = self.last_draw + w = (xAxis[1] - xAxis[0]) * Ratio[0] + h = (yAxis[1] - yAxis[0]) * Ratio[1] + xAxis = ( x - w/2, x + w/2 ) + yAxis = ( y - h/2, y + h/2 ) + self.Draw(graphics, xAxis, yAxis) + + def GetClosestPoints(self, pntXY, pointScaled= True): + """Returns list with + [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] + list for each curve. + Returns [] if no curves are being plotted. + + x, y in user coords + if pointScaled == True based on screen coords + if pointScaled == False based on user coords + """ + if self.last_draw == None: + #no graph available + return [] + graphics, xAxis, yAxis= self.last_draw + l = [] + for curveNum,obj in enumerate(graphics): + #check there are points in the curve + if len(obj.points) == 0: + continue #go to next obj + #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance] + cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled) + l.append(cn) + return l + + def GetClosetPoint(self, pntXY, pointScaled= True): + """Returns list with + [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] + list for only the closest curve. + Returns [] if no curves are being plotted. + + x, y in user coords + if pointScaled == True based on screen coords + if pointScaled == False based on user coords + """ + #closest points on screen based on screen scaling (pointScaled= True) + #list [curveNumber, index, pointXY, scaledXY, distance] for each curve + closestPts= self.GetClosestPoints(pntXY, pointScaled) + if closestPts == []: + return [] #no graph present + #find one with least distance + dists = [c[-1] for c in closestPts] + mdist = min(dists) #Min dist + i = dists.index(mdist) #index for min dist + return closestPts[i] #this is the closest point on closest curve + + def UpdatePointLabel(self, mDataDict): + """Updates the pointLabel point on screen with data contained in + mDataDict. + + mDataDict will be passed to your function set by + SetPointLabelFunc. It can contain anything you + want to display on the screen at the scaledXY point + you specify. + + This function can be called from parent window with onClick, + onMotion events etc. + """ + if self.last_PointLabel != None: + #compare pointXY + if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]: + #closest changed + self._drawPointLabel(self.last_PointLabel) #erase old + self._drawPointLabel(mDataDict) #plot new + else: + #just plot new with no erase + self._drawPointLabel(mDataDict) #plot new + #save for next erase + self.last_PointLabel = mDataDict + + # event handlers ********************************** + def OnMotion(self, event): + if self._zoomEnabled and event.LeftIsDown(): + if self._hasDragged: + self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old + else: + self._hasDragged= True + self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event) + self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new + + def OnMouseLeftDown(self,event): + self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event) + + def OnMouseLeftUp(self, event): + if self._zoomEnabled: + if self._hasDragged == True: + self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old + self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event) + self._hasDragged = False # reset flag + minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2) + maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2) + self.last_PointLabel = None #reset pointLabel + if self.last_draw != None: + self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None) + #else: # A box has not been drawn, zoom in on a point + ## this interfered with the double click, so I've disables it. + # X,Y = self.GetXY(event) + # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) ) + + def OnMouseDoubleClick(self,event): + if self._zoomEnabled: + self.Reset() + + def OnMouseRightDown(self,event): + if self._zoomEnabled: + X,Y = self.GetXY(event) + self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) ) + + def OnPaint(self, event): + # All that is needed here is to draw the buffer to screen + if self.last_PointLabel != None: + self._drawPointLabel(self.last_PointLabel) #erase old + self.last_PointLabel = None + dc = wx.BufferedPaintDC(self, self._Buffer) + + def OnSize(self,event): + # The Buffer init is done here, to make sure the buffer is always + # the same size as the Window + Size = self.GetClientSize() + + # Make new offscreen bitmap: this bitmap will always have the + # current drawing in it, so it can be used to save the image to + # a file, or whatever. + self._Buffer = wx.EmptyBitmap(Size[0],Size[1]) + self._setSize() + + self.last_PointLabel = None #reset pointLabel + + if self.last_draw is None: + self.Clear() + else: + graphics, xSpec, ySpec = self.last_draw + self.Draw(graphics,xSpec,ySpec) + + def OnLeave(self, event): + """Used to erase pointLabel when mouse outside window""" + if self.last_PointLabel != None: + self._drawPointLabel(self.last_PointLabel) #erase old + self.last_PointLabel = None + + + # Private Methods ************************************************** + def _setSize(self, width=None, height=None): + """DC width and height.""" + if width == None: + (self.width,self.height) = self.GetClientSize() + else: + self.width, self.height= width,height + self.plotbox_size = 0.97*_Numeric.array([self.width, self.height]) + xo = 0.5*(self.width-self.plotbox_size[0]) + yo = self.height-0.5*(self.height-self.plotbox_size[1]) + self.plotbox_origin = _Numeric.array([xo, yo]) + + def _setPrinterScale(self, scale): + """Used to thicken lines and increase marker size for print out.""" + # line thickness on printer is very thin at 600 dot/in. Markers small + self.printerScale= scale + + def _printDraw(self, printDC): + """Used for printing.""" + if self.last_draw != None: + graphics, xSpec, ySpec= self.last_draw + self.Draw(graphics,xSpec,ySpec,printDC) + + def _drawPointLabel(self, mDataDict): + """Draws and erases pointLabels""" + width = self._Buffer.GetWidth() + height = self._Buffer.GetHeight() + tmp_Buffer = wx.EmptyBitmap(width,height) + dcs = wx.MemoryDC() + dcs.SelectObject(tmp_Buffer) + dcs.Clear() + dcs.BeginDrawing() + self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function + dcs.EndDrawing() + + dc = wx.ClientDC( self ) + #this will erase if called twice + dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst + + + def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt): + """Draws legend symbols and text""" + # top right hand corner of graph box is ref corner + trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1] + legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box + lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines + dc.SetFont(self._getFont(self._fontSizeLegend)) + for i in range(len(graphics)): + o = graphics[i] + s= i*lineHeight + if isinstance(o,PolyMarker): + # draw marker with legend + pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.) + o.draw(dc, self.printerScale, coord= _Numeric.array([pnt])) + elif isinstance(o,PolyLine): + # draw line with legend + pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.) + pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.) + o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2])) + else: + raise TypeError, "object is neither PolyMarker or PolyLine instance" + # draw legend txt + pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2) + dc.DrawText(o.getLegend(),pnt[0],pnt[1]) + dc.SetFont(self._getFont(self._fontSizeAxis)) # reset + + def _titleLablesWH(self, dc, graphics): + """Draws Title and labels and returns width and height for each""" + # TextExtents for Title and Axis Labels + dc.SetFont(self._getFont(self._fontSizeTitle)) + title= graphics.getTitle() + titleWH= dc.GetTextExtent(title) + dc.SetFont(self._getFont(self._fontSizeAxis)) + xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel() + xLabelWH= dc.GetTextExtent(xLabel) + yLabelWH= dc.GetTextExtent(yLabel) + return titleWH, xLabelWH, yLabelWH + + def _legendWH(self, dc, graphics): + """Returns the size in screen units for legend box""" + if self._legendEnabled != True: + legendBoxWH= symExt= txtExt= (0,0) + else: + # find max symbol size + symExt= graphics.getSymExtent(self.printerScale) + # find max legend text extent + dc.SetFont(self._getFont(self._fontSizeLegend)) + txtList= graphics.getLegendNames() + txtExt= dc.GetTextExtent(txtList[0]) + for txt in graphics.getLegendNames()[1:]: + txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt)) + maxW= symExt[0]+txtExt[0] + maxH= max(symExt[1],txtExt[1]) + # padding .1 for lhs of legend box and space between lines + maxW= maxW* 1.1 + maxH= maxH* 1.1 * len(txtList) + dc.SetFont(self._getFont(self._fontSizeAxis)) + legendBoxWH= (maxW,maxH) + return (legendBoxWH, symExt, txtExt) + + def _drawRubberBand(self, corner1, corner2): + """Draws/erases rect box from corner1 to corner2""" + ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2) + # draw rectangle + dc = wx.ClientDC( self ) + dc.BeginDrawing() + dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) ) + dc.SetLogicalFunction(wx.INVERT) + dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) + dc.SetLogicalFunction(wx.COPY) + dc.EndDrawing() + + def _getFont(self,size): + """Take font size, adjusts if printing and returns wx.Font""" + s = size*self.printerScale + of = self.GetFont() + # Linux speed up to get font from cache rather than X font server + key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ()) + font = self._fontCache.get (key, None) + if font: + return font # yeah! cache hit + else: + font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight()) + self._fontCache[key] = font + return font + + + def _point2ClientCoord(self, corner1, corner2): + """Converts user point coords to client screen int coords x,y,width,height""" + c1= _Numeric.array(corner1) + c2= _Numeric.array(corner2) + # convert to screen coords + pt1= c1*self._pointScale+self._pointShift + pt2= c2*self._pointScale+self._pointShift + # make height and width positive + pul= _Numeric.minimum(pt1,pt2) # Upper left corner + plr= _Numeric.maximum(pt1,pt2) # Lower right corner + rectWidth, rectHeight= plr-pul + ptx,pty= pul + return ptx, pty, rectWidth, rectHeight + + def _axisInterval(self, spec, lower, upper): + """Returns sensible axis range for given spec""" + if spec == 'none' or spec == 'min': + if lower == upper: + return lower-0.5, upper+0.5 + else: + return lower, upper + elif spec == 'auto': + range = upper-lower + # if range == 0.: + if abs(range) < 1e-36: + return lower-0.5, upper+0.5 + log = _Numeric.log10(range) + power = _Numeric.floor(log) + fraction = log-power + if fraction <= 0.05: + power = power-1 + grid = 10.**power + lower = lower - lower % grid + mod = upper % grid + if mod != 0: + upper = upper - mod + grid + return lower, upper + elif type(spec) == type(()): + lower, upper = spec + if lower <= upper: + return lower, upper + else: + return upper, lower + else: + raise ValueError, str(spec) + ': illegal axis specification' + + def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks): + + penWidth= self.printerScale # increases thickness for printing only + dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth)) + + # set length of tick marks--long ones make grid + if self._gridEnabled: + x,y,width,height= self._point2ClientCoord(p1,p2) + yTickLength= width/2.0 +1 + xTickLength= height/2.0 +1 + else: + yTickLength= 3 * self.printerScale # lengthens lines for printing + xTickLength= 3 * self.printerScale + + if self._xSpec is not 'none': + lower, upper = p1[0],p2[0] + text = 1 + for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths + a1 = scale*_Numeric.array([lower, y])+shift + a2 = scale*_Numeric.array([upper, y])+shift + dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line + for x, label in xticks: + pt = scale*_Numeric.array([x, y])+shift + dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units + if text: + dc.DrawText(label,pt[0],pt[1]) + text = 0 # axis values not drawn on top side + + if self._ySpec is not 'none': + lower, upper = p1[1],p2[1] + text = 1 + h = dc.GetCharHeight() + for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]: + a1 = scale*_Numeric.array([x, lower])+shift + a2 = scale*_Numeric.array([x, upper])+shift + dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) + for y, label in yticks: + pt = scale*_Numeric.array([x, y])+shift + dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1]) + if text: + dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0], + pt[1]-0.5*h) + text = 0 # axis values not drawn on right side + + def _ticks(self, lower, upper): + ideal = (upper-lower)/7. + log = _Numeric.log10(ideal) + power = _Numeric.floor(log) + fraction = log-power + factor = 1. + error = fraction + for f, lf in self._multiples: + e = _Numeric.fabs(fraction-lf) + if e < error: + error = e + factor = f + grid = factor * 10.**power + if power > 4 or power < -4: + format = '%+7.1e' + elif power >= 0: + digits = max(1, int(power)) + format = '%' + `digits`+'.0f' + else: + digits = -int(power) + format = '%'+`digits+2`+'.'+`digits`+'f' + ticks = [] + t = -grid*_Numeric.floor(-lower/grid) + while t <= upper: + ticks.append( (t, format % (t,)) ) + t = t + grid + return ticks + + def _scope_ticks (self, lower, upper): + '''Always 10 divisions, no labels''' + grid = (upper - lower) / 10.0 + ticks = [] + t = lower + while t <= upper: + ticks.append( (t, "")) + t = t + grid + return ticks + + _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))] + + +#------------------------------------------------------------------------------- +# Used to layout the printer page + +class PlotPrintout(wx.Printout): + """Controls how the plot is made in printing and previewing""" + # Do not change method names in this class, + # we have to override wx.Printout methods here! + def __init__(self, graph): + """graph is instance of plotCanvas to be printed or previewed""" + wx.Printout.__init__(self) + self.graph = graph + + def HasPage(self, page): + if page == 1: + return True + else: + return False + + def GetPageInfo(self): + return (1, 1, 1, 1) # disable page numbers + + def OnPrintPage(self, page): + dc = self.GetDC() # allows using floats for certain functions +## print "PPI Printer",self.GetPPIPrinter() +## print "PPI Screen", self.GetPPIScreen() +## print "DC GetSize", dc.GetSize() +## print "GetPageSizePixels", self.GetPageSizePixels() + # Note PPIScreen does not give the correct number + # Calulate everything for printer and then scale for preview + PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h) + #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h) + dcSize= dc.GetSize() # DC size + pageSize= self.GetPageSizePixels() # page size in terms of pixcels + clientDcSize= self.graph.GetClientSize() + + # find what the margins are (mm) + margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft() + margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight() + + # calculate offset and scale for dc + pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in) + pixRight= margRightSize*PPIPrinter[0]/25.4 + pixTop= margTopSize*PPIPrinter[1]/25.4 + pixBottom= margBottomSize*PPIPrinter[1]/25.4 + + plotAreaW= pageSize[0]-(pixLeft+pixRight) + plotAreaH= pageSize[1]-(pixTop+pixBottom) + + # ratio offset and scale to screen size if preview + if self.IsPreview(): + ratioW= float(dcSize[0])/pageSize[0] + ratioH= float(dcSize[1])/pageSize[1] + pixLeft *= ratioW + pixTop *= ratioH + plotAreaW *= ratioW + plotAreaH *= ratioH + + # rescale plot to page or preview plot area + self.graph._setSize(plotAreaW,plotAreaH) + + # Set offset and scale + dc.SetDeviceOrigin(pixLeft,pixTop) + + # Thicken up pens and increase marker size for printing + ratioW= float(plotAreaW)/clientDcSize[0] + ratioH= float(plotAreaH)/clientDcSize[1] + aveScale= (ratioW+ratioH)/2 + self.graph._setPrinterScale(aveScale) # tickens up pens for printing + + self.graph._printDraw(dc) + # rescale back to original + self.graph._setSize() + self.graph._setPrinterScale(1) + self.graph.Redraw() #to get point label scale and shift correct + + return True + + + + +#--------------------------------------------------------------------------- +# if running standalone... +# +# ...a sample implementation using the above +# + +def _draw1Objects(): + # 100 points sin function, plotted as green circles + data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200. + data1.shape = (100, 2) + data1[:,1] = _Numeric.sin(data1[:,0]) + markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1) + + # 50 points cos function, plotted as red line + data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100. + data1.shape = (50,2) + data1[:,1] = _Numeric.cos(data1[:,0]) + lines = PolyLine(data1, legend= 'Red Line', colour='red') + + # A few more points... + pi = _Numeric.pi + markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), + (3.*pi/4., -1)], legend='Cross Legend', colour='blue', + marker='cross') + + return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis") + +def _draw2Objects(): + # 100 points sin function, plotted as green dots + data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200. + data1.shape = (100, 2) + data1[:,1] = _Numeric.sin(data1[:,0]) + line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT) + + # 50 points cos function, plotted as red dot-dash + data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100. + data1.shape = (50,2) + data1[:,1] = _Numeric.cos(data1[:,0]) + line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH) + + # A few more points... + pi = _Numeric.pi + markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), + (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6, + fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH, + marker='square') + + return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles") + +def _draw3Objects(): + markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down', + 'cross', 'plus', 'circle'] + m=[] + for i in range(len(markerList)): + m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue', + marker=markerList[i])) + return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis") + +def _draw4Objects(): + # 25,000 point line + data1 = _Numeric.arange(5e5,1e6,10) + data1.shape = (25000, 2) + line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5) + + # A few more points... + markers2 = PolyMarker(data1, legend='Square', colour='blue', + marker='square') + return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "") + +def _draw5Objects(): + # Empty graph with axis defined but no points/lines + points=[] + line1 = PolyLine(points, legend='Wide Line', colour='green', width=5) + return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y") + +def _draw6Objects(): + # Bar graph + points1=[(1,0), (1,10)] + line1 = PolyLine(points1, colour='green', legend='Feb.', width=10) + points1g=[(2,0), (2,4)] + line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10) + points1b=[(3,0), (3,6)] + line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10) + + points2=[(4,0), (4,12)] + line2 = PolyLine(points2, colour='Yellow', legend='May', width=10) + points2g=[(5,0), (5,8)] + line2g = PolyLine(points2g, colour='orange', legend='June', width=10) + points2b=[(6,0), (6,4)] + line2b = PolyLine(points2b, colour='brown', legend='July', width=10) + + return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b], + "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students") + + +class TestFrame(wx.Frame): + def __init__(self, parent, id, title): + wx.Frame.__init__(self, parent, id, title, + wx.DefaultPosition, (600, 400)) + + # Now Create the menu bar and items + self.mainmenu = wx.MenuBar() + + menu = wx.Menu() + menu.Append(200, 'Page Setup...', 'Setup the printer page') + self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200) + + menu.Append(201, 'Print Preview...', 'Show the current plot on page') + self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201) + + menu.Append(202, 'Print...', 'Print the current plot') + self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202) + + menu.Append(203, 'Save Plot...', 'Save current plot') + self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203) + + menu.Append(205, 'E&xit', 'Enough of this already!') + self.Bind(wx.EVT_MENU, self.OnFileExit, id=205) + self.mainmenu.Append(menu, '&File') + + menu = wx.Menu() + menu.Append(206, 'Draw1', 'Draw plots1') + self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206) + menu.Append(207, 'Draw2', 'Draw plots2') + self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207) + menu.Append(208, 'Draw3', 'Draw plots3') + self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208) + menu.Append(209, 'Draw4', 'Draw plots4') + self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209) + menu.Append(210, 'Draw5', 'Draw plots5') + self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210) + menu.Append(260, 'Draw6', 'Draw plots6') + self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260) + + + menu.Append(211, '&Redraw', 'Redraw plots') + self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211) + menu.Append(212, '&Clear', 'Clear canvas') + self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212) + menu.Append(213, '&Scale', 'Scale canvas') + self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213) + menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214) + menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215) + menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220) + menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222) + + menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit') + self.Bind(wx.EVT_MENU,self.OnScrUp, id=225) + menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units') + self.Bind(wx.EVT_MENU,self.OnScrRt, id=230) + menu.Append(235, '&Plot Reset', 'Reset to original plot') + self.Bind(wx.EVT_MENU,self.OnReset, id=235) + + self.mainmenu.Append(menu, '&Plot') + + menu = wx.Menu() + menu.Append(300, '&About', 'About this thing...') + self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300) + self.mainmenu.Append(menu, '&Help') + + self.SetMenuBar(self.mainmenu) + + # A status bar to tell people what's happening + self.CreateStatusBar(1) + + self.client = PlotCanvas(self) + #define the function for drawing pointLabels + self.client.SetPointLabelFunc(self.DrawPointLabel) + # Create mouse event for showing cursor coords in status bar + self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) + # Show closest point when enabled + self.client.Bind(wx.EVT_MOTION, self.OnMotion) + + self.Show(True) + + def DrawPointLabel(self, dc, mDataDict): + """This is the fuction that defines how the pointLabels are plotted + dc - DC that will be passed + mDataDict - Dictionary of data that you want to use for the pointLabel + + As an example I have decided I want a box at the curve point + with some text information about the curve plotted below. + Any wxDC method can be used. + """ + # ---------- + dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) + + sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point + dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point + px,py = mDataDict["pointXY"] + cNum = mDataDict["curveNum"] + pntIn = mDataDict["pIndex"] + legend = mDataDict["legend"] + #make a string to display + s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn) + dc.DrawText(s, sx , sy+1) + # ----------- + + def OnMouseLeftDown(self,event): + s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event) + self.SetStatusText(s) + event.Skip() #allows plotCanvas OnMouseLeftDown to be called + + def OnMotion(self, event): + #show closest point (when enbled) + if self.client.GetEnablePointLabel() == True: + #make up dict with info for the pointLabel + #I've decided to mark the closest point on the closest curve + dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True) + if dlst != []: #returns [] if none + curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst + #make up dictionary to pass to my user function (see DrawPointLabel) + mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\ + "pointXY":pointXY, "scaledXY":scaledXY} + #pass dict to update the pointLabel + self.client.UpdatePointLabel(mDataDict) + event.Skip() #go to next handler + + def OnFilePageSetup(self, event): + self.client.PageSetup() + + def OnFilePrintPreview(self, event): + self.client.PrintPreview() + + def OnFilePrint(self, event): + self.client.Printout() + + def OnSaveFile(self, event): + self.client.SaveFile() + + def OnFileExit(self, event): + self.Close() + + def OnPlotDraw1(self, event): + self.resetDefaults() + self.client.Draw(_draw1Objects()) + + def OnPlotDraw2(self, event): + self.resetDefaults() + self.client.Draw(_draw2Objects()) + + def OnPlotDraw3(self, event): + self.resetDefaults() + self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL)) + self.client.SetFontSizeAxis(20) + self.client.SetFontSizeLegend(12) + self.client.SetXSpec('min') + self.client.SetYSpec('none') + self.client.Draw(_draw3Objects()) + + def OnPlotDraw4(self, event): + self.resetDefaults() + drawObj= _draw4Objects() + self.client.Draw(drawObj) +## # profile +## start = _time.clock() +## for x in range(10): +## self.client.Draw(drawObj) +## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start) +## # profile end + + def OnPlotDraw5(self, event): + # Empty plot with just axes + self.resetDefaults() + drawObj= _draw5Objects() + # make the axis X= (0,5), Y=(0,10) + # (default with None is X= (-1,1), Y= (-1,1)) + self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10)) + + def OnPlotDraw6(self, event): + #Bar Graph Example + self.resetDefaults() + #self.client.SetEnableLegend(True) #turn on Legend + #self.client.SetEnableGrid(True) #turn on Grid + self.client.SetXSpec('none') #turns off x-axis scale + self.client.SetYSpec('auto') + self.client.Draw(_draw6Objects(), xAxis= (0,7)) + + def OnPlotRedraw(self,event): + self.client.Redraw() + + def OnPlotClear(self,event): + self.client.Clear() + + def OnPlotScale(self, event): + if self.client.last_draw != None: + graphics, xAxis, yAxis= self.client.last_draw + self.client.Draw(graphics,(1,3.05),(0,1)) + + def OnEnableZoom(self, event): + self.client.SetEnableZoom(event.IsChecked()) + + def OnEnableGrid(self, event): + self.client.SetEnableGrid(event.IsChecked()) + + def OnEnableLegend(self, event): + self.client.SetEnableLegend(event.IsChecked()) + + def OnEnablePointLabel(self, event): + self.client.SetEnablePointLabel(event.IsChecked()) + + def OnScrUp(self, event): + self.client.ScrollUp(1) + + def OnScrRt(self,event): + self.client.ScrollRight(2) + + def OnReset(self,event): + self.client.Reset() + + def OnHelpAbout(self, event): + from wx.lib.dialogs import ScrolledMessageDialog + about = ScrolledMessageDialog(self, __doc__, "About...") + about.ShowModal() + + def resetDefaults(self): + """Just to reset the fonts back to the PlotCanvas defaults""" + self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL)) + self.client.SetFontSizeAxis(10) + self.client.SetFontSizeLegend(7) + self.client.SetXSpec('auto') + self.client.SetYSpec('auto') + + +def __test(): + + class MyApp(wx.App): + def OnInit(self): + wx.InitAllImageHandlers() + frame = TestFrame(None, -1, "PlotCanvas") + #frame.Show(True) + self.SetTopWindow(frame) + return True + + + app = MyApp(0) + app.MainLoop() + +if __name__ == '__main__': + __test() diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py new file mode 100755 index 000000000..36be408c4 --- /dev/null +++ b/gr-wxgui/src/python/powermate.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python +# +# Copyright 2005 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +""" +Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs + +This is Linux and wxPython specific. +""" +import select +import os +import fcntl +import struct +import exceptions +import threading +import sys +import wx +from gnuradio import gru + +# First a little bit of background: +# +# The Griffin PowerMate has +# * a single knob which rotates +# * a single button (pressing the knob) +# +# The Contour ShuttleXpress (aka SpaceShuttle) has +# * "Jog Wheel" -- the knob (rotary encoder) on the inside +# * "Shuttle Ring" -- the spring loaded rubber covered ring +# * 5 buttons +# +# The Contour ShuttlePro has +# * "Jog Wheel" -- the knob (rotary encoder) on the inside +# * "Shuttle Ring" -- the spring loaded rubber covered ring +# * 13 buttons +# +# The Contour ShuttlePro V2 has +# *"Jog Wheel" -- the knob (rotary encoder) on the inside +# * "Shuttle Ring" -- the spring loaded rubber covered ring +# * 15 buttons + +# We remap all the buttons on the devices so that they start at zero. + +# For the ShuttleXpress the buttons are 0 to 4 (left to right) + +# For the ShuttlePro, we number the buttons immediately above +# the ring 0 to 4 (left to right) so that they match our numbering +# on the ShuttleXpress. The top row is 5, 6, 7, 8. The first row below +# the ring is 9, 10, and the bottom row is 11, 12. + +# For the ShuttlePro V2, buttons 13 & 14 are to the +# left and right of the wheel respectively. + +# We generate 3 kinds of events: +# +# button press/release (button_number, press/release) +# knob rotation (relative_clicks) # typically -1, +1 +# shuttle position (absolute_position) # -7,-6,...,0,...,6,7 + +# ---------------------------------------------------------------- +# Our ID's for the devices: +# Not to be confused with anything related to magic hardware numbers. + +ID_POWERMATE = 'powermate' +ID_SHUTTLE_XPRESS = 'shuttle xpress' +ID_SHUTTLE_PRO = 'shuttle pro' +ID_SHUTTLE_PRO_V2 = 'shuttle pro v2' + +# ------------------------------------------------------------------------ +# format of messages that we read from /dev/input/event* +# See /usr/include/linux/input.h for more info +# +#struct input_event { +# struct timeval time; = {long seconds, long microseconds} +# unsigned short type; +# unsigned short code; +# unsigned int value; +#}; + +input_event_struct = "@llHHi" +input_event_size = struct.calcsize(input_event_struct) + +# ------------------------------------------------------------------------ +# input_event types +# ------------------------------------------------------------------------ + +IET_SYN = 0x00 # aka RESET +IET_KEY = 0x01 # key or button press/release +IET_REL = 0x02 # relative movement (knob rotation) +IET_ABS = 0x03 # absolute position (graphics pad, etc) +IET_MSC = 0x04 +IET_LED = 0x11 +IET_SND = 0x12 +IET_REP = 0x14 +IET_FF = 0x15 +IET_PWR = 0x16 +IET_FF_STATUS = 0x17 +IET_MAX = 0x1f + +# ------------------------------------------------------------------------ +# input_event codes (there are a zillion of them, we only define a few) +# ------------------------------------------------------------------------ + +# these are valid for IET_KEY + +IEC_BTN_0 = 0x100 +IEC_BTN_1 = 0x101 +IEC_BTN_2 = 0x102 +IEC_BTN_3 = 0x103 +IEC_BTN_4 = 0x104 +IEC_BTN_5 = 0x105 +IEC_BTN_6 = 0x106 +IEC_BTN_7 = 0x107 +IEC_BTN_8 = 0x108 +IEC_BTN_9 = 0x109 +IEC_BTN_10 = 0x10a +IEC_BTN_11 = 0x10b +IEC_BTN_12 = 0x10c +IEC_BTN_13 = 0x10d +IEC_BTN_14 = 0x10e +IEC_BTN_15 = 0x10f + +# these are valid for IET_REL (Relative axes) + +IEC_REL_X = 0x00 +IEC_REL_Y = 0x01 +IEC_REL_Z = 0x02 +IEC_REL_HWHEEL = 0x06 +IEC_REL_DIAL = 0x07 # rotating the knob +IEC_REL_WHEEL = 0x08 # moving the shuttle ring +IEC_REL_MISC = 0x09 +IEC_REL_MAX = 0x0f + +# ------------------------------------------------------------------------ + +class powermate(threading.Thread): + """ + Interface to Griffin PowerMate and Contour Shuttles + """ + def __init__(self, event_receiver=None, filename=None, **kwargs): + self.event_receiver = event_receiver + self.handle = -1 + if filename: + if not self._open_device(filename): + raise exceptions.RuntimeError, 'Unable to find powermate' + else: + ok = False + for d in range(0, 16): + if self._open_device("/dev/input/event%d" % d): + ok = True + break + if not ok: + raise exceptions.RuntimeError, 'Unable to find powermate' + + threading.Thread.__init__(self, **kwargs) + self.setDaemon (1) + self.keep_running = True + self.start () + + def __del__(self): + self.keep_running = False + if self.handle >= 0: + os.close(self.handle) + self.handle = -1 + + def _open_device(self, filename): + try: + self.handle = os.open(filename, os.O_RDWR) + if self.handle < 0: + return False + + # read event device name + name = fcntl.ioctl(self.handle, gru.hexint(0x80ff4506), chr(0) * 256) + name = name.replace(chr(0), '') + + # do we see anything we recognize? + if name == 'Griffin PowerMate' or name == 'Griffin SoundKnob': + self.id = ID_POWERMATE + self.mapper = _powermate_remapper() + elif name == 'CAVS SpaceShuttle A/V' or name == 'Contour Design ShuttleXpress': + self.id = ID_SHUTTLE_XPRESS + self.mapper = _contour_remapper() + elif name == 'Contour Design ShuttlePRO': + self.id = ID_SHUTTLE_PRO + self.mapper = _contour_remapper() + elif name == 'Contour Design ShuttlePRO v2': + self.id = ID_SHUTTLE_PRO_V2 + self.mapper = _contour_remapper() + else: + os.close(self.handle) + self.handle = -1 + return False + + # get exclusive control of the device, using ioctl EVIOCGRAB + # there may be an issue with this on non x86 platforms and if + # the _IOW,_IOC,... macros in are changed + fcntl.ioctl(self.handle,gru.hexint(0x40044590), 1) + return True + except exceptions.OSError: + return False + + + def set_event_receiver(self, obj): + self.event_receiver = obj + + + def set_led_state(self, static_brightness, pulse_speed=0, + pulse_table=0, pulse_on_sleep=0, pulse_on_wake=0): + """ + What do these magic values mean... + """ + if self.id != ID_POWERMATE: + return False + + static_brightness &= 0xff; + if pulse_speed < 0: + pulse_speed = 0 + if pulse_speed > 510: + pulse_speed = 510 + if pulse_table < 0: + pulse_table = 0 + if pulse_table > 2: + pulse_table = 2 + pulse_on_sleep = not not pulse_on_sleep # not not = convert to 0/1 + pulse_on_wake = not not pulse_on_wake + magic = (static_brightness + | (pulse_speed << 8) + | (pulse_table << 17) + | (pulse_on_sleep << 19) + | (pulse_on_wake << 20)) + data = struct.pack(input_event_struct, 0, 0, 0x04, 0x01, magic) + os.write(self.handle, data) + return True + + def run (self): + while (self.keep_running): + s = os.read (self.handle, input_event_size) + if not s: + self.keep_running = False + break + + raw_input_event = struct.unpack(input_event_struct,s) + sec, usec, type, code, val = self.mapper(raw_input_event) + + if self.event_receiver is None: + continue + + if type == IET_SYN: # ignore + pass + elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness) + pass + elif type == IET_REL and code == IEC_REL_DIAL: + #print "Dial: %d" % (val,) + wx.PostEvent(self.event_receiver, PMRotateEvent(val)) + elif type == IET_REL and code == IEC_REL_WHEEL: + #print "Shuttle: %d" % (val,) + wx.PostEvent(self.event_receiver, PMShuttleEvent(val)) + elif type == IET_KEY: + #print "Key: Btn%d %d" % (code - IEC_BTN_0, val) + wx.PostEvent(self.event_receiver, + PMButtonEvent(code - IEC_BTN_0, val)) + else: + print "powermate: unrecognized event: type = 0x%x code = 0x%x val = %d" % (type, code, val) + + +class _powermate_remapper(object): + def __init__(self): + pass + def __call__(self, event): + """ + Notice how nice and simple this is... + """ + return event + +class _contour_remapper(object): + def __init__(self): + self.prev = None + def __call__(self, event): + """ + ...and how screwed up this is + """ + sec, usec, type, code, val = event + if type == IET_REL and code == IEC_REL_WHEEL: + # === Shuttle ring === + # First off, this really ought to be IET_ABS, not IET_REL! + # They never generate a zero value so you can't + # tell when the shuttle ring is back in the center. + # We kludge around this by calling both -1 and 1 zero. + if val == -1 or val == 1: + return (sec, usec, type, code, 0) + return event + + if type == IET_REL and code == IEC_REL_DIAL: + # === Jog knob (rotary encoder) === + # Dim wits got it wrong again! This one should return a + # a relative value, e.g., -1, +1. Instead they return + # a total that runs modulo 256 (almost!). For some + # reason they count like this 253, 254, 255, 1, 2, 3 + + if self.prev is None: # first time call + self.prev = val + return (sec, usec, IET_SYN, 0, 0) # will be ignored above + + diff = val - self.prev + if diff == 0: # sometimes it just sends stuff... + return (sec, usec, IET_SYN, 0, 0) # will be ignored above + + if abs(diff) > 100: # crossed into the twilight zone + if self.prev > val: # we've wrapped going forward + self.prev = val + return (sec, usec, type, code, +1) + else: # we've wrapped going backward + self.prev = val + return (sec, usec, type, code, -1) + + self.prev = val + return (sec, usec, type, code, diff) + + if type == IET_KEY: + # remap keys so that all 3 gadgets have buttons 0 to 4 in common + return (sec, usec, type, + (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8, + IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4, + IEC_BTN_9, IEC_BTN_10, + IEC_BTN_11, IEC_BTN_12, + IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val) + + return event + +# ------------------------------------------------------------------------ +# new wxPython event classes +# ------------------------------------------------------------------------ + +grEVT_POWERMATE_BUTTON = wx.NewEventType() +grEVT_POWERMATE_ROTATE = wx.NewEventType() +grEVT_POWERMATE_SHUTTLE = wx.NewEventType() + +EVT_POWERMATE_BUTTON = wx.PyEventBinder(grEVT_POWERMATE_BUTTON, 0) +EVT_POWERMATE_ROTATE = wx.PyEventBinder(grEVT_POWERMATE_ROTATE, 0) +EVT_POWERMATE_SHUTTLE = wx.PyEventBinder(grEVT_POWERMATE_SHUTTLE, 0) + +class PMButtonEvent(wx.PyEvent): + def __init__(self, button, value): + wx.PyEvent.__init__(self) + self.SetEventType(grEVT_POWERMATE_BUTTON) + self.button = button + self.value = value + + def Clone (self): + self.__class__(self.GetId()) + + +class PMRotateEvent(wx.PyEvent): + def __init__(self, delta): + wx.PyEvent.__init__(self) + self.SetEventType (grEVT_POWERMATE_ROTATE) + self.delta = delta + + def Clone (self): + self.__class__(self.GetId()) + + +class PMShuttleEvent(wx.PyEvent): + def __init__(self, position): + wx.PyEvent.__init__(self) + self.SetEventType (grEVT_POWERMATE_SHUTTLE) + self.position = position + + def Clone (self): + self.__class__(self.GetId()) + +# ------------------------------------------------------------------------ +# Example usage +# ------------------------------------------------------------------------ + +if __name__ == '__main__': + class Frame(wx.Frame): + def __init__(self,parent=None,id=-1,title='Title', + pos=wx.DefaultPosition, size=(400,200)): + wx.Frame.__init__(self,parent,id,title,pos,size) + EVT_POWERMATE_BUTTON(self, self.on_button) + EVT_POWERMATE_ROTATE(self, self.on_rotate) + EVT_POWERMATE_SHUTTLE(self, self.on_shuttle) + self.brightness = 128 + self.pulse_speed = 0 + + try: + self.pm = powermate(self) + except: + sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") + sys.exit(1) + + self.pm.set_led_state(self.brightness, self.pulse_speed) + + + def on_button(self, evt): + print "Button %d %s" % (evt.button, + ("Released", "Pressed")[evt.value]) + + def on_rotate(self, evt): + print "Rotated %d" % (evt.delta,) + if 0: + new = max(0, min(255, self.brightness + evt.delta)) + if new != self.brightness: + self.brightness = new + self.pm.set_led_state(self.brightness, self.pulse_speed) + + def on_shuttle(self, evt): + print "Shuttle %d" % (evt.position,) + + class App(wx.App): + def OnInit(self): + title='PowerMate Demo' + self.frame = Frame(parent=None,id=-1,title=title) + self.frame.Show() + self.SetTopWindow(self.frame) + return True + + app = App() + app.MainLoop () diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py new file mode 100755 index 000000000..f231b3b79 --- /dev/null +++ b/gr-wxgui/src/python/scopesink.py @@ -0,0 +1,650 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2006 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gru, eng_notation +from gnuradio.wxgui import stdgui +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import threading +import struct + +default_scopesink_size = (640, 240) +default_v_scale = 1000 +default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) + +class scope_sink_f(gr.hier_block): + def __init__(self, fg, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None): + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + self.guts = gr.oscope_sink_f(sample_rate, msgq) + gr.hier_block.__init__(self, fg, self.guts, self.guts) + self.win = scope_window(win_info (msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +class scope_sink_c(gr.hier_block): + def __init__(self, fg, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None): + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + c2f = gr.complex_to_float() + self.guts = gr.oscope_sink_f(sample_rate, msgq) + fg.connect((c2f, 0), (self.guts, 0)) + fg.connect((c2f, 1), (self.guts, 1)) + gr.hier_block.__init__(self, fg, c2f, self.guts) + self.win = scope_window(win_info(msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +# ======================================================================== +# This is the deprecated interface, retained for compatibility... +# +# returns (block, win). +# block requires a N input stream of float +# win is a subclass of wxWindow + +def make_scope_sink_f (fg, parent, label, input_rate): + block = scope_sink_f(fg, parent, title=label, sample_rate=input_rate) + return (block, block.win) + +# ======================================================================== + + +time_base_list = [ # time / division + 1.0e-7, # 100ns / div + 2.5e-7, + 5.0e-7, + 1.0e-6, # 1us / div + 2.5e-6, + 5.0e-6, + 1.0e-5, # 10us / div + 2.5e-5, + 5.0e-5, + 1.0e-4, # 100us / div + 2.5e-4, + 5.0e-4, + 1.0e-3, # 1ms / div + 2.5e-3, + 5.0e-3, + 1.0e-2, # 10ms / div + 2.5e-2, + 5.0e-2 + ] + +v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER + 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div + 5.0e-3, + 1.0e-2, + 2.0e-2, + 5.0e-2, + 1.0e-1, + 2.0e-1, + 5.0e-1, + 1.0e+0, + 2.0e+0, + 5.0e+0, + 1.0e+1, + 2.0e+1, + 5.0e+1, + 1.0e+2, + 2.0e+2, + 5.0e+2, + 1.0e+3, + 2.0e+3, + 5.0e+3, + 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 + ] + + +wxDATA_EVENT = wx.NewEventType() + +def EVT_DATA_EVENT(win, func): + win.Connect(-1, -1, wxDATA_EVENT, func) + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (wxDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class win_info (object): + __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', + 'scopesink', 'title', + 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', + 'autorange', 'running'] + + def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, + scopesink, title = "Oscilloscope"): + self.msgq = msgq + self.sample_rate = sample_rate + self.frame_decim = frame_decim + self.scopesink = scopesink + self.title = title; + + self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) + self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) + + self.marker = 'line' + self.xy = False + if v_scale == None: # 0 and None are both False, but 0 != None + self.autorange = True + else: + self.autorange = False # 0 is a valid v_scale + self.running = True + + def get_time_per_div (self): + return self.time_scale_cursor.current () + + def get_volts_per_div (self): + return self.v_scale_cursor.current () + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + + def get_sample_rate (self): + return self.sample_rate + + def get_decimation_rate (self): + return 1.0 + + def set_marker (self, s): + self.marker = s + + def get_marker (self): + return self.marker + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, event_receiver, frame_decim, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.event_receiver = event_receiver + self.frame_decim = frame_decim + self.iscan = 0 + self.keep_running = True + self.start () + + def run (self): + # print "input_watcher: pid = ", os.getpid () + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + if self.iscan == 0: # only display at frame_decim + self.iscan = self.frame_decim + + 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 + + bytes_per_chan = nsamples * gr.sizeof_float + + records = [] + for ch in range (nchan): + + start = ch * bytes_per_chan + chan_data = s[start:start+bytes_per_chan] + rec = Numeric.fromstring (chan_data, Numeric.Float32) + records.append (rec) + + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) + + de = DataEvent (records) + wx.PostEvent (self.event_receiver, de) + records = [] + del de + + # end if iscan == 0 + self.iscan -= 1 + + +class scope_window (wx.Panel): + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): + wx.Panel.__init__ (self, parent, -1) + self.info = info + + vbox = wx.BoxSizer (wx.VERTICAL) + + self.graph = graph_window (info, self, -1) + + vbox.Add (self.graph, 1, wx.EXPAND) + vbox.Add (self.make_control_box(), 0, wx.EXPAND) + vbox.Add (self.make_control2_box(), 0, wx.EXPAND) + + self.sizer = vbox + self.SetSizer (self.sizer) + self.SetAutoLayout (True) + self.sizer.Fit (self) + self.set_autorange(self.info.autorange) + + + # second row of control buttons etc. appears BELOW control_box + def make_control2_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT) + self.inc_v_button.SetToolTipString ("Increase vertical range") + wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above + + self.dec_v_button = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT) + self.dec_v_button.SetToolTipString ("Decrease vertical range") + wx.EVT_BUTTON (self, 1100, self.decr_v_scale) + + self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div + self.update_v_scale_label () + + self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange") + self.autorange_checkbox.SetToolTipString ("Select autorange on/off") + wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) + + ctrlbox.Add ((5,0) ,0) # left margin space + ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER) + ctrlbox.Add ((20,0) ,0) # spacer + ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER) + + return ctrlbox + + def make_control_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT) + tb_left.SetToolTipString ("Increase time base") + wx.EVT_BUTTON (self, 1001, self.incr_timebase) + + + tb_right = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT) + tb_right.SetToolTipString ("Decrease time base") + wx.EVT_BUTTON (self, 1000, self.decr_timebase) + + self.time_base_label = wx.StaticText (self, 1002, "") + self.update_timebase_label () + + ctrlbox.Add ((5,0) ,0) + # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) + ctrlbox.Add (tb_left, 0, wx.EXPAND) + ctrlbox.Add (tb_right, 0, wx.EXPAND) + ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER) + + ctrlbox.Add ((10,0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER) + self.trig_chan_choice = wx.Choice (self, 1004, + choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) + self.trig_chan_choice.SetToolTipString ("Select channel for trigger") + wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event) + ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) + + self.trig_mode_choice = wx.Choice (self, 1005, + choices = ['Pos', 'Neg', 'Auto']) + self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") + wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) + ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) + + trig_level50 = wx.Button (self, 1006, "50%") + trig_level50.SetToolTipString ("Set trigger level to 50%") + wx.EVT_BUTTON (self, 1006, self.set_trig_level50) + ctrlbox.Add (trig_level50, 0, wx.EXPAND) + + run_stop = wx.Button (self, 1007, "Run/Stop") + run_stop.SetToolTipString ("Toggle Run/Stop mode") + wx.EVT_BUTTON (self, 1007, self.run_stop) + ctrlbox.Add (run_stop, 0, wx.EXPAND) + + ctrlbox.Add ((10, 0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) + self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices) + self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots") + wx.EVT_CHOICE (self, 1002, self.marker_choice_event) + ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER) + + self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y']) + self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display") + wx.EVT_CHOICE (self, 1003, self.xy_choice_event) + ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) + + return ctrlbox + + _marker_choices = ['line', 'plus', 'dot'] + + def update_timebase_label (self): + time_per_div = self.info.get_time_per_div () + s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' + self.time_base_label.SetLabel (s) + + def decr_timebase (self, evt): + self.info.time_scale_cursor.prev () + self.update_timebase_label () + + def incr_timebase (self, evt): + self.info.time_scale_cursor.next () + self.update_timebase_label () + + def update_v_scale_label (self): + volts_per_div = self.info.get_volts_per_div () + s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div + self.v_scale_label.SetLabel (s) + + def decr_v_scale (self, evt): + self.info.v_scale_cursor.prev () + self.update_v_scale_label () + + def incr_v_scale (self, evt): + self.info.v_scale_cursor.next () + self.update_v_scale_label () + + def marker_choice_event (self, evt): + s = evt.GetString () + self.set_marker (s) + + def set_autorange(self, on): + if on: + self.v_scale_label.SetLabel(" (auto)") + self.info.autorange = True + self.autorange_checkbox.SetValue(True) + self.inc_v_button.Enable(False) + self.dec_v_button.Enable(False) + else: + if self.graph.y_range: + (l,u) = self.graph.y_range # found by autorange + self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) + self.update_v_scale_label() + self.info.autorange = False + self.autorange_checkbox.SetValue(False) + self.inc_v_button.Enable(True) + self.dec_v_button.Enable(True) + + def autorange_checkbox_event(self, evt): + if evt.Checked(): + self.set_autorange(True) + else: + self.set_autorange(False) + + def set_marker (self, s): + self.info.set_marker (s) # set info for drawing routines + i = self.marker_choice.FindString (s) + assert i >= 0, "Hmmm, set_marker problem" + self.marker_choice.SetSelection (i) + + def set_format_line (self): + self.set_marker ('line') + + def set_format_dot (self): + self.set_marker ('dot') + + def set_format_plus (self): + self.set_marker ('plus') + + def xy_choice_event (self, evt): + s = evt.GetString () + self.info.xy = s == 'X:Y' + + def trig_chan_choice_event (self, evt): + s = evt.GetString () + ch = int (s[-1]) - 1 + self.info.scopesink.set_trigger_channel (ch) + + def trig_mode_choice_event (self, evt): + sink = self.info.scopesink + s = evt.GetString () + if s == 'Pos': + sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) + elif s == 'Neg': + sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) + elif s == 'Auto': + sink.set_trigger_mode (gr.gr_TRIG_AUTO) + else: + assert 0, "Bad trig_mode_choice string" + + def set_trig_level50 (self, evt): + self.info.scopesink.set_trigger_level_auto () + + def run_stop (self, evt): + self.info.running = not self.info.running + + +class graph_window (plot.PlotCanvas): + + channel_colors = ['BLUE', 'RED', + 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = (640, 240), + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + + self.SetXUseScopeTicks (True) + self.SetEnableGrid (True) + self.SetEnableZoom (True) + self.SetEnableLegend(True) + # self.SetBackgroundColour ('black') + + self.info = info; + self.y_range = None + self.x_range = None + self.avg_y_min = None + self.avg_y_max = None + self.avg_x_min = None + self.avg_x_max = None + + EVT_DATA_EVENT (self, self.format_data) + + self.input_watcher = input_watcher (info.msgq, self, info.frame_decim) + + def channel_color (self, ch): + return self.channel_colors[ch % len(self.channel_colors)] + + def format_data (self, evt): + if not self.info.running: + return + + if self.info.xy: + self.format_xy_data (evt) + return + + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + objects = [] + + Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) + x_vals = Ts * Numeric.arrayrange (-npoints/2, npoints/2) + + # preliminary clipping based on time axis here, instead of in graphics code + time_per_window = self.info.get_time_per_div () * 10 + n = int (time_per_window / Ts + 0.5) + n = n & ~0x1 # make even + n = max (2, min (n, npoints)) + + self.SetXUseScopeTicks (True) # use 10 divisions, no labels + + for ch in range(nchannels): + r = records[ch] + + # plot middle n points of record + + lb = npoints/2 - n/2 + ub = npoints/2 + n/2 + # points = zip (x_vals[lb:ub], r[lb:ub]) + points = Numeric.zeros ((ub-lb, 2), Numeric.Float64) + points[:,0] = x_vals[lb:ub] + points[:,1] = r[lb:ub] + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = '', yLabel = '') + + time_per_div = info.get_time_per_div () + x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! + volts_per_div = info.get_volts_per_div () + if not self.info.autorange: + self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) + self.Draw (graphics, xAxis=x_range, yAxis=self.y_range) + self.update_y_range () # autorange to self.y_range + + + def format_xy_data (self, evt): + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + if nchannels < 2: + return + + objects = [] + # points = zip (records[0], records[1]) + points = Numeric.zeros ((len(records[0]), 2), Numeric.Float32) + points[:,0] = records[0] + points[:,1] = records[1] + + self.SetXUseScopeTicks (False) + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (0))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (0))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = 'I', yLabel = 'Q') + + self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) + self.update_y_range () + self.update_x_range () + + + def update_y_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_y_min: # prevent vertical scale from jumping abruptly --? + self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) + self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) + else: # initial guess + self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 + self.avg_y_max = p2[1] # 500.0 + + self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) + # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ + # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) + + + def update_x_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_x_min: + self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) + self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) + else: + self.avg_x_min = p1[0] + self.avg_x_max = p2[0] + + self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max) + + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +class test_app_flow_graph (stdgui.gui_flow_graph): + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + if len(argv) > 1: + frame_decim = int(argv[1]) + else: + frame_decim = 1 + + if len(argv) > 2: + v_scale = float(argv[2]) # start up at this v_scale value + else: + v_scale = None # start up in autorange mode, default + + if len(argv) > 3: + t_scale = float(argv[3]) # start up at this t_scale value + else: + t_scale = None # old behavior + + print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + + input_rate = 1e6 + + # Generate a complex sinusoid + src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) + + # We add this throttle block so that this demo doesn't suck down + # all the CPU available. You normally wouldn't use it... + throttle = gr.throttle(gr.sizeof_gr_complex, input_rate) + + scope = scope_sink_c (self, panel,"Secret Data",sample_rate=input_rate, + frame_decim=frame_decim, + v_scale=v_scale, t_scale=t_scale) + vbox.Add (scope.win, 1, wx.EXPAND) + + # wire the blocks together + self.connect (src0, throttle, scope) + +def main (): + app = stdgui.stdapp (test_app_flow_graph, "O'Scope Test App") + app.MainLoop () + +if __name__ == '__main__': + main () + +# ---------------------------------------------------------------- diff --git a/gr-wxgui/src/python/slider.py b/gr-wxgui/src/python/slider.py new file mode 100755 index 000000000..e8cdcfcac --- /dev/null +++ b/gr-wxgui/src/python/slider.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import wx + +def slider(parent, min, max, callback): + """ + Return a wx.Slider object. + + @param min: minimum slider value + @type min: float + @param max: maximum slider value + @type max: float + @param callback: function of one arg invoked when slider moves. + @rtype: wx.Slider + """ + new_id = wx.NewId() + s = wx.Slider(parent, new_id, (max+min)/2, min, max, wx.DefaultPosition, + wx.Size(250,-1), wx.SL_HORIZONTAL | wx.SL_LABELS) + wx.EVT_COMMAND_SCROLL(parent, new_id, + lambda evt : callback(evt.GetInt())) + return s + + +# ---------------------------------------------------------------- +# Demo app +# ---------------------------------------------------------------- +if __name__ == '__main__': + + from gnuradio.wxgui import stdgui + + class demo_graph(stdgui.gui_flow_graph): + + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + vbox.Add(slider(panel, 23, 47, self.my_callback1), 1, wx.ALIGN_CENTER) + vbox.Add(slider(panel, -100, 100, self.my_callback2), 1, wx.ALIGN_CENTER) + + def my_callback1(self, val): + print "cb1 = ", val + + def my_callback2(self, val): + print "cb2 = ", val + + def main (): + app = stdgui.stdapp (demo_graph, "Slider Demo") + app.MainLoop () + + main () diff --git a/gr-wxgui/src/python/stdgui.py b/gr-wxgui/src/python/stdgui.py new file mode 100644 index 000000000..76cc1773b --- /dev/null +++ b/gr-wxgui/src/python/stdgui.py @@ -0,0 +1,90 @@ +# +# Copyright 2004 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +'''A simple wx gui for GNU Radio applications''' + +import wx +import sys +from gnuradio import gr + + +class stdapp (wx.App): + def __init__ (self, flow_graph_maker, title="GNU Radio", nstatus=2): + self.flow_graph_maker = flow_graph_maker + self.title = title + self._nstatus = nstatus + # All our initialization must come before calling wx.App.__init__. + # OnInit is called from somewhere in the guts of __init__. + wx.App.__init__ (self, redirect=False) + + def OnInit (self): + frame = stdframe (self.flow_graph_maker, self.title, self._nstatus) + frame.Show (True) + self.SetTopWindow (frame) + return True + + +class stdframe (wx.Frame): + def __init__ (self, flow_graph_maker, title="GNU Radio", nstatus=2): + # print "stdframe.__init__" + wx.Frame.__init__(self, None, -1, title) + + self.CreateStatusBar (nstatus) + mainmenu = wx.MenuBar () + + menu = wx.Menu () + item = menu.Append (200, 'E&xit', 'Exit') + self.Bind (wx.EVT_MENU, self.OnCloseWindow, item) + mainmenu.Append (menu, "&File") + self.SetMenuBar (mainmenu) + + self.Bind (wx.EVT_CLOSE, self.OnCloseWindow) + self.panel = stdpanel (self, self, flow_graph_maker) + vbox = wx.BoxSizer(wx.VERTICAL) + vbox.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(vbox) + self.SetAutoLayout(True) + vbox.Fit(self) + + def OnCloseWindow (self, event): + self.flow_graph().stop() + self.Destroy () + + def flow_graph (self): + return self.panel.fg + +class stdpanel (wx.Panel): + def __init__ (self, parent, frame, flow_graph_maker): + # print "stdpanel.__init__" + wx.Panel.__init__ (self, parent, -1) + self.frame = frame + + vbox = wx.BoxSizer (wx.VERTICAL) + self.fg = flow_graph_maker (frame, self, vbox, sys.argv) + self.SetSizer (vbox) + self.SetAutoLayout (True) + vbox.Fit (self) + + self.fg.start () + +class gui_flow_graph (gr.flow_graph): + def __init__ (self, *ignore): + gr.flow_graph.__init__ (self) diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py new file mode 100755 index 000000000..f5a6d243f --- /dev/null +++ b/gr-wxgui/src/python/waterfallsink.py @@ -0,0 +1,469 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import os +import threading +import math + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class waterfall_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title=''): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.input_is_real = input_is_real + (self.r_fd, self.w_fd) = os.pipe() + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + else: + self.avg.set_taps(1.0) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + +class waterfall_sink_f(gr.hier_block, waterfall_sink_base): + def __init__(self, fg, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + mywindow = window.blackmanharris(self.fft_size) + fft = gr.fft_vfc(self.fft_size, True, mywindow) + c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + sink = gr.file_descriptor_sink(gr.sizeof_float * self.fft_size, self.w_fd) + + fg.connect (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) + gr.hier_block.__init__(self, fg, s2p, sink) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +class waterfall_sink_c(gr.hier_block, waterfall_sink_base): + def __init__(self, fg, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + fft = gr.fft_vcc(self.fft_size, True, mywindow) + c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + sink = gr.file_descriptor_sink(gr.sizeof_float * self.fft_size, self.w_fd) + + fg.connect(s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) + gr.hier_block.__init__(self, fg, s2p, sink) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, file_descriptor, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.file_descriptor = file_descriptor + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + s = gru.os_read_exactly (self.file_descriptor, + gr.sizeof_float * self.fft_size) + if not s: + self.keep_running = False + break + + complex_data = Numeric.fromstring (s, Numeric.Float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + + +class waterfall_window (wx.Panel): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + wx.Panel.__init__(self, parent, id, pos, size, style, name) + + self.fftsink = fftsink + self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) + + self.scale_factor = 5.0 # FIXME should autoscale, or set this + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Clear() + + self.pens = self.make_pens() + + wx.EVT_PAINT( self, self.OnPaint ) + wx.EVT_CLOSE (self, self.on_close_window) + EVT_DATA_EVENT (self, self.set_data) + + self.build_popup_menu() + + wx.EVT_CLOSE (self, self.on_close_window) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(fftsink.r_fd, fftsink.fft_size, self) + + + def on_close_window (self, event): + print "waterfall_window: on_close_window" + self.keep_running = False + + def const_list(self,const,len): + return [const] * len + + def make_colormap(self): + r = [] + r.extend(self.const_list(0,96)) + r.extend(range(0,255,4)) + r.extend(self.const_list(255,64)) + r.extend(range(255,128,-4)) + + g = [] + g.extend(self.const_list(0,32)) + g.extend(range(0,255,4)) + g.extend(self.const_list(255,64)) + g.extend(range(255,0,-4)) + g.extend(self.const_list(0,32)) + + b = range(128,255,4) + b.extend(self.const_list(255,64)) + b.extend(range(255,0,-4)) + b.extend(self.const_list(0,96)) + return (r,g,b) + + def make_pens(self): + (r,g,b) = self.make_colormap() + pens = [] + for i in range(0,256): + colour = wx.Colour(r[i], g[i], b[i]) + pens.append( wx.Pen(colour, 2, wx.SOLID)) + return pens + + def OnPaint(self, event): + dc = wx.PaintDC(self) + self.DoDrawing(dc) + + def DoDrawing(self, dc=None): + if dc is None: + dc = wx.ClientDC(self) + dc.DrawBitmap(self.bm, 0, 0, False ) + + + def const_list(self,const,len): + a = [const] + for i in range(1,len): + a.append(const) + return a + + def make_colormap(self): + r = [] + r.extend(self.const_list(0,96)) + r.extend(range(0,255,4)) + r.extend(self.const_list(255,64)) + r.extend(range(255,128,-4)) + + g = [] + g.extend(self.const_list(0,32)) + g.extend(range(0,255,4)) + g.extend(self.const_list(255,64)) + g.extend(range(255,0,-4)) + g.extend(self.const_list(0,32)) + + b = range(128,255,4) + b.extend(self.const_list(255,64)) + b.extend(range(255,0,-4)) + b.extend(self.const_list(0,96)) + return (r,g,b) + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) + + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + sf = 1e-9 + units = "GHz" + elif x >= 1e6: + sf = 1e-6 + units = "MHz" + else: + sf = 1e-3 + units = "kHz" + + + if self.fftsink.input_is_real: # only plot 1/2 the points + d_max = L/2 + p_width = 2 + else: + d_max = L/2 + p_width = 1 + + scale_factor = self.scale_factor + if self.fftsink.input_is_real: # real fft + for x_pos in range(0, d_max): + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + else: # complex fft + for x_pos in range(0, d_max): # positive freqs + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 1) + for x_pos in range(0 , d_max): # negative freqs + value = int(dB[x_pos+d_max] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + + self.DoDrawing (None) + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + # menu.Append(self.id_incr_ref_level, "Incr Ref Level") + # menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + # menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average + #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Deprecated interfaces +# ---------------------------------------------------------------- + +# returns (block, win). +# block requires a single input stream of float +# win is a subclass of wxWindow + +def make_waterfall_sink_f(fg, parent, title, fft_size, input_rate): + + block = waterfall_sink_f(fg, parent, title=title, fft_size=fft_size, + sample_rate=input_rate) + return (block, block.win) + +# returns (block, win). +# block requires a single input stream of gr_complex +# win is a subclass of wxWindow + +def make_waterfall_sink_c(fg, parent, title, fft_size, input_rate): + block = waterfall_sink_c(fg, parent, title=title, fft_size=fft_size, + sample_rate=input_rate) + return (block, block.win) + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_flow_graph (stdgui.gui_flow_graph): + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + fft_size = 512 + + # build our flow graph + input_rate = 20.000e3 + + # Generate a complex sinusoid + src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = waterfall_sink_c (self, panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + vbox.Add (sink1.win, 1, wx.EXPAND) + self.connect (src1, thr1, sink1) + + # generate a real sinusoid + src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = waterfall_sink_f (self, panel, title="Real Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + vbox.Add (sink2.win, 1, wx.EXPAND) + self.connect (src2, thr2, sink2) + +def main (): + app = stdgui.stdapp (test_app_flow_graph, + "Waterfall Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () -- cgit From 21abdfb3f1ba6520a7911a0787adab664d9a790f Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 31 Aug 2006 06:30:59 +0000 Subject: Applied patch in ticket:59 by Don Ward. Updates waterfallsink.py to use messages instead of pipes. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3454 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/waterfallsink.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py index f5a6d243f..5d75122bf 100755 --- a/gr-wxgui/src/python/waterfallsink.py +++ b/gr-wxgui/src/python/waterfallsink.py @@ -50,7 +50,7 @@ class waterfall_sink_base(object): self.avg_alpha = avg_alpha self.title = title self.input_is_real = input_is_real - (self.r_fd, self.w_fd) = os.pipe() + self.msgq = gr.msg_queue(2) # queue up to 2 messages def set_average(self, average): self.average = average @@ -91,7 +91,7 @@ class waterfall_sink_f(gr.hier_block, waterfall_sink_base): c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - sink = gr.file_descriptor_sink(gr.sizeof_float * self.fft_size, self.w_fd) + sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) fg.connect (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) gr.hier_block.__init__(self, fg, s2p, sink) @@ -120,7 +120,7 @@ class waterfall_sink_c(gr.hier_block, waterfall_sink_base): c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - sink = gr.file_descriptor_sink(gr.sizeof_float * self.fft_size, self.w_fd) + sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) fg.connect(s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) gr.hier_block.__init__(self, fg, s2p, sink) @@ -146,10 +146,10 @@ class DataEvent(wx.PyEvent): class input_watcher (threading.Thread): - def __init__ (self, file_descriptor, fft_size, event_receiver, **kwds): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): threading.Thread.__init__ (self, **kwds) self.setDaemon (1) - self.file_descriptor = file_descriptor + self.msgq = msgq self.fft_size = fft_size self.event_receiver = event_receiver self.keep_running = True @@ -157,11 +157,17 @@ class input_watcher (threading.Thread): def run (self): while (self.keep_running): - s = gru.os_read_exactly (self.file_descriptor, - gr.sizeof_float * self.fft_size) - if not s: - self.keep_running = False - break + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] complex_data = Numeric.fromstring (s, Numeric.Float32) de = DataEvent (complex_data) @@ -195,7 +201,7 @@ class waterfall_window (wx.Panel): wx.EVT_CLOSE (self, self.on_close_window) self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - self.input_watcher = input_watcher(fftsink.r_fd, fftsink.fft_size, self) + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) def on_close_window (self, event): -- cgit From 86f5c92427b3f4bb30536d76cf63c3fca388fb2f Mon Sep 17 00:00:00 2001 From: eb Date: Wed, 13 Sep 2006 21:30:04 +0000 Subject: Updated FSF address in all files. Fixes ticket:51 git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3534 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 4 ++-- gr-wxgui/src/python/fftsink.py | 4 ++-- gr-wxgui/src/python/form.py | 4 ++-- gr-wxgui/src/python/powermate.py | 4 ++-- gr-wxgui/src/python/scopesink.py | 4 ++-- gr-wxgui/src/python/stdgui.py | 4 ++-- gr-wxgui/src/python/waterfallsink.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 2d25de05b..0e72f3281 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -15,8 +15,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # include $(top_srcdir)/Makefile.common diff --git a/gr-wxgui/src/python/fftsink.py b/gr-wxgui/src/python/fftsink.py index 8796a1b92..f85ec9d1a 100755 --- a/gr-wxgui/src/python/fftsink.py +++ b/gr-wxgui/src/python/fftsink.py @@ -16,8 +16,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # from gnuradio import gr, gru, window diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index bb41817ca..ea9a8dc9a 100755 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -16,8 +16,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # import wx diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py index 36be408c4..f79e22b12 100755 --- a/gr-wxgui/src/python/powermate.py +++ b/gr-wxgui/src/python/powermate.py @@ -16,8 +16,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # """ diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py index f231b3b79..e1c9382ab 100755 --- a/gr-wxgui/src/python/scopesink.py +++ b/gr-wxgui/src/python/scopesink.py @@ -16,8 +16,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # from gnuradio import gr, gru, eng_notation diff --git a/gr-wxgui/src/python/stdgui.py b/gr-wxgui/src/python/stdgui.py index 76cc1773b..8636ea2bf 100644 --- a/gr-wxgui/src/python/stdgui.py +++ b/gr-wxgui/src/python/stdgui.py @@ -15,8 +15,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # '''A simple wx gui for GNU Radio applications''' diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py index 5d75122bf..dee8d544a 100755 --- a/gr-wxgui/src/python/waterfallsink.py +++ b/gr-wxgui/src/python/waterfallsink.py @@ -16,8 +16,8 @@ # # 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. # from gnuradio import gr, gru, window -- cgit From 8030ff9a8451d4320ba46aad916487a95ce8e7ae Mon Sep 17 00:00:00 2001 From: jcorgan Date: Tue, 19 Sep 2006 00:25:13 +0000 Subject: Fixes ticket:68, making 'Auto' the default trigger option. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3572 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/scopesink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py index e1c9382ab..14df9b265 100755 --- a/gr-wxgui/src/python/scopesink.py +++ b/gr-wxgui/src/python/scopesink.py @@ -310,7 +310,7 @@ class scope_window (wx.Panel): ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) self.trig_mode_choice = wx.Choice (self, 1005, - choices = ['Pos', 'Neg', 'Auto']) + choices = ['Auto', 'Pos', 'Neg']) self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) -- cgit From 566df7b80d836b42b75b63d9a0704a04b387115a Mon Sep 17 00:00:00 2001 From: eb Date: Wed, 20 Dec 2006 19:52:04 +0000 Subject: trial fix for ticket:102, import error in powermate.py under MinGW/Cygwin git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@4163 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/powermate.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py index f79e22b12..041a2cd6b 100755 --- a/gr-wxgui/src/python/powermate.py +++ b/gr-wxgui/src/python/powermate.py @@ -25,16 +25,24 @@ Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs This is Linux and wxPython specific. """ -import select + import os -import fcntl +import sys import struct import exceptions import threading -import sys import wx from gnuradio import gru +imported_ok = True + +try: + import select + import fcntl +except ImportError: + imported_ok = False + + # First a little bit of background: # # The Griffin PowerMate has @@ -157,6 +165,9 @@ class powermate(threading.Thread): def __init__(self, event_receiver=None, filename=None, **kwargs): self.event_receiver = event_receiver self.handle = -1 + if not imported_ok: + raise exceptions.RuntimeError, 'powermate not supported on this platform' + if filename: if not self._open_device(filename): raise exceptions.RuntimeError, 'Unable to find powermate' -- cgit From b310478456293a9d94a923f2ac83050e566f7117 Mon Sep 17 00:00:00 2001 From: anastas Date: Sun, 18 Feb 2007 20:44:36 +0000 Subject: Adding the display sink by Martin Dvh git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@4499 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 3 +- gr-wxgui/src/python/numbersink.py | 614 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 gr-wxgui/src/python/numbersink.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 0e72f3281..ab5030740 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -37,4 +37,5 @@ ourpython_PYTHON = \ scopesink.py \ waterfallsink.py \ slider.py \ - stdgui.py + stdgui.py \ + numbersink.py diff --git a/gr-wxgui/src/python/numbersink.py b/gr-wxgui/src/python/numbersink.py new file mode 100644 index 000000000..b6319a3e8 --- /dev/null +++ b/gr-wxgui/src/python/numbersink.py @@ -0,0 +1,614 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2006 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 2, 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui +import wx +#from wx import StaticText +import gnuradio.wxgui.plot as plot +import Numeric +import threading +import math + +default_numbersink_size = (640,240) +default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15) + +class number_sink_base(object): + def __init__(self, input_is_real=False, unit='',base_value=0, minval=-100.0,maxval=100.0,factor=1.0,decimal_places=10, ref_level=50, + sample_rate=1, + number_rate=default_number_rate, + average=False, avg_alpha=None, label='', peak_hold=False): + + # initialize common attributes + self.unit=unit + self.base_value = base_value + self.minval=minval + self.maxval=maxval + self.factor=factor + self.y_divs = 8 + self.decimal_places=decimal_places + self.ref_level = ref_level + self.sample_rate = sample_rate + number_size=1 + self.number_size = number_size + self.number_rate = number_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / number_rate + else: + self.avg_alpha = avg_alpha + self.label = label + self.peak_hold = peak_hold + self.show_gauge = True + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages + + def set_decimal_places(self, decimal_places): + self.decimal_places = decimal_places + + def set_ref_level(self, ref_level): + self.ref_level = ref_level + + def print_current_value(self, comment): + print comment,self.win.current_value + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + self.set_peak_hold(False) + else: + self.avg.set_taps(1.0) + + def set_peak_hold(self, enable): + self.peak_hold = enable + if enable: + self.set_average(False) + self.win.set_peak_hold(enable) + + def set_show_gauge(self, enable): + self.show_gauge = enable + self.win.set_show_gauge(enable) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_base_value(self, base_value): + self.base_value = base_value + + +class number_sink_f(gr.hier_block, number_sink_base): + def __init__(self, fg, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, + decimal_places=10, ref_level=50, sample_rate=1, #number_size=512, + number_rate=default_number_rate, average=False, avg_alpha=None, + label='', size=default_numbersink_size, peak_hold=False): + + number_sink_base.__init__(self, unit=unit, input_is_real=True, base_value=base_value, + minval=minval,maxval=maxval,factor=factor, + decimal_places=decimal_places, ref_level=ref_level, + sample_rate=sample_rate, #number_size=number_size, + number_rate=number_rate, + average=average, avg_alpha=avg_alpha, label=label, + peak_hold=peak_hold) + + number_size=1 + #s2p = gr.stream_to_vector(gr.sizeof_float, number_size) + one_in_n = gr.keep_one_in_n(gr.sizeof_float, + max(1, int(sample_rate/number_rate))) + + + #c2mag = gr.complex_to_mag(number_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, number_size) + + # FIXME We need to add 3dB to all bins but the DC bin + #log = gr.nlog10_ff(20, number_size, + # -20*math.log10(number_size)-10*math.log10(power/number_size)) + sink = gr.message_sink(gr.sizeof_float , self.msgq, True) + + #fg.connect (s2p, one_in_n, fft, c2mag, self.avg, log, sink) + fg.connect(self.avg,one_in_n,sink) + gr.hier_block.__init__(self, fg, self.avg, sink) + self.win = number_window(self, parent, size=size,label=label) + self.set_average(self.average) + + +class number_sink_c(gr.hier_block, number_sink_base): + def __init__(self, fg, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, + decimal_places=10, ref_level=50, sample_rate=1, #number_size=512, + number_rate=default_number_rate, average=False, avg_alpha=None, + label='', size=default_numbersink_size, peak_hold=False): + + number_sink_base.__init__(self, unit=unit, input_is_real=False, base_value=base_value,factor=factor, + minval=minval,maxval=maxval,decimal_places=decimal_places, ref_level=ref_level, + sample_rate=sample_rate, #number_size=number_size, + number_rate=number_rate, + average=average, avg_alpha=avg_alpha, label=label, + peak_hold=peak_hold) + + number_size=1 + one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex, + max(1, int(sample_rate/number_rate))) + + + #c2mag = gr.complex_to_mag(number_size) + self.avg = gr.single_pole_iir_filter_cc(1.0, number_size) + + # FIXME We need to add 3dB to all bins but the DC bin + #log = gr.nlog10_ff(20, number_size, + # -20*math.log10(number_size)-10*math.log10(power/number_size)) + sink = gr.message_sink(gr.sizeof_gr_complex , self.msgq, True) + + #fg.connect (s2p, one_in_n, fft, c2mag, self.avg, log, sink) + fg.connect(self.avg,one_in_n,sink) + gr.hier_block.__init__(self, fg, self.avg, sink) + self.win = number_window(self, parent, size=size,label=label) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, number_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.number_size = number_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one number in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = Numeric.fromstring (s, Numeric.Float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + +#======================================================================================== +class static_text_window (wx.StaticText): #plot.PlotCanvas): + def __init__ (self, parent, numbersink,id = -1,label="number", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + #plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + wx.StaticText.__init__(self, parent, id, label, pos, size, style, name) + #self.static_text=wx.StaticText( parent, id, label, pos, (size[0]/2,size[1]/2), style, name) + #gauge_style = wx.GA_HORIZONTAL + #self.gauge=wx.Gauge( parent, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") + #wx.BoxSizer.__init__ (self,wx.VERTICAL) + #self.Add (self.static_text, 0, wx.EXPAND) + #self.Add (self.gauge, 1, wx.EXPAND) + self.parent=parent + self.label=label + #self.y_range = None + self.numbersink = numbersink + self.peak_hold = False + self.peak_vals = None + + #self.SetEnableGrid (True) + # self.SetEnableZoom (True) + # self.SetBackgroundColour ('black') + + self.build_popup_menu() + + #EVT_DATA_EVENT (self, self.set_data) + #wx.EVT_CLOSE (self, self.on_close_window) + #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + #self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) + + + def on_close_window (self, event): + print "number_window:on_close_window" + self.keep_running = False + + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.numbersink.ref_level + ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.numbersink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.numbersink.set_peak_hold(evt.IsChecked()) + + def on_show_gauge(self, evt): + # print "on_show_gauge" + #if evt.IsChecked(): + self.numbersink.set_show_gauge(evt.IsChecked()) + print evt.IsChecked() + # print "show gauge" + #else: + # self.parent.gauge.Hide() + # print "hide gauge" + + def on_incr_ref_level(self, evt): + # print "on_incr_ref_level" + self.numbersink.set_ref_level(self.numbersink.ref_level + + self.numbersink.decimal_places) + + def on_decr_ref_level(self, evt): + # print "on_decr_ref_level" + self.numbersink.set_ref_level(self.numbersink.ref_level + - self.numbersink.decimal_places) + + def on_incr_decimal_places(self, evt): + # print "on_incr_decimal_places" + self.numbersink.set_decimal_places(self.numbersink.decimal_places+1) #next_up(self.numbersink.decimal_places, (1,2,5,10,20))) + + def on_decr_decimal_places(self, evt): + # print "on_decr_decimal_places" + self.numbersink.set_decimal_places(max(self.numbersink.decimal_places-1,0)) #next_down(self.numbersink.decimal_places, (1,2,5,10,20))) + + def on_decimal_places(self, evt): + # print "on_decimal_places" + Id = evt.GetId() + if Id == self.id_decimal_places_0: + self.numbersink.set_decimal_places(0) + elif Id == self.id_decimal_places_1: + self.numbersink.set_decimal_places(1) + elif Id == self.id_decimal_places_2: + self.numbersink.set_decimal_places(2) + elif Id == self.id_decimal_places_3: + self.numbersink.set_decimal_places(3) + elif Id == self.id_decimal_places_6: + self.numbersink.set_decimal_places(6) + elif Id == self.id_decimal_places_9: + self.numbersink.set_decimal_places(9) + + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + #self.id_hide_gauge = wx.NewId() + self.id_show_gauge = wx.NewId() + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_decimal_places = wx.NewId() + self.id_decr_decimal_places = wx.NewId() + self.id_decimal_places_0 = wx.NewId() + self.id_decimal_places_1 = wx.NewId() + self.id_decimal_places_2 = wx.NewId() + self.id_decimal_places_3 = wx.NewId() + self.id_decimal_places_6 = wx.NewId() + self.id_decimal_places_9 = wx.NewId() + self.id_average = wx.NewId() + self.id_peak_hold = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + #self.Bind(wx.EVT_MENU, self.on_hide_gauge, id=self.id_hide_gauge) + self.Bind(wx.EVT_MENU, self.on_show_gauge, id=self.id_show_gauge) + self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.Bind(wx.EVT_MENU, self.on_incr_decimal_places, id=self.id_incr_decimal_places) + self.Bind(wx.EVT_MENU, self.on_decr_decimal_places, id=self.id_decr_decimal_places) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_0) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_1) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_2) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_3) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_6) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_9) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") + #menu.Append(self.id_hide_gauge, "Hide gauge") + menu.AppendCheckItem(self.id_show_gauge, "Show gauge") + menu.Append(self.id_incr_ref_level, "Incr Ref Level") + menu.Append(self.id_decr_ref_level, "Decr Ref Level") + menu.Append(self.id_incr_decimal_places, "Incr decimal places") + menu.Append(self.id_decr_decimal_places, "Decr decimal places") + menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + menu.AppendCheckItem(self.id_decimal_places_0, "0 decimal places") + menu.AppendCheckItem(self.id_decimal_places_1, "1 decimal places") + menu.AppendCheckItem(self.id_decimal_places_2, "2 decimal places") + menu.AppendCheckItem(self.id_decimal_places_3, "3 decimal places") + menu.AppendCheckItem(self.id_decimal_places_6, "6 decimal places") + menu.AppendCheckItem(self.id_decimal_places_9, "9 decimal places") + + self.checkmarks = { + self.id_average : lambda : self.numbersink.average, + self.id_peak_hold : lambda : self.numbersink.peak_hold,# self.id_hide_gauge : lambda : self.numbersink.hide_gauge, + self.id_show_gauge : lambda : self.numbersink.show_gauge, + self.id_decimal_places_0 : lambda : self.numbersink.decimal_places == 0, + self.id_decimal_places_1 : lambda : self.numbersink.decimal_places == 1, + self.id_decimal_places_2 : lambda : self.numbersink.decimal_places == 2, + self.id_decimal_places_3 : lambda : self.numbersink.decimal_places == 3, + self.id_decimal_places_6 : lambda : self.numbersink.decimal_places == 6, + self.id_decimal_places_9 : lambda : self.numbersink.decimal_places == 9, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +#======================================================================================== +class number_window (plot.PlotCanvas): + def __init__ (self, numbersink, parent, id = -1,label="number", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + #wx.StaticText.__init__(self, parent, id, label, pos, (size[0]/2,size[1]/2), style, name) + #print 'parent',parent + self.static_text=static_text_window( self, numbersink,id, label, pos, (size[0]/2,size[1]/2), style, name) + gauge_style = wx.GA_HORIZONTAL + vbox=wx.BoxSizer(wx.VERTICAL) + vbox.Add (self.static_text, 0, wx.EXPAND) + self.current_value=None + if numbersink.input_is_real: + self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") + vbox.Add (self.gauge, 1, wx.EXPAND) + else: + self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge") + #hbox=wx.BoxSizer(wx.HORIZONTAL) + self.gauge_imag=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]*2/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge_imag") + vbox.Add (self.gauge, 1, wx.EXPAND) + vbox.Add (self.gauge_imag, 1, wx.EXPAND) + #vbox.Add (hbox, 1, wx.EXPAND) + self.sizer = vbox + self.SetSizer (self.sizer) + self.SetAutoLayout (True) + self.sizer.Fit (self) + + self.label=label + #self.y_range = None + self.numbersink = numbersink + self.peak_hold = False + self.peak_vals = None + + #self.SetEnableGrid (True) + # self.SetEnableZoom (True) + # self.SetBackgroundColour ('black') + + #self.build_popup_menu() + + EVT_DATA_EVENT (self, self.set_data) + wx.EVT_CLOSE (self, self.on_close_window) + #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) + + + def on_close_window (self, event): + print "number_window:on_close_window" + self.keep_running = False + + def set_show_gauge(self, enable): + self.show_gauge = enable + if enable: + self.gauge.Show() + if not self.numbersink.input_is_real: + self.gauge_imag.Show() + #print 'show' + else: + self.gauge.Hide() + if not self.numbersink.input_is_real: + self.gauge_imag.Hide() + #print 'hide' + + def set_data (self, evt): + numbers = evt.data + L = len (numbers) + + if self.peak_hold: + if self.peak_vals is None: + self.peak_vals = numbers + else: + self.peak_vals = Numeric.maximum(numbers, self.peak_vals) + numbers = self.peak_vals + + if self.numbersink.input_is_real: + real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value + imag_value=0.0 + self.current_value=real_value + else: + real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value + imag_value=numbers[1]*self.numbersink.factor + self.numbersink.base_value + self.current_value=complex(real_value,imag_value) + #x = max(abs(self.numbersink.sample_rate), abs(self.numbersink.base_value)) + x = max(real_value, imag_value) + if x >= 1e9: + sf = 1e-9 + unit_prefix = "G" + elif x >= 1e6: + sf = 1e-6 + unit_prefix = "M" + elif x>= 1e3: + sf = 1e-3 + unit_prefix = "k" + else : + sf = 1 + unit_prefix = "" + #self.update_y_range () + if self.numbersink.input_is_real: + showtext = "%s: %.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,unit_prefix,self.numbersink.unit) + else: + showtext = "%s: %.*f,%.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf, + self.numbersink.decimal_places,imag_value*sf,unit_prefix,self.numbersink.unit) + self.static_text.SetLabel(showtext) + #print (int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) + self.gauge.SetValue(int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) + if not self.numbersink.input_is_real: + self.gauge.SetValue(int(float((imag_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.numbersink.ref_level + ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.numbersink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.numbersink.set_peak_hold(evt.IsChecked()) + + + + + + + + + + +# ---------------------------------------------------------------- +# Deprecated interfaces +# ---------------------------------------------------------------- + +# returns (block, win). +# block requires a single input stream of float +# win is a subclass of wxWindow + +def make_number_sink_f(fg, parent, label, number_size, input_rate, ymin = 0, ymax=50): + + block = number_sink_f(fg, parent, label=label, number_size=number_size, sample_rate=input_rate, + decimal_places=(ymax - ymin)/8, ref_level=ymax) + return (block, block.win) + +# returns (block, win). +# block requires a single input stream of gr_complex +# win is a subclass of wxWindow + +def make_number_sink_c(fg, parent, label, number_size, input_rate, ymin=0, ymax=50): + block = number_sink_c(fg, parent, label=label, number_size=number_size, sample_rate=input_rate, + decimal_places=(ymax - ymin)/8, ref_level=ymax) + return (block, block.win) + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_flow_graph (stdgui.gui_flow_graph): + def __init__(self, frame, panel, vbox, argv): + stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + + #number_size = 256 + + # build our flow graph + input_rate = 20.48e3 + + # Generate a complex sinusoid + src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + #sink1 = number_sink_c (self, panel, label="Complex Data", number_size=number_size, + # sample_rate=input_rate, base_value=100e3, + # ref_level=0, decimal_places=3) + #vbox.Add (sink1.win, 1, wx.EXPAND) + #self.connect (src1, thr1, sink1) + + src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = number_sink_f (self, panel, unit='Hz',label="Real Data", avg_alpha=0.001,#number_size=number_size*2, + sample_rate=input_rate, base_value=100e3, + ref_level=0, decimal_places=3) + vbox.Add (sink2.win, 1, wx.EXPAND) + sink3 = number_sink_c (self, panel, unit='V',label="Complex Data", avg_alpha=0.001,#number_size=number_size*2, + sample_rate=input_rate, base_value=0, + ref_level=0, decimal_places=3) + vbox.Add (sink3.win, 1, wx.EXPAND) + self.connect (src2, thr2, sink2) + self.connect (src1, thr1, sink3) +def main (): + app = stdgui.stdapp (test_app_flow_graph, + "Number Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () -- cgit From 19a5b154cc5c2cd5f80bcd17d71241572ca7fbd6 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Fri, 2 Mar 2007 02:49:48 +0000 Subject: Merged r4671:4680 from jcorgan/est into trunk. Pulls in gr-wxgui updates for new hierarchical blocks and work-in-progress channel sounder example. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@4681 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 4 + gr-wxgui/src/python/fftsink2.py | 506 ++++++++++++++++++++++++++ gr-wxgui/src/python/scopesink2.py | 659 ++++++++++++++++++++++++++++++++++ gr-wxgui/src/python/stdgui2.py | 95 +++++ gr-wxgui/src/python/waterfallsink2.py | 489 +++++++++++++++++++++++++ 5 files changed, 1753 insertions(+) create mode 100755 gr-wxgui/src/python/fftsink2.py create mode 100755 gr-wxgui/src/python/scopesink2.py create mode 100644 gr-wxgui/src/python/stdgui2.py create mode 100755 gr-wxgui/src/python/waterfallsink2.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index ab5030740..ce62574d7 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -32,10 +32,14 @@ ourpython_PYTHON = \ __init__.py \ form.py \ fftsink.py \ + fftsink2.py \ plot.py \ powermate.py \ scopesink.py \ + scopesink2.py \ waterfallsink.py \ + waterfallsink2.py \ slider.py \ stdgui.py \ + stdgui2.py \ numbersink.py diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py new file mode 100755 index 000000000..6de071880 --- /dev/null +++ b/gr-wxgui/src/python/fftsink2.py @@ -0,0 +1,506 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2006 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 2, 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 gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import threading +import math + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class fft_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, ref_level=50, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title='', peak_hold=False): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.y_divs = 8 + self.y_per_div=y_per_div + self.ref_level = ref_level + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.peak_hold = peak_hold + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages + + def set_y_per_div(self, y_per_div): + self.y_per_div = y_per_div + + def set_ref_level(self, ref_level): + self.ref_level = ref_level + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + self.set_peak_hold(False) + else: + self.avg.set_taps(1.0) + + def set_peak_hold(self, enable): + self.peak_hold = enable + if enable: + self.set_average(False) + self.win.set_peak_hold(enable) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + +class fft_sink_f(gr.hier_block2, fft_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "fft_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0,0,0)) + + fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + y_per_div=y_per_div, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + self.define_component("s2p", gr.stream_to_vector(gr.sizeof_float, self.fft_size)) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + self.define_component("one_in_n", self.one_in_n) + + mywindow = window.blackmanharris(self.fft_size) + self.define_component("fft", gr.fft_vfc(self.fft_size, True, mywindow)) + power = 0 + for tap in mywindow: + power += tap*tap + + self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.define_component("avg", self.avg) + + # FIXME We need to add 3dB to all bins but the DC bin + self.define_component("log", gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))) + self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) + + + # Ultimately this will be + # self.connect("self s2p one_in_n fft c2mag avg log sink") + self.connect("self", 0, "s2p", 0) + self.connect("s2p", 0, "one_in_n", 0) + self.connect("one_in_n", 0, "fft", 0) + self.connect("fft", 0, "c2mag", 0) + self.connect("c2mag", 0, "avg", 0) + self.connect("avg", 0, "log", 0) + self.connect("log", 0, "sink", 0) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + + +class fft_sink_c(gr.hier_block2, fft_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "fft_sink_c", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + y_per_div=y_per_div, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + self.define_component("s2p", gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + self.define_component("one_in_n", self.one_in_n) + + mywindow = window.blackmanharris(self.fft_size) + self.define_component("fft", gr.fft_vcc(self.fft_size, True, mywindow)) + power = 0 + for tap in mywindow: + power += tap*tap + + self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.define_component("avg", self.avg) + + # FIXME We need to add 3dB to all bins but the DC bin + self.define_component("log", gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))) + self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) + + # Ultimately this will be + # self.connect("self s2p one_in_n fft c2mag avg log sink") + self.connect("self", 0, "s2p", 0) + self.connect("s2p", 0, "one_in_n", 0) + self.connect("one_in_n", 0, "fft", 0) + self.connect("fft", 0, "c2mag", 0) + self.connect("c2mag", 0, "avg", 0) + self.connect("avg", 0, "log", 0) + self.connect("log", 0, "sink", 0) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = Numeric.fromstring (s, Numeric.Float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + + +class fft_window (plot.PlotCanvas): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + + self.y_range = None + self.fftsink = fftsink + self.peak_hold = False + self.peak_vals = None + + self.SetEnableGrid (True) + # self.SetEnableZoom (True) + # self.SetBackgroundColour ('black') + + self.build_popup_menu() + + EVT_DATA_EVENT (self, self.set_data) + wx.EVT_CLOSE (self, self.on_close_window) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + + + def on_close_window (self, event): + print "fft_window:on_close_window" + self.keep_running = False + + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + if self.peak_hold: + if self.peak_vals is None: + self.peak_vals = dB + else: + self.peak_vals = Numeric.maximum(dB, self.peak_vals) + dB = self.peak_vals + + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + sf = 1e-9 + units = "GHz" + elif x >= 1e6: + sf = 1e-6 + units = "MHz" + else: + sf = 1e-3 + units = "kHz" + + if self.fftsink.input_is_real: # only plot 1/2 the points + x_vals = ((Numeric.arrayrange (L/2) + * (self.fftsink.sample_rate * sf / L)) + + self.fftsink.baseband_freq * sf) + points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points[:,0] = x_vals + points[:,1] = dB[0:L/2] + else: + # the "negative freqs" are in the second half of the array + x_vals = ((Numeric.arrayrange (-L/2, L/2) + * (self.fftsink.sample_rate * sf / L)) + + self.fftsink.baseband_freq * sf) + points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points[:,0] = x_vals + points[:,1] = Numeric.concatenate ((dB[L/2:], dB[0:L/2])) + + + lines = plot.PolyLine (points, colour='BLUE') + + graphics = plot.PlotGraphics ([lines], + title=self.fftsink.title, + xLabel = units, yLabel = "dB") + + self.Draw (graphics, xAxis=None, yAxis=self.y_range) + self.update_y_range () + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.fftsink.ref_level + ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.fftsink.set_peak_hold(evt.IsChecked()) + + def on_incr_ref_level(self, evt): + # print "on_incr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + + self.fftsink.y_per_div) + + def on_decr_ref_level(self, evt): + # print "on_decr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + - self.fftsink.y_per_div) + + def on_incr_y_per_div(self, evt): + # print "on_incr_y_per_div" + self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, (1,2,5,10,20))) + + def on_decr_y_per_div(self, evt): + # print "on_decr_y_per_div" + self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, (1,2,5,10,20))) + + def on_y_per_div(self, evt): + # print "on_y_per_div" + Id = evt.GetId() + if Id == self.id_y_per_div_1: + self.fftsink.set_y_per_div(1) + elif Id == self.id_y_per_div_2: + self.fftsink.set_y_per_div(2) + elif Id == self.id_y_per_div_5: + self.fftsink.set_y_per_div(5) + elif Id == self.id_y_per_div_10: + self.fftsink.set_y_per_div(10) + elif Id == self.id_y_per_div_20: + self.fftsink.set_y_per_div(20) + + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + self.id_peak_hold = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") + menu.Append(self.id_incr_ref_level, "Incr Ref Level") + menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average, + self.id_peak_hold : lambda : self.fftsink.peak_hold, + self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 256 + + # build our flow graph + input_rate = 20.48e3 + + # Generate a complex sinusoid + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20) + vbox.Add (sink1.win, 1, wx.EXPAND) + + self.define_component("src1", src1) + self.define_component("thr1", thr1) + self.define_component("sink1", sink1) + + self.connect ("src1", 0, "thr1", 0) + self.connect ("thr1", 0, "sink1", 0) + + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.define_component("src2", src2) + self.define_component("thr2", thr2) + self.define_component("sink2", sink2) + + self.connect ("src2", 0, "thr2", 0) + self.connect ("thr2", 0, "sink2", 0) + +def main (): + app = stdgui2.stdapp (test_app_block, + "FFT Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py new file mode 100755 index 000000000..2163d1eed --- /dev/null +++ b/gr-wxgui/src/python/scopesink2.py @@ -0,0 +1,659 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2006,2007 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 2, 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 gnuradio import gr, gru, eng_notation +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import threading +import struct + +default_scopesink_size = (640, 240) +default_v_scale = 1000 +default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) + +class scope_sink_f(gr.hier_block2): + def __init__(self, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None): + + gr.hier_block2.__init__(self, "scope_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0,0,0)) + + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + self.guts = gr.oscope_sink_f(sample_rate, msgq) + self.define_component("guts", self.guts) + + self.connect("self", 0, "guts", 0) + + self.win = scope_window(win_info (msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +class scope_sink_c(gr.hier_block2): + def __init__(self, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None): + + gr.hier_block2.__init__(self, "scope_sink_c", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + self.define_component("c2f", gr.complex_to_float()) + self.guts = gr.oscope_sink_f(sample_rate, msgq) + self.define_component("guts", self.guts) + + self.connect("self", 0, "c2f", 0) + self.connect("c2f", 0, "guts", 0) + self.connect("c2f", 1, "guts", 1) + + self.win = scope_window(win_info(msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +# ======================================================================== + + +time_base_list = [ # time / division + 1.0e-7, # 100ns / div + 2.5e-7, + 5.0e-7, + 1.0e-6, # 1us / div + 2.5e-6, + 5.0e-6, + 1.0e-5, # 10us / div + 2.5e-5, + 5.0e-5, + 1.0e-4, # 100us / div + 2.5e-4, + 5.0e-4, + 1.0e-3, # 1ms / div + 2.5e-3, + 5.0e-3, + 1.0e-2, # 10ms / div + 2.5e-2, + 5.0e-2 + ] + +v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER + 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div + 5.0e-3, + 1.0e-2, + 2.0e-2, + 5.0e-2, + 1.0e-1, + 2.0e-1, + 5.0e-1, + 1.0e+0, + 2.0e+0, + 5.0e+0, + 1.0e+1, + 2.0e+1, + 5.0e+1, + 1.0e+2, + 2.0e+2, + 5.0e+2, + 1.0e+3, + 2.0e+3, + 5.0e+3, + 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 + ] + + +wxDATA_EVENT = wx.NewEventType() + +def EVT_DATA_EVENT(win, func): + win.Connect(-1, -1, wxDATA_EVENT, func) + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (wxDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class win_info (object): + __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', + 'scopesink', 'title', + 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', + 'autorange', 'running'] + + def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, + scopesink, title = "Oscilloscope"): + self.msgq = msgq + self.sample_rate = sample_rate + self.frame_decim = frame_decim + self.scopesink = scopesink + self.title = title; + + self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) + self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) + + self.marker = 'line' + self.xy = False + if v_scale == None: # 0 and None are both False, but 0 != None + self.autorange = True + else: + self.autorange = False # 0 is a valid v_scale + self.running = True + + def get_time_per_div (self): + return self.time_scale_cursor.current () + + def get_volts_per_div (self): + return self.v_scale_cursor.current () + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + + def get_sample_rate (self): + return self.sample_rate + + def get_decimation_rate (self): + return 1.0 + + def set_marker (self, s): + self.marker = s + + def get_marker (self): + return self.marker + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, event_receiver, frame_decim, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.event_receiver = event_receiver + self.frame_decim = frame_decim + self.iscan = 0 + self.keep_running = True + self.start () + + def run (self): + # print "input_watcher: pid = ", os.getpid () + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + if self.iscan == 0: # only display at frame_decim + self.iscan = self.frame_decim + + 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 + + bytes_per_chan = nsamples * gr.sizeof_float + + records = [] + for ch in range (nchan): + + start = ch * bytes_per_chan + chan_data = s[start:start+bytes_per_chan] + rec = Numeric.fromstring (chan_data, Numeric.Float32) + records.append (rec) + + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) + + de = DataEvent (records) + wx.PostEvent (self.event_receiver, de) + records = [] + del de + + # end if iscan == 0 + self.iscan -= 1 + + +class scope_window (wx.Panel): + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): + wx.Panel.__init__ (self, parent, -1) + self.info = info + + vbox = wx.BoxSizer (wx.VERTICAL) + + self.graph = graph_window (info, self, -1) + + vbox.Add (self.graph, 1, wx.EXPAND) + vbox.Add (self.make_control_box(), 0, wx.EXPAND) + vbox.Add (self.make_control2_box(), 0, wx.EXPAND) + + self.sizer = vbox + self.SetSizer (self.sizer) + self.SetAutoLayout (True) + self.sizer.Fit (self) + self.set_autorange(self.info.autorange) + + + # second row of control buttons etc. appears BELOW control_box + def make_control2_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT) + self.inc_v_button.SetToolTipString ("Increase vertical range") + wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above + + self.dec_v_button = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT) + self.dec_v_button.SetToolTipString ("Decrease vertical range") + wx.EVT_BUTTON (self, 1100, self.decr_v_scale) + + self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div + self.update_v_scale_label () + + self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange") + self.autorange_checkbox.SetToolTipString ("Select autorange on/off") + wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) + + ctrlbox.Add ((5,0) ,0) # left margin space + ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER) + ctrlbox.Add ((20,0) ,0) # spacer + ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER) + + return ctrlbox + + def make_control_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT) + tb_left.SetToolTipString ("Increase time base") + wx.EVT_BUTTON (self, 1001, self.incr_timebase) + + + tb_right = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT) + tb_right.SetToolTipString ("Decrease time base") + wx.EVT_BUTTON (self, 1000, self.decr_timebase) + + self.time_base_label = wx.StaticText (self, 1002, "") + self.update_timebase_label () + + ctrlbox.Add ((5,0) ,0) + # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) + ctrlbox.Add (tb_left, 0, wx.EXPAND) + ctrlbox.Add (tb_right, 0, wx.EXPAND) + ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER) + + ctrlbox.Add ((10,0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER) + self.trig_chan_choice = wx.Choice (self, 1004, + choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) + self.trig_chan_choice.SetToolTipString ("Select channel for trigger") + wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event) + ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) + + self.trig_mode_choice = wx.Choice (self, 1005, + choices = ['Auto', 'Pos', 'Neg']) + self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") + wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) + ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) + + trig_level50 = wx.Button (self, 1006, "50%") + trig_level50.SetToolTipString ("Set trigger level to 50%") + wx.EVT_BUTTON (self, 1006, self.set_trig_level50) + ctrlbox.Add (trig_level50, 0, wx.EXPAND) + + run_stop = wx.Button (self, 1007, "Run/Stop") + run_stop.SetToolTipString ("Toggle Run/Stop mode") + wx.EVT_BUTTON (self, 1007, self.run_stop) + ctrlbox.Add (run_stop, 0, wx.EXPAND) + + ctrlbox.Add ((10, 0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) + self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices) + self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots") + wx.EVT_CHOICE (self, 1002, self.marker_choice_event) + ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER) + + self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y']) + self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display") + wx.EVT_CHOICE (self, 1003, self.xy_choice_event) + ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) + + return ctrlbox + + _marker_choices = ['line', 'plus', 'dot'] + + def update_timebase_label (self): + time_per_div = self.info.get_time_per_div () + s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' + self.time_base_label.SetLabel (s) + + def decr_timebase (self, evt): + self.info.time_scale_cursor.prev () + self.update_timebase_label () + + def incr_timebase (self, evt): + self.info.time_scale_cursor.next () + self.update_timebase_label () + + def update_v_scale_label (self): + volts_per_div = self.info.get_volts_per_div () + s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div + self.v_scale_label.SetLabel (s) + + def decr_v_scale (self, evt): + self.info.v_scale_cursor.prev () + self.update_v_scale_label () + + def incr_v_scale (self, evt): + self.info.v_scale_cursor.next () + self.update_v_scale_label () + + def marker_choice_event (self, evt): + s = evt.GetString () + self.set_marker (s) + + def set_autorange(self, on): + if on: + self.v_scale_label.SetLabel(" (auto)") + self.info.autorange = True + self.autorange_checkbox.SetValue(True) + self.inc_v_button.Enable(False) + self.dec_v_button.Enable(False) + else: + if self.graph.y_range: + (l,u) = self.graph.y_range # found by autorange + self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) + self.update_v_scale_label() + self.info.autorange = False + self.autorange_checkbox.SetValue(False) + self.inc_v_button.Enable(True) + self.dec_v_button.Enable(True) + + def autorange_checkbox_event(self, evt): + if evt.Checked(): + self.set_autorange(True) + else: + self.set_autorange(False) + + def set_marker (self, s): + self.info.set_marker (s) # set info for drawing routines + i = self.marker_choice.FindString (s) + assert i >= 0, "Hmmm, set_marker problem" + self.marker_choice.SetSelection (i) + + def set_format_line (self): + self.set_marker ('line') + + def set_format_dot (self): + self.set_marker ('dot') + + def set_format_plus (self): + self.set_marker ('plus') + + def xy_choice_event (self, evt): + s = evt.GetString () + self.info.xy = s == 'X:Y' + + def trig_chan_choice_event (self, evt): + s = evt.GetString () + ch = int (s[-1]) - 1 + self.info.scopesink.set_trigger_channel (ch) + + def trig_mode_choice_event (self, evt): + sink = self.info.scopesink + s = evt.GetString () + if s == 'Pos': + sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) + elif s == 'Neg': + sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) + elif s == 'Auto': + sink.set_trigger_mode (gr.gr_TRIG_AUTO) + else: + assert 0, "Bad trig_mode_choice string" + + def set_trig_level50 (self, evt): + self.info.scopesink.set_trigger_level_auto () + + def run_stop (self, evt): + self.info.running = not self.info.running + + +class graph_window (plot.PlotCanvas): + + channel_colors = ['BLUE', 'RED', + 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = (640, 240), + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + + self.SetXUseScopeTicks (True) + self.SetEnableGrid (True) + self.SetEnableZoom (True) + self.SetEnableLegend(True) + # self.SetBackgroundColour ('black') + + self.info = info; + self.y_range = None + self.x_range = None + self.avg_y_min = None + self.avg_y_max = None + self.avg_x_min = None + self.avg_x_max = None + + EVT_DATA_EVENT (self, self.format_data) + + self.input_watcher = input_watcher (info.msgq, self, info.frame_decim) + + def channel_color (self, ch): + return self.channel_colors[ch % len(self.channel_colors)] + + def format_data (self, evt): + if not self.info.running: + return + + if self.info.xy: + self.format_xy_data (evt) + return + + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + objects = [] + + Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) + x_vals = Ts * Numeric.arrayrange (-npoints/2, npoints/2) + + # preliminary clipping based on time axis here, instead of in graphics code + time_per_window = self.info.get_time_per_div () * 10 + n = int (time_per_window / Ts + 0.5) + n = n & ~0x1 # make even + n = max (2, min (n, npoints)) + + self.SetXUseScopeTicks (True) # use 10 divisions, no labels + + for ch in range(nchannels): + r = records[ch] + + # plot middle n points of record + + lb = npoints/2 - n/2 + ub = npoints/2 + n/2 + # points = zip (x_vals[lb:ub], r[lb:ub]) + points = Numeric.zeros ((ub-lb, 2), Numeric.Float64) + points[:,0] = x_vals[lb:ub] + points[:,1] = r[lb:ub] + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = '', yLabel = '') + + time_per_div = info.get_time_per_div () + x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! + volts_per_div = info.get_volts_per_div () + if not self.info.autorange: + self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) + self.Draw (graphics, xAxis=x_range, yAxis=self.y_range) + self.update_y_range () # autorange to self.y_range + + + def format_xy_data (self, evt): + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + if nchannels < 2: + return + + objects = [] + # points = zip (records[0], records[1]) + points = Numeric.zeros ((len(records[0]), 2), Numeric.Float32) + points[:,0] = records[0] + points[:,1] = records[1] + + self.SetXUseScopeTicks (False) + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (0))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (0))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = 'I', yLabel = 'Q') + + self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) + self.update_y_range () + self.update_x_range () + + + def update_y_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_y_min: # prevent vertical scale from jumping abruptly --? + self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) + self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) + else: # initial guess + self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 + self.avg_y_max = p2[1] # 500.0 + + self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) + # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ + # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) + + + def update_x_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_x_min: + self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) + self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) + else: + self.avg_x_min = p1[0] + self.avg_x_max = p2[0] + + self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max) + + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + if len(argv) > 1: + frame_decim = int(argv[1]) + else: + frame_decim = 1 + + if len(argv) > 2: + v_scale = float(argv[2]) # start up at this v_scale value + else: + v_scale = None # start up in autorange mode, default + + if len(argv) > 3: + t_scale = float(argv[3]) # start up at this t_scale value + else: + t_scale = None # old behavior + + print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + + input_rate = 1e6 + + # Generate a complex sinusoid + self.define_component("src0", gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3)) + + # We add this throttle block so that this demo doesn't suck down + # all the CPU available. You normally wouldn't use it... + self.define_component("throttle", gr.throttle(gr.sizeof_gr_complex, input_rate)) + + scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, + frame_decim=frame_decim, + v_scale=v_scale, t_scale=t_scale) + self.define_component("scope", scope) + vbox.Add (scope.win, 1, wx.EXPAND) + + # Ultimately this will be + # self.connect("src0 throttle scope") + self.connect("src0", 0, "throttle", 0) + self.connect("throttle", 0, "scope", 0) + + +def main (): + app = stdgui2.stdapp (test_top_block, "O'Scope Test App") + app.MainLoop () + +if __name__ == '__main__': + main () + +# ---------------------------------------------------------------- diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py new file mode 100644 index 000000000..28b2fadd3 --- /dev/null +++ b/gr-wxgui/src/python/stdgui2.py @@ -0,0 +1,95 @@ +# +# Copyright 2004 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 2, 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. +# + +'''A simple wx gui for GNU Radio applications''' + +import wx +import sys +from gnuradio import gr + + +class stdapp (wx.App): + def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2): + self.top_block_maker = top_block_maker + self.title = title + self._nstatus = nstatus + # All our initialization must come before calling wx.App.__init__. + # OnInit is called from somewhere in the guts of __init__. + wx.App.__init__ (self, redirect=False) + + def OnInit (self): + frame = stdframe (self.top_block_maker, self.title, self._nstatus) + frame.Show (True) + self.SetTopWindow (frame) + return True + + +class stdframe (wx.Frame): + def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2): + # print "stdframe.__init__" + wx.Frame.__init__(self, None, -1, title) + + self.CreateStatusBar (nstatus) + mainmenu = wx.MenuBar () + + menu = wx.Menu () + item = menu.Append (200, 'E&xit', 'Exit') + self.Bind (wx.EVT_MENU, self.OnCloseWindow, item) + mainmenu.Append (menu, "&File") + self.SetMenuBar (mainmenu) + + self.Bind (wx.EVT_CLOSE, self.OnCloseWindow) + self.panel = stdpanel (self, self, top_block_maker) + vbox = wx.BoxSizer(wx.VERTICAL) + vbox.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(vbox) + self.SetAutoLayout(True) + vbox.Fit(self) + + def OnCloseWindow (self, event): + self.runtime().stop() + self.Destroy () + + def runtime (self): + return self.panel.runtime + +class stdpanel (wx.Panel): + def __init__ (self, parent, frame, top_block_maker): + # print "stdpanel.__init__" + wx.Panel.__init__ (self, parent, -1) + self.frame = frame + + vbox = wx.BoxSizer (wx.VERTICAL) + self.top_block = top_block_maker (frame, self, vbox, sys.argv) + self.SetSizer (vbox) + self.SetAutoLayout (True) + vbox.Fit (self) + + self.runtime = gr.runtime(self.top_block) + self.runtime.start () + +class std_top_block (gr.hier_block2): + def __init__ (self, parent, panel, vbox, argv): + # Call the hier_block2 constructor + # Top blocks have no inputs and outputs + gr.hier_block2.__init__(self, "std_top_block", + gr.io_signature(0,0,0), + gr.io_signature(0,0,0)) diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py new file mode 100755 index 000000000..e91789532 --- /dev/null +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2007 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 2, 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 gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import Numeric +import os +import threading +import math + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class waterfall_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title=''): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue up to 2 messages + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + else: + self.avg.set_taps(1.0) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + +class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + gr.hier_block2.__init__(self, "waterfall_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0,0,0)) + + waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + self.define_component("s2p", gr.serial_to_parallel(gr.sizeof_float, self.fft_size)) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + self.define_component("one_in_n", self.one_in_n) + + mywindow = window.blackmanharris(self.fft_size) + self.define_component("fft", gr.fft_vfc(self.fft_size, True, mywindow)) + self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.define_component("avg", self.avg) + self.define_component("log", gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))) + self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) + + # Ultimately this will be + # self.connect("self s2p one_in_n fft c2mag avg log sink") + self.connect("self", 0, "s2p", 0) + self.connect("s2p", 0, "one_in_n", 0) + self.connect("one_in_n", 0, "fft", 0) + self.connect("fft", 0, "c2mag", 0) + self.connect("c2mag", 0, "avg", 0) + self.connect("avg", 0, "log", 0) + self.connect("log", 0, "sink", 0) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + gr.hier_block2.__init__(self, "waterfall_sink_f", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + self.define_component("s2p", gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size)) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + self.define_component("one_in_n", self.one_in_n) + + mywindow = window.blackmanharris(self.fft_size) + self.define_component("fft", gr.fft_vcc(self.fft_size, True, mywindow)) + self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.define_component("avg", self.avg) + self.define_component("log", gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))) + self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) + + # Ultimately this will be + # self.connect("self s2p one_in_n fft c2mag avg log sink") + self.connect("self", 0, "s2p", 0) + self.connect("s2p", 0, "one_in_n", 0) + self.connect("one_in_n", 0, "fft", 0) + self.connect("fft", 0, "c2mag", 0) + self.connect("c2mag", 0, "avg", 0) + self.connect("avg", 0, "log", 0) + self.connect("log", 0, "sink", 0) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = Numeric.fromstring (s, Numeric.Float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + + +class waterfall_window (wx.Panel): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + wx.Panel.__init__(self, parent, id, pos, size, style, name) + + self.fftsink = fftsink + self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) + + self.scale_factor = 5.0 # FIXME should autoscale, or set this + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Clear() + + self.pens = self.make_pens() + + wx.EVT_PAINT( self, self.OnPaint ) + wx.EVT_CLOSE (self, self.on_close_window) + EVT_DATA_EVENT (self, self.set_data) + + self.build_popup_menu() + + wx.EVT_CLOSE (self, self.on_close_window) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + + + def on_close_window (self, event): + print "waterfall_window: on_close_window" + self.keep_running = False + + def const_list(self,const,len): + return [const] * len + + def make_colormap(self): + r = [] + r.extend(self.const_list(0,96)) + r.extend(range(0,255,4)) + r.extend(self.const_list(255,64)) + r.extend(range(255,128,-4)) + + g = [] + g.extend(self.const_list(0,32)) + g.extend(range(0,255,4)) + g.extend(self.const_list(255,64)) + g.extend(range(255,0,-4)) + g.extend(self.const_list(0,32)) + + b = range(128,255,4) + b.extend(self.const_list(255,64)) + b.extend(range(255,0,-4)) + b.extend(self.const_list(0,96)) + return (r,g,b) + + def make_pens(self): + (r,g,b) = self.make_colormap() + pens = [] + for i in range(0,256): + colour = wx.Colour(r[i], g[i], b[i]) + pens.append( wx.Pen(colour, 2, wx.SOLID)) + return pens + + def OnPaint(self, event): + dc = wx.PaintDC(self) + self.DoDrawing(dc) + + def DoDrawing(self, dc=None): + if dc is None: + dc = wx.ClientDC(self) + dc.DrawBitmap(self.bm, 0, 0, False ) + + + def const_list(self,const,len): + a = [const] + for i in range(1,len): + a.append(const) + return a + + def make_colormap(self): + r = [] + r.extend(self.const_list(0,96)) + r.extend(range(0,255,4)) + r.extend(self.const_list(255,64)) + r.extend(range(255,128,-4)) + + g = [] + g.extend(self.const_list(0,32)) + g.extend(range(0,255,4)) + g.extend(self.const_list(255,64)) + g.extend(range(255,0,-4)) + g.extend(self.const_list(0,32)) + + b = range(128,255,4) + b.extend(self.const_list(255,64)) + b.extend(range(255,0,-4)) + b.extend(self.const_list(0,96)) + return (r,g,b) + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) + + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + sf = 1e-9 + units = "GHz" + elif x >= 1e6: + sf = 1e-6 + units = "MHz" + else: + sf = 1e-3 + units = "kHz" + + + if self.fftsink.input_is_real: # only plot 1/2 the points + d_max = L/2 + p_width = 2 + else: + d_max = L/2 + p_width = 1 + + scale_factor = self.scale_factor + if self.fftsink.input_is_real: # real fft + for x_pos in range(0, d_max): + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + else: # complex fft + for x_pos in range(0, d_max): # positive freqs + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 1) + for x_pos in range(0 , d_max): # negative freqs + value = int(dB[x_pos+d_max] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + + self.DoDrawing (None) + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + # menu.Append(self.id_incr_ref_level, "Incr Ref Level") + # menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + # menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average + #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 512 + + # build our flow graph + input_rate = 20.000e3 + + # Generate a complex sinusoid + self.define_component("src1", gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)) + #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + self.define_component("thr1", gr.throttle(gr.sizeof_gr_complex, input_rate)) + + sink1 = waterfall_sink_c (self, panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.define_component("sink1", sink1) + + vbox.Add (sink1.win, 1, wx.EXPAND) + + # Ultimately this will be + # self.connect("src1 thr1 sink1") + self.connect("src1", 0, "thr1", 0) + self.connect("thr1", 0, "sink1", 0) + + # generate a real sinusoid + self.define_component("src2", gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)) + #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + self.define_component("thr2", gr.throttle(gr.sizeof_float, input_rate)) + sink2 = waterfall_sink_f (self, panel, title="Real Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.define_component("sink2", sink2) + vbox.Add (sink2.win, 1, wx.EXPAND) + + # Ultimately this will be + # self.connect("src2 thr2 sink2") + self.connect("src2", 0, "thr2", 0) + self.connect("thr2", 0, "sink2", 0) + +def main (): + app = stdgui2.stdapp (test_top_block, + "Waterfall Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () -- cgit From b26ea69676c09f5366a9e2f33b11ae5a7521ffe5 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sat, 28 Apr 2007 02:20:28 +0000 Subject: Merged -r 5137:5174 from developer branch jcorgan/hb. Trunk passes distcheck. Converts gr.hier_block2 API to not use 'define_component' methodology anymore. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@5177 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 70 +++++++++----------------------- gr-wxgui/src/python/scopesink2.py | 23 ++++------- gr-wxgui/src/python/stdgui2.py | 6 +-- gr-wxgui/src/python/waterfallsink2.py | 76 ++++++++++------------------------- 4 files changed, 49 insertions(+), 126 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 6de071880..b591add12 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -106,37 +106,25 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): average=average, avg_alpha=avg_alpha, title=title, peak_hold=peak_hold) - self.define_component("s2p", gr.stream_to_vector(gr.sizeof_float, self.fft_size)) + self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - self.define_component("one_in_n", self.one_in_n) mywindow = window.blackmanharris(self.fft_size) - self.define_component("fft", gr.fft_vfc(self.fft_size, True, mywindow)) + self.fft = gr.fft_vfc(self.fft_size, True, mywindow) power = 0 for tap in mywindow: power += tap*tap - self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.define_component("avg", self.avg) # FIXME We need to add 3dB to all bins but the DC bin - self.define_component("log", gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))) - self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) - - - # Ultimately this will be - # self.connect("self s2p one_in_n fft c2mag avg log sink") - self.connect("self", 0, "s2p", 0) - self.connect("s2p", 0, "one_in_n", 0) - self.connect("one_in_n", 0, "fft", 0) - self.connect("fft", 0, "c2mag", 0) - self.connect("c2mag", 0, "avg", 0) - self.connect("avg", 0, "log", 0) - self.connect("log", 0, "sink", 0) - + self.log = gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + self.win = fft_window(self, parent, size=size) self.set_average(self.average) @@ -158,35 +146,24 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): average=average, avg_alpha=avg_alpha, title=title, peak_hold=peak_hold) - self.define_component("s2p", gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)) + self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - self.define_component("one_in_n", self.one_in_n) mywindow = window.blackmanharris(self.fft_size) - self.define_component("fft", gr.fft_vcc(self.fft_size, True, mywindow)) + self.fft = gr.fft_vcc(self.fft_size, True, mywindow) power = 0 for tap in mywindow: power += tap*tap - self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.define_component("avg", self.avg) # FIXME We need to add 3dB to all bins but the DC bin - self.define_component("log", gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))) - self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) - - # Ultimately this will be - # self.connect("self s2p one_in_n fft c2mag avg log sink") - self.connect("self", 0, "s2p", 0) - self.connect("s2p", 0, "one_in_n", 0) - self.connect("one_in_n", 0, "fft", 0) - self.connect("fft", 0, "c2mag", 0) - self.connect("c2mag", 0, "avg", 0) - self.connect("avg", 0, "log", 0) - self.connect("log", 0, "sink", 0) + self.log = gr.nlog10_ff(20, self.fft_size, + -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) self.win = fft_window(self, parent, size=size) self.set_average(self.average) @@ -475,12 +452,7 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20) vbox.Add (sink1.win, 1, wx.EXPAND) - self.define_component("src1", src1) - self.define_component("thr1", thr1) - self.define_component("sink1", sink1) - - self.connect ("src1", 0, "thr1", 0) - self.connect ("thr1", 0, "sink1", 0) + self.connect(src1, thr1, sink1) #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) @@ -490,16 +462,10 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20) vbox.Add (sink2.win, 1, wx.EXPAND) - self.define_component("src2", src2) - self.define_component("thr2", thr2) - self.define_component("sink2", sink2) - - self.connect ("src2", 0, "thr2", 0) - self.connect ("thr2", 0, "sink2", 0) + self.connect(src2, thr2, sink2) def main (): - app = stdgui2.stdapp (test_app_block, - "FFT Sink Test App") + app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") app.MainLoop () if __name__ == '__main__': diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 2163d1eed..aa2d76b1e 100755 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -43,9 +43,7 @@ class scope_sink_f(gr.hier_block2): msgq = gr.msg_queue(2) # message queue that holds at most 2 messages self.guts = gr.oscope_sink_f(sample_rate, msgq) - self.define_component("guts", self.guts) - - self.connect("self", 0, "guts", 0) + self.connect(self, self.guts) self.win = scope_window(win_info (msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) @@ -64,13 +62,11 @@ class scope_sink_c(gr.hier_block2): gr.io_signature(0,0,0)) msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.define_component("c2f", gr.complex_to_float()) + self.c2f = gr.complex_to_float() self.guts = gr.oscope_sink_f(sample_rate, msgq) - self.define_component("guts", self.guts) - - self.connect("self", 0, "c2f", 0) - self.connect("c2f", 0, "guts", 0) - self.connect("c2f", 1, "guts", 1) + self.connect(self, self.c2f) + self.connect((self.c2f, 0), (self.guts, 0)) + self.connect((self.c2f, 1), (self.guts, 1)) self.win = scope_window(win_info(msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) @@ -631,23 +627,20 @@ class test_top_block (stdgui2.std_top_block): input_rate = 1e6 # Generate a complex sinusoid - self.define_component("src0", gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3)) + self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) # We add this throttle block so that this demo doesn't suck down # all the CPU available. You normally wouldn't use it... - self.define_component("throttle", gr.throttle(gr.sizeof_gr_complex, input_rate)) + self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, frame_decim=frame_decim, v_scale=v_scale, t_scale=t_scale) - self.define_component("scope", scope) vbox.Add (scope.win, 1, wx.EXPAND) # Ultimately this will be # self.connect("src0 throttle scope") - self.connect("src0", 0, "throttle", 0) - self.connect("throttle", 0, "scope", 0) - + self.connect(self.src0, self.thr, scope) def main (): app = stdgui2.stdapp (test_top_block, "O'Scope Test App") diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py index 28b2fadd3..83158abd9 100644 --- a/gr-wxgui/src/python/stdgui2.py +++ b/gr-wxgui/src/python/stdgui2.py @@ -86,10 +86,8 @@ class stdpanel (wx.Panel): self.runtime = gr.runtime(self.top_block) self.runtime.start () -class std_top_block (gr.hier_block2): +class std_top_block (gr.top_block): def __init__ (self, parent, panel, vbox, argv): # Call the hier_block2 constructor # Top blocks have no inputs and outputs - gr.hier_block2.__init__(self, "std_top_block", - gr.io_signature(0,0,0), - gr.io_signature(0,0,0)) + gr.top_block.__init__(self, "std_top_block") diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index e91789532..cd4cc75da 100755 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -87,28 +87,17 @@ class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title) - self.define_component("s2p", gr.serial_to_parallel(gr.sizeof_float, self.fft_size)) + self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - self.define_component("one_in_n", self.one_in_n) mywindow = window.blackmanharris(self.fft_size) - self.define_component("fft", gr.fft_vfc(self.fft_size, True, mywindow)) - self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.fft = gr.fft_vfc(self.fft_size, True, mywindow) + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.define_component("avg", self.avg) - self.define_component("log", gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))) - self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) - - # Ultimately this will be - # self.connect("self s2p one_in_n fft c2mag avg log sink") - self.connect("self", 0, "s2p", 0) - self.connect("s2p", 0, "one_in_n", 0) - self.connect("one_in_n", 0, "fft", 0) - self.connect("fft", 0, "c2mag", 0) - self.connect("c2mag", 0, "avg", 0) - self.connect("avg", 0, "log", 0) - self.connect("log", 0, "sink", 0) + self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) self.win = waterfall_window(self, parent, size=size) self.set_average(self.average) @@ -129,28 +118,17 @@ class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title) - self.define_component("s2p", gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size)) + self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - self.define_component("one_in_n", self.one_in_n) mywindow = window.blackmanharris(self.fft_size) - self.define_component("fft", gr.fft_vcc(self.fft_size, True, mywindow)) - self.define_component("c2mag", gr.complex_to_mag(self.fft_size)) + self.fft = gr.fft_vcc(self.fft_size, True, mywindow) + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.define_component("avg", self.avg) - self.define_component("log", gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))) - self.define_component("sink", gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)) - - # Ultimately this will be - # self.connect("self s2p one_in_n fft c2mag avg log sink") - self.connect("self", 0, "s2p", 0) - self.connect("s2p", 0, "one_in_n", 0) - self.connect("one_in_n", 0, "fft", 0) - self.connect("fft", 0, "c2mag", 0) - self.connect("c2mag", 0, "avg", 0) - self.connect("avg", 0, "log", 0) - self.connect("log", 0, "sink", 0) + self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) self.win = waterfall_window(self, parent, size=size) self.set_average(self.average) @@ -448,41 +426,29 @@ class test_top_block (stdgui2.std_top_block): input_rate = 20.000e3 # Generate a complex sinusoid - self.define_component("src1", gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)) + self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. - self.define_component("thr1", gr.throttle(gr.sizeof_gr_complex, input_rate)) + self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) - sink1 = waterfall_sink_c (self, panel, title="Complex Data", fft_size=fft_size, + sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size, sample_rate=input_rate, baseband_freq=100e3) - self.define_component("sink1", sink1) - + self.connect(self.src1, self.thr1, sink1) vbox.Add (sink1.win, 1, wx.EXPAND) - # Ultimately this will be - # self.connect("src1 thr1 sink1") - self.connect("src1", 0, "thr1", 0) - self.connect("thr1", 0, "sink1", 0) - # generate a real sinusoid - self.define_component("src2", gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)) - #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) - self.define_component("thr2", gr.throttle(gr.sizeof_float, input_rate)) - sink2 = waterfall_sink_f (self, panel, title="Real Data", fft_size=fft_size, + self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + self.thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size, sample_rate=input_rate, baseband_freq=100e3) - self.define_component("sink2", sink2) + self.connect(self.src2, self.thr2, sink2) vbox.Add (sink2.win, 1, wx.EXPAND) - # Ultimately this will be - # self.connect("src2 thr2 sink2") - self.connect("src2", 0, "thr2", 0) - self.connect("thr2", 0, "sink2", 0) def main (): - app = stdgui2.stdapp (test_top_block, - "Waterfall Sink Test App") + app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App") app.MainLoop () if __name__ == '__main__': -- cgit From 9880e7bb383054aa43681b52ebd33c8fd4cb8fcb Mon Sep 17 00:00:00 2001 From: jcorgan Date: Mon, 28 May 2007 17:04:09 +0000 Subject: Merged r5547:5542 from jcorgan/num into trunk. Converts from using Python Numeric to numpy. Trunk passes distcheck. gr-radio-astronomy still needs conversion. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@5553 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink.py | 18 ++-- gr-wxgui/src/python/fftsink2.py | 18 ++-- gr-wxgui/src/python/numbersink.py | 8 +- gr-wxgui/src/python/plot.py | 153 +++++++++++++++++----------------- gr-wxgui/src/python/scopesink.py | 12 +-- gr-wxgui/src/python/scopesink2.py | 10 +-- gr-wxgui/src/python/waterfallsink.py | 6 +- gr-wxgui/src/python/waterfallsink2.py | 4 +- 8 files changed, 115 insertions(+), 114 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink.py b/gr-wxgui/src/python/fftsink.py index f85ec9d1a..a1e85e7be 100755 --- a/gr-wxgui/src/python/fftsink.py +++ b/gr-wxgui/src/python/fftsink.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,7 +24,7 @@ from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import threading import math @@ -202,7 +202,7 @@ class input_watcher (threading.Thread): start = itemsize * (nitems - 1) s = s[start:start+itemsize] - complex_data = Numeric.fromstring (s, Numeric.Float32) + complex_data = numpy.fromstring (s, numpy.float32) de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de @@ -245,7 +245,7 @@ class fft_window (plot.PlotCanvas): if self.peak_vals is None: self.peak_vals = dB else: - self.peak_vals = Numeric.maximum(dB, self.peak_vals) + self.peak_vals = numpy.maximum(dB, self.peak_vals) dB = self.peak_vals x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) @@ -260,20 +260,20 @@ class fft_window (plot.PlotCanvas): units = "kHz" if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((Numeric.arrayrange (L/2) + x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points = numpy.zeros((len(x_vals), 2), numpy.float64) points[:,0] = x_vals points[:,1] = dB[0:L/2] else: # the "negative freqs" are in the second half of the array - x_vals = ((Numeric.arrayrange (-L/2, L/2) + x_vals = ((numpy.arange (-L/2, L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points = numpy.zeros((len(x_vals), 2), numpy.float64) points[:,0] = x_vals - points[:,1] = Numeric.concatenate ((dB[L/2:], dB[0:L/2])) + points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) lines = plot.PolyLine (points, colour='BLUE') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index b591add12..cb7bb8c07 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,7 +24,7 @@ from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui2 import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import threading import math @@ -209,7 +209,7 @@ class input_watcher (threading.Thread): start = itemsize * (nitems - 1) s = s[start:start+itemsize] - complex_data = Numeric.fromstring (s, Numeric.Float32) + complex_data = numpy.fromstring (s, numpy.float32) de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de @@ -252,7 +252,7 @@ class fft_window (plot.PlotCanvas): if self.peak_vals is None: self.peak_vals = dB else: - self.peak_vals = Numeric.maximum(dB, self.peak_vals) + self.peak_vals = numpy.maximum(dB, self.peak_vals) dB = self.peak_vals x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) @@ -267,20 +267,20 @@ class fft_window (plot.PlotCanvas): units = "kHz" if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((Numeric.arrayrange (L/2) + x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points = numpy.zeros((len(x_vals), 2), numpy.float64) points[:,0] = x_vals points[:,1] = dB[0:L/2] else: # the "negative freqs" are in the second half of the array - x_vals = ((Numeric.arrayrange (-L/2, L/2) + x_vals = ((numpy.arange (-L/2, L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = Numeric.zeros((len(x_vals), 2), Numeric.Float64) + points = numpy.zeros((len(x_vals), 2), numpy.float64) points[:,0] = x_vals - points[:,1] = Numeric.concatenate ((dB[L/2:], dB[0:L/2])) + points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) lines = plot.PolyLine (points, colour='BLUE') diff --git a/gr-wxgui/src/python/numbersink.py b/gr-wxgui/src/python/numbersink.py index b6319a3e8..137e78fe2 100644 --- a/gr-wxgui/src/python/numbersink.py +++ b/gr-wxgui/src/python/numbersink.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -25,7 +25,7 @@ from gnuradio.wxgui import stdgui import wx #from wx import StaticText import gnuradio.wxgui.plot as plot -import Numeric +import numpy import threading import math @@ -204,7 +204,7 @@ class input_watcher (threading.Thread): start = itemsize * (nitems - 1) s = s[start:start+itemsize] - complex_data = Numeric.fromstring (s, Numeric.Float32) + complex_data = numpy.fromstring (s, numpy.float32) de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de @@ -478,7 +478,7 @@ class number_window (plot.PlotCanvas): if self.peak_vals is None: self.peak_vals = numbers else: - self.peak_vals = Numeric.maximum(numbers, self.peak_vals) + self.peak_vals = numpy.maximum(numbers, self.peak_vals) numbers = self.peak_vals if self.numbersink.input_is_real: diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index d902d417c..837ff947b 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -6,7 +6,7 @@ # # Created: 2003/11/03 # RCS-ID: $Id$ -# Copyright: (c) 2002 +# Copyright: (c) 2002,2007 # Licence: Use as you wish. #----------------------------------------------------------------------------- # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net) @@ -34,7 +34,8 @@ # - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve) # can be in either user coords or screen coords. # -# +# May 27, 2007 Johnathan Corgan (jcorgan@corganenterprises.com) +# - Converted from numarray to numpy """ This is a simple light weight plotting module that can be used with @@ -92,20 +93,20 @@ import string as _string import time as _time import wx -# Needs Numeric or numarray +# Needs numpy or numarray try: - import Numeric as _Numeric + import numpy as _numpy except: try: - import numarray as _Numeric #if numarray is used it is renamed Numeric + import numarray as _numpy #if numarray is used it is renamed numpy except: msg= """ - This module requires the Numeric or numarray module, + This module requires the numpy or numarray module, which could not be imported. It probably is not installed (it's not part of the standard Python distribution). See the Python site (http://www.python.org) for information on downloading source or binaries.""" - raise ImportError, "Numeric or numarray not found. \n" + msg + raise ImportError, "numpy or numarray not found. \n" + msg @@ -118,7 +119,7 @@ class PolyPoints: """ def __init__(self, points, attr): - self.points = _Numeric.array(points) + self.points = _numpy.array(points) self.currentScale= (1,1) self.currentShift= (0,0) self.scaled = self.points @@ -133,11 +134,11 @@ class PolyPoints: if len(self.points) == 0: # no curves to draw # defaults to (-1,-1) and (1,1) but axis can be set in Draw - minXY= _Numeric.array([-1,-1]) - maxXY= _Numeric.array([ 1, 1]) + minXY= _numpy.array([-1,-1]) + maxXY= _numpy.array([ 1, 1]) else: - minXY= _Numeric.minimum.reduce(self.points) - maxXY= _Numeric.maximum.reduce(self.points) + minXY= _numpy.minimum.reduce(self.points) + maxXY= _numpy.maximum.reduce(self.points) return minXY, maxXY def scaleAndShift(self, scale=(1,1), shift=(0,0)): @@ -163,14 +164,14 @@ class PolyPoints: if pointScaled == True: #Using screen coords p = self.scaled - pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift + pxy = self.currentScale * _numpy.array(pntXY)+ self.currentShift else: #Using user coords p = self.points - pxy = _Numeric.array(pntXY) + pxy = _numpy.array(pntXY) #determine distance for each point - d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2) - pntIndex = _Numeric.argmin(d) + d= _numpy.sqrt(_numpy.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2) + pntIndex = _numpy.argmin(d) dist = d[pntIndex] return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist] @@ -284,9 +285,9 @@ class PolyMarker(PolyPoints): def _circle(self, dc, coords, size=1): fact= 2.5*size wh= 5.0*size - rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh] + rect= _numpy.zeros((len(coords),4),_numpy.float)+[0.0,0.0,wh,wh] rect[:,0:2]= coords-[fact,fact] - dc.DrawEllipseList(rect.astype(_Numeric.Int32)) + dc.DrawEllipseList(rect.astype(_numpy.int32)) def _dot(self, dc, coords, size=1): dc.DrawPointList(coords) @@ -294,35 +295,35 @@ class PolyMarker(PolyPoints): def _square(self, dc, coords, size=1): fact= 2.5*size wh= 5.0*size - rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh] + rect= _numpy.zeros((len(coords),4),_numpy.float)+[0.0,0.0,wh,wh] rect[:,0:2]= coords-[fact,fact] - dc.DrawRectangleList(rect.astype(_Numeric.Int32)) + dc.DrawRectangleList(rect.astype(_numpy.int32)) def _triangle(self, dc, coords, size=1): shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)] - poly= _Numeric.repeat(coords,3) + poly= _numpy.repeat(coords,3) poly.shape= (len(coords),3,2) poly += shape - dc.DrawPolygonList(poly.astype(_Numeric.Int32)) + dc.DrawPolygonList(poly.astype(_numpy.int32)) def _triangle_down(self, dc, coords, size=1): shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)] - poly= _Numeric.repeat(coords,3) + poly= _numpy.repeat(coords,3) poly.shape= (len(coords),3,2) poly += shape - dc.DrawPolygonList(poly.astype(_Numeric.Int32)) + dc.DrawPolygonList(poly.astype(_numpy.int32)) def _cross(self, dc, coords, size=1): fact= 2.5*size for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]: - lines= _Numeric.concatenate((coords,coords),axis=1)+f - dc.DrawLineList(lines.astype(_Numeric.Int32)) + lines= _numpy.concatenate((coords,coords),axis=1)+f + dc.DrawLineList(lines.astype(_numpy.int32)) def _plus(self, dc, coords, size=1): fact= 2.5*size for f in [[-fact,0,fact,0],[0,-fact,0,fact]]: - lines= _Numeric.concatenate((coords,coords),axis=1)+f - dc.DrawLineList(lines.astype(_Numeric.Int32)) + lines= _numpy.concatenate((coords,coords),axis=1)+f + dc.DrawLineList(lines.astype(_numpy.int32)) class PlotGraphics: """Container to hold PolyXXX objects and graph labels @@ -347,8 +348,8 @@ class PlotGraphics: p1, p2 = self.objects[0].boundingBox() for o in self.objects[1:]: p1o, p2o = o.boundingBox() - p1 = _Numeric.minimum(p1, p1o) - p2 = _Numeric.maximum(p2, p2o) + p1 = _numpy.minimum(p1, p1o) + p2 = _numpy.maximum(p2, p2o) return p1, p2 def scaleAndShift(self, scale=(1,1), shift=(0,0)): @@ -395,7 +396,7 @@ class PlotGraphics: symExt = self.objects[0].getSymExtent(printerScale) for o in self.objects[1:]: oSymExt = o.getSymExtent(printerScale) - symExt = _Numeric.maximum(symExt, oSymExt) + symExt = _numpy.maximum(symExt, oSymExt) return symExt def getLegendNames(self): @@ -453,8 +454,8 @@ class PlotCanvas(wx.Window): # Zooming variables self._zoomInFactor = 0.5 self._zoomOutFactor = 2 - self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner - self._zoomCorner2= _Numeric.array([0.0, 0.0]) # left mouse up corner + self._zoomCorner1= _numpy.array([0.0, 0.0]) # left mouse down corner + self._zoomCorner2= _numpy.array([0.0, 0.0]) # left mouse up corner self._zoomEnabled= False self._hasDragged= False @@ -701,13 +702,13 @@ class PlotCanvas(wx.Window): def PositionUserToScreen(self, pntXY): """Converts User position to Screen Coordinates""" - userPos= _Numeric.array(pntXY) + userPos= _numpy.array(pntXY) x,y= userPos * self._pointScale + self._pointShift return x,y def PositionScreenToUser(self, pntXY): """Converts Screen position to User Coordinates""" - screenPos= _Numeric.array(pntXY) + screenPos= _numpy.array(pntXY) x,y= (screenPos-self._pointShift)/self._pointScale return x,y @@ -812,8 +813,8 @@ class PlotCanvas(wx.Window): p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax) else: # Both axis specified in Draw - p1= _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin) - p2= _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax) + p1= _numpy.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin) + p2= _numpy.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax) self.last_draw = (graphics, xAxis, yAxis) # saves most recient values @@ -848,8 +849,8 @@ class PlotCanvas(wx.Window): lhsW= yTextExtent[0]+ yLabelWH[1] bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1] topH= yTextExtent[1]/2. + titleWH[1] - textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size - textSize_shift= _Numeric.array([lhsW, bottomH]) # shift plot area by this amount + textSize_scale= _numpy.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size + textSize_shift= _numpy.array([lhsW, bottomH]) # shift plot area by this amount # drawing title and labels text dc.SetFont(self._getFont(self._fontSizeTitle)) @@ -870,8 +871,8 @@ class PlotCanvas(wx.Window): self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt) # allow for scaling and shifting plotted points - scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1)) - shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1)) + scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _numpy.array((1,-1)) + shift = -p1*scale + self.plotbox_origin + textSize_shift * _numpy.array((1,-1)) self._pointScale= scale # make available for mouse events self._pointShift= shift self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) @@ -1006,8 +1007,8 @@ class PlotCanvas(wx.Window): self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event) self._hasDragged = False # reset flag - minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2) - maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2) + minX, minY= _numpy.minimum( self._zoomCorner1, self._zoomCorner2) + maxX, maxY= _numpy.maximum( self._zoomCorner1, self._zoomCorner2) self.last_PointLabel = None #reset pointLabel if self.last_draw != None: self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None) @@ -1065,10 +1066,10 @@ class PlotCanvas(wx.Window): (self.width,self.height) = self.GetClientSize() else: self.width, self.height= width,height - self.plotbox_size = 0.97*_Numeric.array([self.width, self.height]) + self.plotbox_size = 0.97*_numpy.array([self.width, self.height]) xo = 0.5*(self.width-self.plotbox_size[0]) yo = self.height-0.5*(self.height-self.plotbox_size[1]) - self.plotbox_origin = _Numeric.array([xo, yo]) + self.plotbox_origin = _numpy.array([xo, yo]) def _setPrinterScale(self, scale): """Used to thicken lines and increase marker size for print out.""" @@ -1111,12 +1112,12 @@ class PlotCanvas(wx.Window): if isinstance(o,PolyMarker): # draw marker with legend pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.) - o.draw(dc, self.printerScale, coord= _Numeric.array([pnt])) + o.draw(dc, self.printerScale, coord= _numpy.array([pnt])) elif isinstance(o,PolyLine): # draw line with legend pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.) pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.) - o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2])) + o.draw(dc, self.printerScale, coord= _numpy.array([pnt1,pnt2])) else: raise TypeError, "object is neither PolyMarker or PolyLine instance" # draw legend txt @@ -1148,7 +1149,7 @@ class PlotCanvas(wx.Window): txtList= graphics.getLegendNames() txtExt= dc.GetTextExtent(txtList[0]) for txt in graphics.getLegendNames()[1:]: - txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt)) + txtExt= _numpy.maximum(txtExt,dc.GetTextExtent(txt)) maxW= symExt[0]+txtExt[0] maxH= max(symExt[1],txtExt[1]) # padding .1 for lhs of legend box and space between lines @@ -1188,14 +1189,14 @@ class PlotCanvas(wx.Window): def _point2ClientCoord(self, corner1, corner2): """Converts user point coords to client screen int coords x,y,width,height""" - c1= _Numeric.array(corner1) - c2= _Numeric.array(corner2) + c1= _numpy.array(corner1) + c2= _numpy.array(corner2) # convert to screen coords pt1= c1*self._pointScale+self._pointShift pt2= c2*self._pointScale+self._pointShift # make height and width positive - pul= _Numeric.minimum(pt1,pt2) # Upper left corner - plr= _Numeric.maximum(pt1,pt2) # Lower right corner + pul= _numpy.minimum(pt1,pt2) # Upper left corner + plr= _numpy.maximum(pt1,pt2) # Lower right corner rectWidth, rectHeight= plr-pul ptx,pty= pul return ptx, pty, rectWidth, rectHeight @@ -1212,8 +1213,8 @@ class PlotCanvas(wx.Window): # if range == 0.: if abs(range) < 1e-36: return lower-0.5, upper+0.5 - log = _Numeric.log10(range) - power = _Numeric.floor(log) + log = _numpy.log10(range) + power = _numpy.floor(log) fraction = log-power if fraction <= 0.05: power = power-1 @@ -1250,11 +1251,11 @@ class PlotCanvas(wx.Window): lower, upper = p1[0],p2[0] text = 1 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths - a1 = scale*_Numeric.array([lower, y])+shift - a2 = scale*_Numeric.array([upper, y])+shift + a1 = scale*_numpy.array([lower, y])+shift + a2 = scale*_numpy.array([upper, y])+shift dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line for x, label in xticks: - pt = scale*_Numeric.array([x, y])+shift + pt = scale*_numpy.array([x, y])+shift dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units if text: dc.DrawText(label,pt[0],pt[1]) @@ -1265,11 +1266,11 @@ class PlotCanvas(wx.Window): text = 1 h = dc.GetCharHeight() for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]: - a1 = scale*_Numeric.array([x, lower])+shift - a2 = scale*_Numeric.array([x, upper])+shift + a1 = scale*_numpy.array([x, lower])+shift + a2 = scale*_numpy.array([x, upper])+shift dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) for y, label in yticks: - pt = scale*_Numeric.array([x, y])+shift + pt = scale*_numpy.array([x, y])+shift dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1]) if text: dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0], @@ -1278,13 +1279,13 @@ class PlotCanvas(wx.Window): def _ticks(self, lower, upper): ideal = (upper-lower)/7. - log = _Numeric.log10(ideal) - power = _Numeric.floor(log) + log = _numpy.log10(ideal) + power = _numpy.floor(log) fraction = log-power factor = 1. error = fraction for f, lf in self._multiples: - e = _Numeric.fabs(fraction-lf) + e = _numpy.fabs(fraction-lf) if e < error: error = e factor = f @@ -1298,7 +1299,7 @@ class PlotCanvas(wx.Window): digits = -int(power) format = '%'+`digits+2`+'.'+`digits`+'f' ticks = [] - t = -grid*_Numeric.floor(-lower/grid) + t = -grid*_numpy.floor(-lower/grid) while t <= upper: ticks.append( (t, format % (t,)) ) t = t + grid @@ -1314,7 +1315,7 @@ class PlotCanvas(wx.Window): t = t + grid return ticks - _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))] + _multiples = [(2., _numpy.log10(2.)), (5., _numpy.log10(5.))] #------------------------------------------------------------------------------- @@ -1405,19 +1406,19 @@ class PlotPrintout(wx.Printout): def _draw1Objects(): # 100 points sin function, plotted as green circles - data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200. + data1 = 2.*_numpy.pi*_numpy.arange(200)/200. data1.shape = (100, 2) - data1[:,1] = _Numeric.sin(data1[:,0]) + data1[:,1] = _numpy.sin(data1[:,0]) markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1) # 50 points cos function, plotted as red line - data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100. + data1 = 2.*_numpy.pi*_numpy.arange(100)/100. data1.shape = (50,2) - data1[:,1] = _Numeric.cos(data1[:,0]) + data1[:,1] = _numpy.cos(data1[:,0]) lines = PolyLine(data1, legend= 'Red Line', colour='red') # A few more points... - pi = _Numeric.pi + pi = _numpy.pi markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), (3.*pi/4., -1)], legend='Cross Legend', colour='blue', marker='cross') @@ -1426,19 +1427,19 @@ def _draw1Objects(): def _draw2Objects(): # 100 points sin function, plotted as green dots - data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200. + data1 = 2.*_numpy.pi*_numpy.arange(200)/200. data1.shape = (100, 2) - data1[:,1] = _Numeric.sin(data1[:,0]) + data1[:,1] = _numpy.sin(data1[:,0]) line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT) # 50 points cos function, plotted as red dot-dash - data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100. + data1 = 2.*_numpy.pi*_numpy.arange(100)/100. data1.shape = (50,2) - data1[:,1] = _Numeric.cos(data1[:,0]) + data1[:,1] = _numpy.cos(data1[:,0]) line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH) # A few more points... - pi = _Numeric.pi + pi = _numpy.pi markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6, fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH, @@ -1457,7 +1458,7 @@ def _draw3Objects(): def _draw4Objects(): # 25,000 point line - data1 = _Numeric.arange(5e5,1e6,10) + data1 = _numpy.arange(5e5,1e6,10) data1.shape = (25000, 2) line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5) diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py index 14df9b265..a7f76fecf 100755 --- a/gr-wxgui/src/python/scopesink.py +++ b/gr-wxgui/src/python/scopesink.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2006 Free Software Foundation, Inc. +# Copyright 2003,2004,2006,2007 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,7 +24,7 @@ from gnuradio import gr, gru, eng_notation from gnuradio.wxgui import stdgui import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import threading import struct @@ -215,7 +215,7 @@ class input_watcher (threading.Thread): start = ch * bytes_per_chan chan_data = s[start:start+bytes_per_chan] - rec = Numeric.fromstring (chan_data, Numeric.Float32) + rec = numpy.fromstring (chan_data, numpy.float32) records.append (rec) # print "nrecords = %d, reclen = %d" % (len (records),nsamples) @@ -485,7 +485,7 @@ class graph_window (plot.PlotCanvas): objects = [] Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) - x_vals = Ts * Numeric.arrayrange (-npoints/2, npoints/2) + x_vals = Ts * numpy.arange (-npoints/2, npoints/2) # preliminary clipping based on time axis here, instead of in graphics code time_per_window = self.info.get_time_per_div () * 10 @@ -503,7 +503,7 @@ class graph_window (plot.PlotCanvas): lb = npoints/2 - n/2 ub = npoints/2 + n/2 # points = zip (x_vals[lb:ub], r[lb:ub]) - points = Numeric.zeros ((ub-lb, 2), Numeric.Float64) + points = numpy.zeros ((ub-lb, 2), numpy.float64) points[:,0] = x_vals[lb:ub] points[:,1] = r[lb:ub] @@ -542,7 +542,7 @@ class graph_window (plot.PlotCanvas): objects = [] # points = zip (records[0], records[1]) - points = Numeric.zeros ((len(records[0]), 2), Numeric.Float32) + points = numpy.zeros ((len(records[0]), 2), numpy.float32) points[:,0] = records[0] points[:,1] = records[1] diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index aa2d76b1e..90654fd81 100755 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -24,7 +24,7 @@ from gnuradio import gr, gru, eng_notation from gnuradio.wxgui import stdgui2 import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import threading import struct @@ -216,7 +216,7 @@ class input_watcher (threading.Thread): start = ch * bytes_per_chan chan_data = s[start:start+bytes_per_chan] - rec = Numeric.fromstring (chan_data, Numeric.Float32) + rec = numpy.fromstring (chan_data, numpy.float32) records.append (rec) # print "nrecords = %d, reclen = %d" % (len (records),nsamples) @@ -486,7 +486,7 @@ class graph_window (plot.PlotCanvas): objects = [] Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) - x_vals = Ts * Numeric.arrayrange (-npoints/2, npoints/2) + x_vals = Ts * numpy.arange (-npoints/2, npoints/2) # preliminary clipping based on time axis here, instead of in graphics code time_per_window = self.info.get_time_per_div () * 10 @@ -504,7 +504,7 @@ class graph_window (plot.PlotCanvas): lb = npoints/2 - n/2 ub = npoints/2 + n/2 # points = zip (x_vals[lb:ub], r[lb:ub]) - points = Numeric.zeros ((ub-lb, 2), Numeric.Float64) + points = numpy.zeros ((ub-lb, 2), numpy.float64) points[:,0] = x_vals[lb:ub] points[:,1] = r[lb:ub] @@ -543,7 +543,7 @@ class graph_window (plot.PlotCanvas): objects = [] # points = zip (records[0], records[1]) - points = Numeric.zeros ((len(records[0]), 2), Numeric.Float32) + points = numpy.zeros ((len(records[0]), 2), numpy.float32) points[:,0] = records[0] points[:,1] = records[1] diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py index dee8d544a..9924bfc64 100755 --- a/gr-wxgui/src/python/waterfallsink.py +++ b/gr-wxgui/src/python/waterfallsink.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2007 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,7 +24,7 @@ from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import os import threading import math @@ -169,7 +169,7 @@ class input_watcher (threading.Thread): start = itemsize * (nitems - 1) s = s[start:start+itemsize] - complex_data = Numeric.fromstring (s, Numeric.Float32) + complex_data = numpy.fromstring (s, numpy.float32) de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index cd4cc75da..ea041d003 100755 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -24,7 +24,7 @@ from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui2 import wx import gnuradio.wxgui.plot as plot -import Numeric +import numpy import os import threading import math @@ -174,7 +174,7 @@ class input_watcher (threading.Thread): start = itemsize * (nitems - 1) s = s[start:start+itemsize] - complex_data = Numeric.fromstring (s, Numeric.Float32) + complex_data = numpy.fromstring (s, numpy.float32) de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de -- cgit From 937b719d2e57d0497293d603da10cac2532346f6 Mon Sep 17 00:00:00 2001 From: eb Date: Sat, 21 Jul 2007 03:44:38 +0000 Subject: Updated license from GPL version 2 or later to GPL version 3 or later. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6044 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 2 +- gr-wxgui/src/python/fftsink.py | 2 +- gr-wxgui/src/python/fftsink2.py | 2 +- gr-wxgui/src/python/form.py | 2 +- gr-wxgui/src/python/numbersink.py | 2 +- gr-wxgui/src/python/powermate.py | 2 +- gr-wxgui/src/python/scopesink.py | 2 +- gr-wxgui/src/python/scopesink2.py | 2 +- gr-wxgui/src/python/stdgui.py | 2 +- gr-wxgui/src/python/stdgui2.py | 2 +- gr-wxgui/src/python/waterfallsink.py | 2 +- gr-wxgui/src/python/waterfallsink2.py | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index ce62574d7..9316eb384 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -5,7 +5,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/fftsink.py b/gr-wxgui/src/python/fftsink.py index a1e85e7be..85e3a1f9e 100755 --- a/gr-wxgui/src/python/fftsink.py +++ b/gr-wxgui/src/python/fftsink.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index cb7bb8c07..1499a3bb3 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index ea9a8dc9a..5ca661836 100755 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/numbersink.py b/gr-wxgui/src/python/numbersink.py index 137e78fe2..5427d2a40 100644 --- a/gr-wxgui/src/python/numbersink.py +++ b/gr-wxgui/src/python/numbersink.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py index 041a2cd6b..7f54dff33 100755 --- a/gr-wxgui/src/python/powermate.py +++ b/gr-wxgui/src/python/powermate.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py index a7f76fecf..ffd4529bc 100755 --- a/gr-wxgui/src/python/scopesink.py +++ b/gr-wxgui/src/python/scopesink.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 90654fd81..39c613941 100755 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/stdgui.py b/gr-wxgui/src/python/stdgui.py index 8636ea2bf..178739547 100644 --- a/gr-wxgui/src/python/stdgui.py +++ b/gr-wxgui/src/python/stdgui.py @@ -5,7 +5,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py index 83158abd9..7b21d6b0a 100644 --- a/gr-wxgui/src/python/stdgui2.py +++ b/gr-wxgui/src/python/stdgui2.py @@ -5,7 +5,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py index 9924bfc64..4803cbeb2 100755 --- a/gr-wxgui/src/python/waterfallsink.py +++ b/gr-wxgui/src/python/waterfallsink.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index ea041d003..4b60391fb 100755 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -6,7 +6,7 @@ # # 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 2, or (at your option) +# 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, -- cgit From c088a546ac7ae55748e5421201f3387f3e1286f9 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Mon, 27 Aug 2007 18:49:11 +0000 Subject: Merged r6171:6186 from jcorgan/fg into trunk. Changes hierarchical flow graph API to use gr.top_block instead of gr.runtime. See discuss-gnuradio mailing list for explanation of changes. GRC has not been updated to use the changed API. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6187 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/stdgui2.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py index 7b21d6b0a..e510f174c 100644 --- a/gr-wxgui/src/python/stdgui2.py +++ b/gr-wxgui/src/python/stdgui2.py @@ -65,11 +65,11 @@ class stdframe (wx.Frame): vbox.Fit(self) def OnCloseWindow (self, event): - self.runtime().stop() + self.top_block().stop() self.Destroy () - def runtime (self): - return self.panel.runtime + def top_block (self): + return self.panel.top_block class stdpanel (wx.Panel): def __init__ (self, parent, frame, top_block_maker): @@ -83,8 +83,7 @@ class stdpanel (wx.Panel): self.SetAutoLayout (True) vbox.Fit (self) - self.runtime = gr.runtime(self.top_block) - self.runtime.start () + self.top_block.start () class std_top_block (gr.top_block): def __init__ (self, parent, panel, vbox, argv): -- cgit From 57cb93004961c2d5ecf2db6201dd98dfc253e7ac Mon Sep 17 00:00:00 2001 From: jcorgan Date: Wed, 19 Sep 2007 17:22:48 +0000 Subject: Added missed numbersink2.py git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6474 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 3 +- gr-wxgui/src/python/numbersink.py | 0 gr-wxgui/src/python/numbersink2.py | 510 +++++++++++++++++++++++++++++++++++++ 3 files changed, 512 insertions(+), 1 deletion(-) mode change 100644 => 100755 gr-wxgui/src/python/numbersink.py create mode 100755 gr-wxgui/src/python/numbersink2.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 9316eb384..f466db163 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -42,4 +42,5 @@ ourpython_PYTHON = \ slider.py \ stdgui.py \ stdgui2.py \ - numbersink.py + numbersink.py \ + numbersink2.py diff --git a/gr-wxgui/src/python/numbersink.py b/gr-wxgui/src/python/numbersink.py old mode 100644 new mode 100755 diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py new file mode 100755 index 000000000..1411c9fc0 --- /dev/null +++ b/gr-wxgui/src/python/numbersink2.py @@ -0,0 +1,510 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2006,2007 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., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import numpy +import threading +import math + +default_numbersink_size = (640,240) +default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15) + +class number_sink_base(object): + def __init__(self, input_is_real=False, unit='',base_value=0, minval=-100.0,maxval=100.0,factor=1.0,decimal_places=10, ref_level=50, + sample_rate=1, + number_rate=default_number_rate, + average=False, avg_alpha=None, label='', peak_hold=False): + + # initialize common attributes + self.unit=unit + self.base_value = base_value + self.minval=minval + self.maxval=maxval + self.factor=factor + self.y_divs = 8 + self.decimal_places=decimal_places + self.ref_level = ref_level + self.sample_rate = sample_rate + number_size=1 + self.number_size = number_size + self.number_rate = number_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / number_rate + else: + self.avg_alpha = avg_alpha + self.label = label + self.peak_hold = peak_hold + self.show_gauge = True + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages + + def set_decimal_places(self, decimal_places): + self.decimal_places = decimal_places + + def set_ref_level(self, ref_level): + self.ref_level = ref_level + + def print_current_value(self, comment): + print comment,self.win.current_value + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + self.set_peak_hold(False) + else: + self.avg.set_taps(1.0) + + def set_peak_hold(self, enable): + self.peak_hold = enable + if enable: + self.set_average(False) + self.win.set_peak_hold(enable) + + def set_show_gauge(self, enable): + self.show_gauge = enable + self.win.set_show_gauge(enable) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_base_value(self, base_value): + self.base_value = base_value + + +class number_sink_f(gr.hier_block2, number_sink_base): + def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, + decimal_places=10, ref_level=50, sample_rate=1, + number_rate=default_number_rate, average=False, avg_alpha=None, + label='', size=default_numbersink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "number_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(0, 0, 0)) # Output signature + + number_sink_base.__init__(self, unit=unit, input_is_real=True, base_value=base_value, + minval=minval,maxval=maxval,factor=factor, + decimal_places=decimal_places, ref_level=ref_level, + sample_rate=sample_rate, number_rate=number_rate, + average=average, avg_alpha=avg_alpha, label=label, + peak_hold=peak_hold) + + number_size=1 + one_in_n = gr.keep_one_in_n(gr.sizeof_float, + max(1, int(sample_rate/number_rate))) + + self.avg = gr.single_pole_iir_filter_ff(1.0, number_size) + sink = gr.message_sink(gr.sizeof_float , self.msgq, True) + self.connect(self, self.avg, one_in_n, sink) + + self.win = number_window(self, parent, size=size,label=label) + self.set_average(self.average) + +class number_sink_c(gr.hier_block2, number_sink_base): + def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, + decimal_places=10, ref_level=50, sample_rate=1, + number_rate=default_number_rate, average=False, avg_alpha=None, + label='', size=default_numbersink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "number_sink_c", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(0, 0, 0)) # Output signature + + number_sink_base.__init__(self, unit=unit, input_is_real=False, base_value=base_value,factor=factor, + minval=minval,maxval=maxval,decimal_places=decimal_places, ref_level=ref_level, + sample_rate=sample_rate, number_rate=number_rate, + average=average, avg_alpha=avg_alpha, label=label, + peak_hold=peak_hold) + + number_size=1 + one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex, + max(1, int(sample_rate/number_rate))) + + self.avg = gr.single_pole_iir_filter_cc(1.0, number_size) + sink = gr.message_sink(gr.sizeof_gr_complex , self.msgq, True) + self.connect(self, self.avg, one_in_n, sink) + + self.win = number_window(self, parent, size=size,label=label) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, number_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.number_size = number_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one number in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = numpy.fromstring (s, numpy.float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + +#======================================================================================== +class static_text_window (wx.StaticText): #plot.PlotCanvas): + def __init__ (self, parent, numbersink,id = -1,label="number", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + wx.StaticText.__init__(self, parent, id, label, pos, size, style, name) + self.parent=parent + self.label=label + self.numbersink = numbersink + self.peak_hold = False + self.peak_vals = None + self.build_popup_menu() + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + def on_close_window (self, event): + print "number_window:on_close_window" + self.keep_running = False + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.numbersink.ref_level + ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.numbersink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.numbersink.set_peak_hold(evt.IsChecked()) + + def on_show_gauge(self, evt): + # print "on_show_gauge" + self.numbersink.set_show_gauge(evt.IsChecked()) + print evt.IsChecked() + + def on_incr_ref_level(self, evt): + # print "on_incr_ref_level" + self.numbersink.set_ref_level(self.numbersink.ref_level + + self.numbersink.decimal_places) + + def on_decr_ref_level(self, evt): + # print "on_decr_ref_level" + self.numbersink.set_ref_level(self.numbersink.ref_level + - self.numbersink.decimal_places) + + def on_incr_decimal_places(self, evt): + # print "on_incr_decimal_places" + self.numbersink.set_decimal_places(self.numbersink.decimal_places+1) + + def on_decr_decimal_places(self, evt): + # print "on_decr_decimal_places" + self.numbersink.set_decimal_places(max(self.numbersink.decimal_places-1,0)) + + def on_decimal_places(self, evt): + # print "on_decimal_places" + Id = evt.GetId() + if Id == self.id_decimal_places_0: + self.numbersink.set_decimal_places(0) + elif Id == self.id_decimal_places_1: + self.numbersink.set_decimal_places(1) + elif Id == self.id_decimal_places_2: + self.numbersink.set_decimal_places(2) + elif Id == self.id_decimal_places_3: + self.numbersink.set_decimal_places(3) + elif Id == self.id_decimal_places_6: + self.numbersink.set_decimal_places(6) + elif Id == self.id_decimal_places_9: + self.numbersink.set_decimal_places(9) + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + def build_popup_menu(self): + self.id_show_gauge = wx.NewId() + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_decimal_places = wx.NewId() + self.id_decr_decimal_places = wx.NewId() + self.id_decimal_places_0 = wx.NewId() + self.id_decimal_places_1 = wx.NewId() + self.id_decimal_places_2 = wx.NewId() + self.id_decimal_places_3 = wx.NewId() + self.id_decimal_places_6 = wx.NewId() + self.id_decimal_places_9 = wx.NewId() + self.id_average = wx.NewId() + self.id_peak_hold = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + #self.Bind(wx.EVT_MENU, self.on_hide_gauge, id=self.id_hide_gauge) + self.Bind(wx.EVT_MENU, self.on_show_gauge, id=self.id_show_gauge) + self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.Bind(wx.EVT_MENU, self.on_incr_decimal_places, id=self.id_incr_decimal_places) + self.Bind(wx.EVT_MENU, self.on_decr_decimal_places, id=self.id_decr_decimal_places) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_0) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_1) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_2) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_3) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_6) + self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_9) + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") + menu.AppendCheckItem(self.id_show_gauge, "Show gauge") + menu.Append(self.id_incr_ref_level, "Incr Ref Level") + menu.Append(self.id_decr_ref_level, "Decr Ref Level") + menu.Append(self.id_incr_decimal_places, "Incr decimal places") + menu.Append(self.id_decr_decimal_places, "Decr decimal places") + menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + menu.AppendCheckItem(self.id_decimal_places_0, "0 decimal places") + menu.AppendCheckItem(self.id_decimal_places_1, "1 decimal places") + menu.AppendCheckItem(self.id_decimal_places_2, "2 decimal places") + menu.AppendCheckItem(self.id_decimal_places_3, "3 decimal places") + menu.AppendCheckItem(self.id_decimal_places_6, "6 decimal places") + menu.AppendCheckItem(self.id_decimal_places_9, "9 decimal places") + + self.checkmarks = { + self.id_average : lambda : self.numbersink.average, + self.id_peak_hold : lambda : self.numbersink.peak_hold, + self.id_show_gauge : lambda : self.numbersink.show_gauge, + self.id_decimal_places_0 : lambda : self.numbersink.decimal_places == 0, + self.id_decimal_places_1 : lambda : self.numbersink.decimal_places == 1, + self.id_decimal_places_2 : lambda : self.numbersink.decimal_places == 2, + self.id_decimal_places_3 : lambda : self.numbersink.decimal_places == 3, + self.id_decimal_places_6 : lambda : self.numbersink.decimal_places == 6, + self.id_decimal_places_9 : lambda : self.numbersink.decimal_places == 9, + } + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +#======================================================================================== +class number_window (plot.PlotCanvas): + def __init__ (self, numbersink, parent, id = -1,label="number", + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + self.static_text=static_text_window( self, numbersink,id, label, pos, (size[0]/2,size[1]/2), style, name) + gauge_style = wx.GA_HORIZONTAL + vbox=wx.BoxSizer(wx.VERTICAL) + vbox.Add (self.static_text, 0, wx.EXPAND) + self.current_value=None + if numbersink.input_is_real: + self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") + vbox.Add (self.gauge, 1, wx.EXPAND) + else: + self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge") + self.gauge_imag=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]*2/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge_imag") + vbox.Add (self.gauge, 1, wx.EXPAND) + vbox.Add (self.gauge_imag, 1, wx.EXPAND) + self.sizer = vbox + self.SetSizer (self.sizer) + self.SetAutoLayout (True) + self.sizer.Fit (self) + + self.label=label + self.numbersink = numbersink + self.peak_hold = False + self.peak_vals = None + + EVT_DATA_EVENT (self, self.set_data) + wx.EVT_CLOSE (self, self.on_close_window) + self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) + + def on_close_window (self, event): + # print "number_window:on_close_window" + self.keep_running = False + + def set_show_gauge(self, enable): + self.show_gauge = enable + if enable: + self.gauge.Show() + if not self.numbersink.input_is_real: + self.gauge_imag.Show() + #print 'show' + else: + self.gauge.Hide() + if not self.numbersink.input_is_real: + self.gauge_imag.Hide() + #print 'hide' + + def set_data (self, evt): + numbers = evt.data + L = len (numbers) + + if self.peak_hold: + if self.peak_vals is None: + self.peak_vals = numbers + else: + self.peak_vals = numpy.maximum(numbers, self.peak_vals) + numbers = self.peak_vals + + if self.numbersink.input_is_real: + real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value + imag_value=0.0 + self.current_value=real_value + else: + real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value + imag_value=numbers[1]*self.numbersink.factor + self.numbersink.base_value + self.current_value=complex(real_value,imag_value) + x = max(real_value, imag_value) + if x >= 1e9: + sf = 1e-9 + unit_prefix = "G" + elif x >= 1e6: + sf = 1e-6 + unit_prefix = "M" + elif x>= 1e3: + sf = 1e-3 + unit_prefix = "k" + else : + sf = 1 + unit_prefix = "" + if self.numbersink.input_is_real: + showtext = "%s: %.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,unit_prefix,self.numbersink.unit) + else: + showtext = "%s: %.*f,%.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf, + self.numbersink.decimal_places,imag_value*sf,unit_prefix,self.numbersink.unit) + self.static_text.SetLabel(showtext) + self.gauge.SetValue(int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) + if not self.numbersink.input_is_real: + self.gauge.SetValue(int(float((imag_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def update_y_range (self): + ymax = self.numbersink.ref_level + ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs + self.y_range = self._axisInterval ('min', ymin, ymax) + + def on_average(self, evt): + # print "on_average" + self.numbersink.set_average(evt.IsChecked()) + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.numbersink.set_peak_hold(evt.IsChecked()) + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_flow_graph (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + # build our flow graph + input_rate = 20.48e3 + + # Generate a real and complex sinusoids + src1 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src2 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_float, input_rate) + thr2 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = number_sink_f (panel, unit='Hz',label="Real Data", avg_alpha=0.001, + sample_rate=input_rate, base_value=100e3, + ref_level=0, decimal_places=3) + vbox.Add (sink1.win, 1, wx.EXPAND) + sink2 = number_sink_c (panel, unit='V',label="Complex Data", avg_alpha=0.001, + sample_rate=input_rate, base_value=0, + ref_level=0, decimal_places=3) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.connect (src1, thr1, sink1) + self.connect (src2, thr2, sink2) + +def main (): + app = stdgui2.stdapp (test_app_flow_graph, "Number Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () -- cgit From 568b72d1d6dd2652e21863d457df7319a9194129 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Wed, 3 Oct 2007 16:20:05 +0000 Subject: Applied patch from Josh Blum. Adds multiple input specification to constructor for scopesink2 and convenience constructor for constellation mode. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6582 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/scopesink2.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 39c613941..71fd7e128 100755 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -35,15 +35,16 @@ default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) class scope_sink_f(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None): + v_scale=default_v_scale, t_scale=None, num_inputs=1): gr.hier_block2.__init__(self, "scope_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), gr.io_signature(0,0,0)) msgq = gr.msg_queue(2) # message queue that holds at most 2 messages self.guts = gr.oscope_sink_f(sample_rate, msgq) - self.connect(self, self.guts) + for i in range(num_inputs): + self.connect((self, i), (self.guts, i)) self.win = scope_window(win_info (msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) @@ -55,18 +56,19 @@ class scope_sink_f(gr.hier_block2): class scope_sink_c(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None): + v_scale=default_v_scale, t_scale=None, num_inputs=1): gr.hier_block2.__init__(self, "scope_sink_c", - gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), gr.io_signature(0,0,0)) msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.c2f = gr.complex_to_float() self.guts = gr.oscope_sink_f(sample_rate, msgq) - self.connect(self, self.c2f) - self.connect((self.c2f, 0), (self.guts, 0)) - self.connect((self.c2f, 1), (self.guts, 1)) + for i in range(num_inputs): + c2f = gr.complex_to_float() + self.connect((self, i), c2f) + self.connect((c2f, 0), (self.guts, 2*i+0)) + self.connect((c2f, 1), (self.guts, 2*i+1)) self.win = scope_window(win_info(msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) @@ -75,6 +77,13 @@ class scope_sink_c(gr.hier_block2): self.guts.set_sample_rate(sample_rate) self.win.info.set_sample_rate(sample_rate) +class constellation_sink(scope_sink_c): + def __init__(self, parent, title='Constellation', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim): + scope_sink_c.__init__(self, parent=parent, title=title, sample_rate=sample_rate, + size=size, frame_decim=frame_decim) + self.win.info.xy = True #constellation mode + # ======================================================================== @@ -146,7 +155,7 @@ class win_info (object): 'autorange', 'running'] def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, - scopesink, title = "Oscilloscope"): + scopesink, title = "Oscilloscope", xy=False): self.msgq = msgq self.sample_rate = sample_rate self.frame_decim = frame_decim @@ -157,7 +166,7 @@ class win_info (object): self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) self.marker = 'line' - self.xy = False + self.xy = xy if v_scale == None: # 0 and None are both False, but 0 != None self.autorange = True else: -- cgit From 9e4536a76f32aeab24b072689b2950cfa19c9f34 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 8 Nov 2007 01:04:58 +0000 Subject: Adds mouse hover value display to fftsink2 git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6828 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 55 ++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 17 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 1499a3bb3..9c55801af 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -225,7 +225,7 @@ class fft_window (plot.PlotCanvas): self.fftsink = fftsink self.peak_hold = False self.peak_vals = None - + self.SetEnableGrid (True) # self.SetEnableZoom (True) # self.SetBackgroundColour ('black') @@ -235,7 +235,8 @@ class fft_window (plot.PlotCanvas): EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - + self.Bind(wx.EVT_MOTION, self.evt_motion) + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) @@ -258,36 +259,36 @@ class fft_window (plot.PlotCanvas): x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) if x >= 1e9: sf = 1e-9 - units = "GHz" + self.units = "GHz" elif x >= 1e6: sf = 1e-6 - units = "MHz" + self.units = "MHz" else: sf = 1e-3 - units = "kHz" + self.units = "kHz" if self.fftsink.input_is_real: # only plot 1/2 the points x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = numpy.zeros((len(x_vals), 2), numpy.float64) - points[:,0] = x_vals - points[:,1] = dB[0:L/2] + self.points = numpy.zeros((len(x_vals), 2), numpy.float64) + self.points[:,0] = x_vals + self.points[:,1] = dB[0:L/2] else: # the "negative freqs" are in the second half of the array x_vals = ((numpy.arange (-L/2, L/2) * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) - points = numpy.zeros((len(x_vals), 2), numpy.float64) - points[:,0] = x_vals - points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) + self.points = numpy.zeros((len(x_vals), 2), numpy.float64) + self.points[:,0] = x_vals + self.points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - lines = plot.PolyLine (points, colour='BLUE') + lines = plot.PolyLine (self.points, colour='BLUE') graphics = plot.PlotGraphics ([lines], title=self.fftsink.title, - xLabel = units, yLabel = "dB") + xLabel = self.units, yLabel = "dB") self.Draw (graphics, xAxis=None, yAxis=self.y_range) self.update_y_range () @@ -349,7 +350,28 @@ class fft_window (plot.PlotCanvas): item.Check(pred()) self.PopupMenu(menu, event.GetPosition()) - + def evt_motion(self, event): + # Clip to plotted values + (ux, uy) = self.GetXY(event) # Scaled position + x_vals = numpy.array(self.points[:,0]) + if ux < x_vals[0] or ux > x_vals[-1]: + tip = self.GetToolTip() + if tip: + tip.Enable(False) + return + + # Get nearest X value (is there a better way)? + index = numpy.argmin(numpy.abs(x_vals-ux)) + x_val = x_vals[index] + db_val = self.points[index, 1] + text = "%3.3f %s dB=%3.3f" % (x_val, self.units, db_val) + + # Display the tooltip + tip = wx.ToolTip(text) + tip.Enable(True) + tip.SetDelay(0) + self.SetToolTip(tip) + def build_popup_menu(self): self.id_incr_ref_level = wx.NewId() self.id_decr_ref_level = wx.NewId() @@ -362,7 +384,7 @@ class fft_window (plot.PlotCanvas): self.id_y_per_div_20 = wx.NewId() self.id_average = wx.NewId() self.id_peak_hold = wx.NewId() - + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) @@ -374,8 +396,7 @@ class fft_window (plot.PlotCanvas): self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - + # make a menu menu = wx.Menu() self.popup_menu = menu -- cgit From 27d33b1d2c8dccf5945d34f9daccc93cb66dab2b Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 8 Nov 2007 03:03:32 +0000 Subject: Improved frequency display format. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6830 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 9c55801af..d64c22a69 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -260,16 +260,19 @@ class fft_window (plot.PlotCanvas): if x >= 1e9: sf = 1e-9 self.units = "GHz" + self.format = "%3.6f" elif x >= 1e6: sf = 1e-6 self.units = "MHz" + self.format = "%3.3f" else: sf = 1e-3 self.units = "kHz" + self.format = "%3.3f" if self.fftsink.input_is_real: # only plot 1/2 the points x_vals = ((numpy.arange (L/2) - * (self.fftsink.sample_rate * sf / L)) + * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) self.points = numpy.zeros((len(x_vals), 2), numpy.float64) self.points[:,0] = x_vals @@ -277,7 +280,7 @@ class fft_window (plot.PlotCanvas): else: # the "negative freqs" are in the second half of the array x_vals = ((numpy.arange (-L/2, L/2) - * (self.fftsink.sample_rate * sf / L)) + * (self.fftsink.sample_rate * sf / L)) + self.fftsink.baseband_freq * sf) self.points = numpy.zeros((len(x_vals), 2), numpy.float64) self.points[:,0] = x_vals @@ -364,7 +367,7 @@ class fft_window (plot.PlotCanvas): index = numpy.argmin(numpy.abs(x_vals-ux)) x_val = x_vals[index] db_val = self.points[index, 1] - text = "%3.3f %s dB=%3.3f" % (x_val, self.units, db_val) + text = (self.format+" %s dB=%3.3f") % (x_val, self.units, db_val) # Display the tooltip tip = wx.ToolTip(text) -- cgit From 16920f6f12df9a4bbca3d9b2fa25f44145ee5513 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 8 Nov 2007 06:43:07 +0000 Subject: Cleanup and instance variable renaming. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6831 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 75 +++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 36 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index d64c22a69..52b898a5d 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -231,7 +231,8 @@ class fft_window (plot.PlotCanvas): # self.SetBackgroundColour ('black') self.build_popup_menu() - + self.set_baseband_freq(0.0) + EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) @@ -239,7 +240,25 @@ class fft_window (plot.PlotCanvas): self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + def set_scale(self, freq): + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + self._scale_factor = 1e-9 + self._units = "GHz" + self._format = "%3.6f" + elif x >= 1e6: + self._scale_factor = 1e-6 + self._units = "MHz" + self._format = "%3.3f" + else: + self._scale_factor = 1e-3 + self._units = "kHz" + self._format = "%3.3f" + def set_baseband_freq(self, baseband_freq): + self.set_scale(baseband_freq) + self.fftsink.set_baseband_freq(baseband_freq) + def on_close_window (self, event): print "fft_window:on_close_window" self.keep_running = False @@ -256,46 +275,31 @@ class fft_window (plot.PlotCanvas): self.peak_vals = numpy.maximum(dB, self.peak_vals) dB = self.peak_vals - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - sf = 1e-9 - self.units = "GHz" - self.format = "%3.6f" - elif x >= 1e6: - sf = 1e-6 - self.units = "MHz" - self.format = "%3.3f" - else: - sf = 1e-3 - self.units = "kHz" - self.format = "%3.3f" - if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((numpy.arange (L/2) - * (self.fftsink.sample_rate * sf / L)) - + self.fftsink.baseband_freq * sf) - self.points = numpy.zeros((len(x_vals), 2), numpy.float64) - self.points[:,0] = x_vals - self.points[:,1] = dB[0:L/2] + x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate + * self._scale_factor / L)) + + self.fftsink.baseband_freq * self._scale_factor) + self._points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._points[:,0] = x_vals + self._points[:,1] = dB[0:L/2] else: # the "negative freqs" are in the second half of the array x_vals = ((numpy.arange (-L/2, L/2) - * (self.fftsink.sample_rate * sf / L)) - + self.fftsink.baseband_freq * sf) - self.points = numpy.zeros((len(x_vals), 2), numpy.float64) - self.points[:,0] = x_vals - self.points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - - - lines = plot.PolyLine (self.points, colour='BLUE') + * (self.fftsink.sample_rate * self._scale_factor / L)) + + self.fftsink.baseband_freq * self._scale_factor) + self._points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._points[:,0] = x_vals + self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) + lines = plot.PolyLine (self._points, colour='BLUE') graphics = plot.PlotGraphics ([lines], title=self.fftsink.title, - xLabel = self.units, yLabel = "dB") + xLabel = self._units, yLabel = "dB") self.Draw (graphics, xAxis=None, yAxis=self.y_range) self.update_y_range () + def set_peak_hold(self, enable): self.peak_hold = enable self.peak_vals = None @@ -345,7 +349,6 @@ class fft_window (plot.PlotCanvas): elif Id == self.id_y_per_div_20: self.fftsink.set_y_per_div(20) - def on_right_click(self, event): menu = self.popup_menu for id, pred in self.checkmarks.items(): @@ -356,7 +359,7 @@ class fft_window (plot.PlotCanvas): def evt_motion(self, event): # Clip to plotted values (ux, uy) = self.GetXY(event) # Scaled position - x_vals = numpy.array(self.points[:,0]) + x_vals = numpy.array(self._points[:,0]) if ux < x_vals[0] or ux > x_vals[-1]: tip = self.GetToolTip() if tip: @@ -364,10 +367,10 @@ class fft_window (plot.PlotCanvas): return # Get nearest X value (is there a better way)? - index = numpy.argmin(numpy.abs(x_vals-ux)) - x_val = x_vals[index] - db_val = self.points[index, 1] - text = (self.format+" %s dB=%3.3f") % (x_val, self.units, db_val) + ind = numpy.argmin(numpy.abs(x_vals-ux)) + x_val = x_vals[ind] + db_val = self._points[ind, 1] + text = (self._format+" %s dB=%3.3f") % (x_val, self._units, db_val) # Display the tooltip tip = wx.ToolTip(text) -- cgit From 28edd49f3d5c1e7eb2f3f2826726bf688f037b1f Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 8 Nov 2007 20:50:47 +0000 Subject: Fix for working with peak hold in usrp_fft.py git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@6839 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 52b898a5d..8e07dda41 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -256,6 +256,8 @@ class fft_window (plot.PlotCanvas): self._format = "%3.3f" def set_baseband_freq(self, baseband_freq): + if self.peak_hold: + self.peak_vals = None self.set_scale(baseband_freq) self.fftsink.set_baseband_freq(baseband_freq) -- cgit From 05005e3d3fad3c9baee9906714510b5d12e0fa6f Mon Sep 17 00:00:00 2001 From: eb Date: Fri, 8 Feb 2008 04:36:24 +0000 Subject: Removed gr.flow_graph, gr.hier_block and friends. From here on out all work on the trunk must use gr.top_block and gr.hier_block2. Merged eb/fg-no-more -r7602:7606 into trunk. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7607 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 7 +- gr-wxgui/src/python/fftsink.py | 488 -------------------------- gr-wxgui/src/python/numbersink.py | 614 --------------------------------- gr-wxgui/src/python/scopesink.py | 650 ----------------------------------- gr-wxgui/src/python/stdgui.py | 90 ----- gr-wxgui/src/python/waterfallsink.py | 475 ------------------------- 6 files changed, 1 insertion(+), 2323 deletions(-) delete mode 100755 gr-wxgui/src/python/fftsink.py delete mode 100755 gr-wxgui/src/python/numbersink.py delete mode 100755 gr-wxgui/src/python/scopesink.py delete mode 100644 gr-wxgui/src/python/stdgui.py delete mode 100755 gr-wxgui/src/python/waterfallsink.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index f466db163..bda7c362c 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004,2005 Free Software Foundation, Inc. +# Copyright 2004,2005,2008 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -31,16 +31,11 @@ ourlibdir = $(grpyexecdir)/wxgui ourpython_PYTHON = \ __init__.py \ form.py \ - fftsink.py \ fftsink2.py \ plot.py \ powermate.py \ - scopesink.py \ scopesink2.py \ - waterfallsink.py \ waterfallsink2.py \ slider.py \ - stdgui.py \ stdgui2.py \ - numbersink.py \ numbersink2.py diff --git a/gr-wxgui/src/python/fftsink.py b/gr-wxgui/src/python/fftsink.py deleted file mode 100755 index 85e3a1f9e..000000000 --- a/gr-wxgui/src/python/fftsink.py +++ /dev/null @@ -1,488 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003,2004,2005,2006,2007 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 gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui -import wx -import gnuradio.wxgui.plot as plot -import numpy -import threading -import math - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class fft_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, ref_level=50, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', peak_hold=False): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.y_divs = 8 - self.y_per_div=y_per_div - self.ref_level = ref_level - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.title = title - self.peak_hold = peak_hold - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages - - def set_y_per_div(self, y_per_div): - self.y_per_div = y_per_div - - def set_ref_level(self, ref_level): - self.ref_level = ref_level - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - self.set_peak_hold(False) - else: - self.avg.set_taps(1.0) - - def set_peak_hold(self, enable): - self.peak_hold = enable - if enable: - self.set_average(False) - self.win.set_peak_hold(enable) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - -class fft_sink_f(gr.hier_block, fft_sink_base): - def __init__(self, fg, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): - - fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - y_per_div=y_per_div, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) - - s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - fft = gr.fft_vfc(self.fft_size, True, mywindow) - power = 0 - for tap in mywindow: - power += tap*tap - - c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - - # FIXME We need to add 3dB to all bins but the DC bin - log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) - sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - - fg.connect (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) - gr.hier_block.__init__(self, fg, s2p, sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - - -class fft_sink_c(gr.hier_block, fft_sink_base): - def __init__(self, fg, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): - - fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - y_per_div=y_per_div, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) - - s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - mywindow = window.blackmanharris(self.fft_size) - power = 0 - for tap in mywindow: - power += tap*tap - - fft = gr.fft_vcc(self.fft_size, True, mywindow) - c2mag = gr.complex_to_mag(fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, fft_size) - log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) - sink = gr.message_sink(gr.sizeof_float * fft_size, self.msgq, True) - - fg.connect(s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) - gr.hier_block.__init__(self, fg, s2p, sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.fft_size = fft_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - - -class fft_window (plot.PlotCanvas): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - - self.y_range = None - self.fftsink = fftsink - self.peak_hold = False - self.peak_vals = None - - self.SetEnableGrid (True) - # self.SetEnableZoom (True) - # self.SetBackgroundColour ('black') - - self.build_popup_menu() - - EVT_DATA_EVENT (self, self.set_data) - wx.EVT_CLOSE (self, self.on_close_window) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - - def on_close_window (self, event): - print "fft_window:on_close_window" - self.keep_running = False - - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - if self.peak_hold: - if self.peak_vals is None: - self.peak_vals = dB - else: - self.peak_vals = numpy.maximum(dB, self.peak_vals) - dB = self.peak_vals - - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - sf = 1e-9 - units = "GHz" - elif x >= 1e6: - sf = 1e-6 - units = "MHz" - else: - sf = 1e-3 - units = "kHz" - - if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((numpy.arange (L/2) - * (self.fftsink.sample_rate * sf / L)) - + self.fftsink.baseband_freq * sf) - points = numpy.zeros((len(x_vals), 2), numpy.float64) - points[:,0] = x_vals - points[:,1] = dB[0:L/2] - else: - # the "negative freqs" are in the second half of the array - x_vals = ((numpy.arange (-L/2, L/2) - * (self.fftsink.sample_rate * sf / L)) - + self.fftsink.baseband_freq * sf) - points = numpy.zeros((len(x_vals), 2), numpy.float64) - points[:,0] = x_vals - points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - - - lines = plot.PolyLine (points, colour='BLUE') - - graphics = plot.PlotGraphics ([lines], - title=self.fftsink.title, - xLabel = units, yLabel = "dB") - - self.Draw (graphics, xAxis=None, yAxis=self.y_range) - self.update_y_range () - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def update_y_range (self): - ymax = self.fftsink.ref_level - ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.fftsink.set_peak_hold(evt.IsChecked()) - - def on_incr_ref_level(self, evt): - # print "on_incr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - + self.fftsink.y_per_div) - - def on_decr_ref_level(self, evt): - # print "on_decr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - - self.fftsink.y_per_div) - - def on_incr_y_per_div(self, evt): - # print "on_incr_y_per_div" - self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, (1,2,5,10,20))) - - def on_decr_y_per_div(self, evt): - # print "on_decr_y_per_div" - self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, (1,2,5,10,20))) - - def on_y_per_div(self, evt): - # print "on_y_per_div" - Id = evt.GetId() - if Id == self.id_y_per_div_1: - self.fftsink.set_y_per_div(1) - elif Id == self.id_y_per_div_2: - self.fftsink.set_y_per_div(2) - elif Id == self.id_y_per_div_5: - self.fftsink.set_y_per_div(5) - elif Id == self.id_y_per_div_10: - self.fftsink.set_y_per_div(10) - elif Id == self.id_y_per_div_20: - self.fftsink.set_y_per_div(20) - - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - self.id_peak_hold = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") - menu.Append(self.id_incr_ref_level, "Incr Ref Level") - menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average, - self.id_peak_hold : lambda : self.fftsink.peak_hold, - self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Deprecated interfaces -# ---------------------------------------------------------------- - -# returns (block, win). -# block requires a single input stream of float -# win is a subclass of wxWindow - -def make_fft_sink_f(fg, parent, title, fft_size, input_rate, ymin = 0, ymax=50): - - block = fft_sink_f(fg, parent, title=title, fft_size=fft_size, sample_rate=input_rate, - y_per_div=(ymax - ymin)/8, ref_level=ymax) - return (block, block.win) - -# returns (block, win). -# block requires a single input stream of gr_complex -# win is a subclass of wxWindow - -def make_fft_sink_c(fg, parent, title, fft_size, input_rate, ymin=0, ymax=50): - block = fft_sink_c(fg, parent, title=title, fft_size=fft_size, sample_rate=input_rate, - y_per_div=(ymax - ymin)/8, ref_level=ymax) - return (block, block.win) - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_app_flow_graph (stdgui.gui_flow_graph): - def __init__(self, frame, panel, vbox, argv): - stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) - - fft_size = 256 - - # build our flow graph - input_rate = 20.48e3 - - # Generate a complex sinusoid - #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = fft_sink_c (self, panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20) - vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect (src1, thr1, sink1) - - #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = fft_sink_f (self, panel, title="Real Data", fft_size=fft_size*2, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20) - vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect (src2, thr2, sink2) - -def main (): - app = stdgui.stdapp (test_app_flow_graph, - "FFT Sink Test App") - app.MainLoop () - -if __name__ == '__main__': - main () diff --git a/gr-wxgui/src/python/numbersink.py b/gr-wxgui/src/python/numbersink.py deleted file mode 100755 index 5427d2a40..000000000 --- a/gr-wxgui/src/python/numbersink.py +++ /dev/null @@ -1,614 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003,2004,2005,2006,2007 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. -# - -from gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui -import wx -#from wx import StaticText -import gnuradio.wxgui.plot as plot -import numpy -import threading -import math - -default_numbersink_size = (640,240) -default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15) - -class number_sink_base(object): - def __init__(self, input_is_real=False, unit='',base_value=0, minval=-100.0,maxval=100.0,factor=1.0,decimal_places=10, ref_level=50, - sample_rate=1, - number_rate=default_number_rate, - average=False, avg_alpha=None, label='', peak_hold=False): - - # initialize common attributes - self.unit=unit - self.base_value = base_value - self.minval=minval - self.maxval=maxval - self.factor=factor - self.y_divs = 8 - self.decimal_places=decimal_places - self.ref_level = ref_level - self.sample_rate = sample_rate - number_size=1 - self.number_size = number_size - self.number_rate = number_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / number_rate - else: - self.avg_alpha = avg_alpha - self.label = label - self.peak_hold = peak_hold - self.show_gauge = True - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages - - def set_decimal_places(self, decimal_places): - self.decimal_places = decimal_places - - def set_ref_level(self, ref_level): - self.ref_level = ref_level - - def print_current_value(self, comment): - print comment,self.win.current_value - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - self.set_peak_hold(False) - else: - self.avg.set_taps(1.0) - - def set_peak_hold(self, enable): - self.peak_hold = enable - if enable: - self.set_average(False) - self.win.set_peak_hold(enable) - - def set_show_gauge(self, enable): - self.show_gauge = enable - self.win.set_show_gauge(enable) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_base_value(self, base_value): - self.base_value = base_value - - -class number_sink_f(gr.hier_block, number_sink_base): - def __init__(self, fg, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, - decimal_places=10, ref_level=50, sample_rate=1, #number_size=512, - number_rate=default_number_rate, average=False, avg_alpha=None, - label='', size=default_numbersink_size, peak_hold=False): - - number_sink_base.__init__(self, unit=unit, input_is_real=True, base_value=base_value, - minval=minval,maxval=maxval,factor=factor, - decimal_places=decimal_places, ref_level=ref_level, - sample_rate=sample_rate, #number_size=number_size, - number_rate=number_rate, - average=average, avg_alpha=avg_alpha, label=label, - peak_hold=peak_hold) - - number_size=1 - #s2p = gr.stream_to_vector(gr.sizeof_float, number_size) - one_in_n = gr.keep_one_in_n(gr.sizeof_float, - max(1, int(sample_rate/number_rate))) - - - #c2mag = gr.complex_to_mag(number_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, number_size) - - # FIXME We need to add 3dB to all bins but the DC bin - #log = gr.nlog10_ff(20, number_size, - # -20*math.log10(number_size)-10*math.log10(power/number_size)) - sink = gr.message_sink(gr.sizeof_float , self.msgq, True) - - #fg.connect (s2p, one_in_n, fft, c2mag, self.avg, log, sink) - fg.connect(self.avg,one_in_n,sink) - gr.hier_block.__init__(self, fg, self.avg, sink) - self.win = number_window(self, parent, size=size,label=label) - self.set_average(self.average) - - -class number_sink_c(gr.hier_block, number_sink_base): - def __init__(self, fg, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, - decimal_places=10, ref_level=50, sample_rate=1, #number_size=512, - number_rate=default_number_rate, average=False, avg_alpha=None, - label='', size=default_numbersink_size, peak_hold=False): - - number_sink_base.__init__(self, unit=unit, input_is_real=False, base_value=base_value,factor=factor, - minval=minval,maxval=maxval,decimal_places=decimal_places, ref_level=ref_level, - sample_rate=sample_rate, #number_size=number_size, - number_rate=number_rate, - average=average, avg_alpha=avg_alpha, label=label, - peak_hold=peak_hold) - - number_size=1 - one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex, - max(1, int(sample_rate/number_rate))) - - - #c2mag = gr.complex_to_mag(number_size) - self.avg = gr.single_pole_iir_filter_cc(1.0, number_size) - - # FIXME We need to add 3dB to all bins but the DC bin - #log = gr.nlog10_ff(20, number_size, - # -20*math.log10(number_size)-10*math.log10(power/number_size)) - sink = gr.message_sink(gr.sizeof_gr_complex , self.msgq, True) - - #fg.connect (s2p, one_in_n, fft, c2mag, self.avg, log, sink) - fg.connect(self.avg,one_in_n,sink) - gr.hier_block.__init__(self, fg, self.avg, sink) - self.win = number_window(self, parent, size=size,label=label) - self.set_average(self.average) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, number_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.number_size = number_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one number in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - -#======================================================================================== -class static_text_window (wx.StaticText): #plot.PlotCanvas): - def __init__ (self, parent, numbersink,id = -1,label="number", - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - #plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - wx.StaticText.__init__(self, parent, id, label, pos, size, style, name) - #self.static_text=wx.StaticText( parent, id, label, pos, (size[0]/2,size[1]/2), style, name) - #gauge_style = wx.GA_HORIZONTAL - #self.gauge=wx.Gauge( parent, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") - #wx.BoxSizer.__init__ (self,wx.VERTICAL) - #self.Add (self.static_text, 0, wx.EXPAND) - #self.Add (self.gauge, 1, wx.EXPAND) - self.parent=parent - self.label=label - #self.y_range = None - self.numbersink = numbersink - self.peak_hold = False - self.peak_vals = None - - #self.SetEnableGrid (True) - # self.SetEnableZoom (True) - # self.SetBackgroundColour ('black') - - self.build_popup_menu() - - #EVT_DATA_EVENT (self, self.set_data) - #wx.EVT_CLOSE (self, self.on_close_window) - #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - #self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) - - - def on_close_window (self, event): - print "number_window:on_close_window" - self.keep_running = False - - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def update_y_range (self): - ymax = self.numbersink.ref_level - ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) - - def on_average(self, evt): - # print "on_average" - self.numbersink.set_average(evt.IsChecked()) - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.numbersink.set_peak_hold(evt.IsChecked()) - - def on_show_gauge(self, evt): - # print "on_show_gauge" - #if evt.IsChecked(): - self.numbersink.set_show_gauge(evt.IsChecked()) - print evt.IsChecked() - # print "show gauge" - #else: - # self.parent.gauge.Hide() - # print "hide gauge" - - def on_incr_ref_level(self, evt): - # print "on_incr_ref_level" - self.numbersink.set_ref_level(self.numbersink.ref_level - + self.numbersink.decimal_places) - - def on_decr_ref_level(self, evt): - # print "on_decr_ref_level" - self.numbersink.set_ref_level(self.numbersink.ref_level - - self.numbersink.decimal_places) - - def on_incr_decimal_places(self, evt): - # print "on_incr_decimal_places" - self.numbersink.set_decimal_places(self.numbersink.decimal_places+1) #next_up(self.numbersink.decimal_places, (1,2,5,10,20))) - - def on_decr_decimal_places(self, evt): - # print "on_decr_decimal_places" - self.numbersink.set_decimal_places(max(self.numbersink.decimal_places-1,0)) #next_down(self.numbersink.decimal_places, (1,2,5,10,20))) - - def on_decimal_places(self, evt): - # print "on_decimal_places" - Id = evt.GetId() - if Id == self.id_decimal_places_0: - self.numbersink.set_decimal_places(0) - elif Id == self.id_decimal_places_1: - self.numbersink.set_decimal_places(1) - elif Id == self.id_decimal_places_2: - self.numbersink.set_decimal_places(2) - elif Id == self.id_decimal_places_3: - self.numbersink.set_decimal_places(3) - elif Id == self.id_decimal_places_6: - self.numbersink.set_decimal_places(6) - elif Id == self.id_decimal_places_9: - self.numbersink.set_decimal_places(9) - - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - - def build_popup_menu(self): - #self.id_hide_gauge = wx.NewId() - self.id_show_gauge = wx.NewId() - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_decimal_places = wx.NewId() - self.id_decr_decimal_places = wx.NewId() - self.id_decimal_places_0 = wx.NewId() - self.id_decimal_places_1 = wx.NewId() - self.id_decimal_places_2 = wx.NewId() - self.id_decimal_places_3 = wx.NewId() - self.id_decimal_places_6 = wx.NewId() - self.id_decimal_places_9 = wx.NewId() - self.id_average = wx.NewId() - self.id_peak_hold = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - #self.Bind(wx.EVT_MENU, self.on_hide_gauge, id=self.id_hide_gauge) - self.Bind(wx.EVT_MENU, self.on_show_gauge, id=self.id_show_gauge) - self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.Bind(wx.EVT_MENU, self.on_incr_decimal_places, id=self.id_incr_decimal_places) - self.Bind(wx.EVT_MENU, self.on_decr_decimal_places, id=self.id_decr_decimal_places) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_0) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_1) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_2) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_3) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_6) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_9) - - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") - #menu.Append(self.id_hide_gauge, "Hide gauge") - menu.AppendCheckItem(self.id_show_gauge, "Show gauge") - menu.Append(self.id_incr_ref_level, "Incr Ref Level") - menu.Append(self.id_decr_ref_level, "Decr Ref Level") - menu.Append(self.id_incr_decimal_places, "Incr decimal places") - menu.Append(self.id_decr_decimal_places, "Decr decimal places") - menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - menu.AppendCheckItem(self.id_decimal_places_0, "0 decimal places") - menu.AppendCheckItem(self.id_decimal_places_1, "1 decimal places") - menu.AppendCheckItem(self.id_decimal_places_2, "2 decimal places") - menu.AppendCheckItem(self.id_decimal_places_3, "3 decimal places") - menu.AppendCheckItem(self.id_decimal_places_6, "6 decimal places") - menu.AppendCheckItem(self.id_decimal_places_9, "9 decimal places") - - self.checkmarks = { - self.id_average : lambda : self.numbersink.average, - self.id_peak_hold : lambda : self.numbersink.peak_hold,# self.id_hide_gauge : lambda : self.numbersink.hide_gauge, - self.id_show_gauge : lambda : self.numbersink.show_gauge, - self.id_decimal_places_0 : lambda : self.numbersink.decimal_places == 0, - self.id_decimal_places_1 : lambda : self.numbersink.decimal_places == 1, - self.id_decimal_places_2 : lambda : self.numbersink.decimal_places == 2, - self.id_decimal_places_3 : lambda : self.numbersink.decimal_places == 3, - self.id_decimal_places_6 : lambda : self.numbersink.decimal_places == 6, - self.id_decimal_places_9 : lambda : self.numbersink.decimal_places == 9, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -#======================================================================================== -class number_window (plot.PlotCanvas): - def __init__ (self, numbersink, parent, id = -1,label="number", - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - #wx.StaticText.__init__(self, parent, id, label, pos, (size[0]/2,size[1]/2), style, name) - #print 'parent',parent - self.static_text=static_text_window( self, numbersink,id, label, pos, (size[0]/2,size[1]/2), style, name) - gauge_style = wx.GA_HORIZONTAL - vbox=wx.BoxSizer(wx.VERTICAL) - vbox.Add (self.static_text, 0, wx.EXPAND) - self.current_value=None - if numbersink.input_is_real: - self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") - vbox.Add (self.gauge, 1, wx.EXPAND) - else: - self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge") - #hbox=wx.BoxSizer(wx.HORIZONTAL) - self.gauge_imag=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]*2/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge_imag") - vbox.Add (self.gauge, 1, wx.EXPAND) - vbox.Add (self.gauge_imag, 1, wx.EXPAND) - #vbox.Add (hbox, 1, wx.EXPAND) - self.sizer = vbox - self.SetSizer (self.sizer) - self.SetAutoLayout (True) - self.sizer.Fit (self) - - self.label=label - #self.y_range = None - self.numbersink = numbersink - self.peak_hold = False - self.peak_vals = None - - #self.SetEnableGrid (True) - # self.SetEnableZoom (True) - # self.SetBackgroundColour ('black') - - #self.build_popup_menu() - - EVT_DATA_EVENT (self, self.set_data) - wx.EVT_CLOSE (self, self.on_close_window) - #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - #self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) - - - def on_close_window (self, event): - print "number_window:on_close_window" - self.keep_running = False - - def set_show_gauge(self, enable): - self.show_gauge = enable - if enable: - self.gauge.Show() - if not self.numbersink.input_is_real: - self.gauge_imag.Show() - #print 'show' - else: - self.gauge.Hide() - if not self.numbersink.input_is_real: - self.gauge_imag.Hide() - #print 'hide' - - def set_data (self, evt): - numbers = evt.data - L = len (numbers) - - if self.peak_hold: - if self.peak_vals is None: - self.peak_vals = numbers - else: - self.peak_vals = numpy.maximum(numbers, self.peak_vals) - numbers = self.peak_vals - - if self.numbersink.input_is_real: - real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value - imag_value=0.0 - self.current_value=real_value - else: - real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value - imag_value=numbers[1]*self.numbersink.factor + self.numbersink.base_value - self.current_value=complex(real_value,imag_value) - #x = max(abs(self.numbersink.sample_rate), abs(self.numbersink.base_value)) - x = max(real_value, imag_value) - if x >= 1e9: - sf = 1e-9 - unit_prefix = "G" - elif x >= 1e6: - sf = 1e-6 - unit_prefix = "M" - elif x>= 1e3: - sf = 1e-3 - unit_prefix = "k" - else : - sf = 1 - unit_prefix = "" - #self.update_y_range () - if self.numbersink.input_is_real: - showtext = "%s: %.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,unit_prefix,self.numbersink.unit) - else: - showtext = "%s: %.*f,%.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf, - self.numbersink.decimal_places,imag_value*sf,unit_prefix,self.numbersink.unit) - self.static_text.SetLabel(showtext) - #print (int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) - self.gauge.SetValue(int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) - if not self.numbersink.input_is_real: - self.gauge.SetValue(int(float((imag_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def update_y_range (self): - ymax = self.numbersink.ref_level - ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) - - def on_average(self, evt): - # print "on_average" - self.numbersink.set_average(evt.IsChecked()) - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.numbersink.set_peak_hold(evt.IsChecked()) - - - - - - - - - - -# ---------------------------------------------------------------- -# Deprecated interfaces -# ---------------------------------------------------------------- - -# returns (block, win). -# block requires a single input stream of float -# win is a subclass of wxWindow - -def make_number_sink_f(fg, parent, label, number_size, input_rate, ymin = 0, ymax=50): - - block = number_sink_f(fg, parent, label=label, number_size=number_size, sample_rate=input_rate, - decimal_places=(ymax - ymin)/8, ref_level=ymax) - return (block, block.win) - -# returns (block, win). -# block requires a single input stream of gr_complex -# win is a subclass of wxWindow - -def make_number_sink_c(fg, parent, label, number_size, input_rate, ymin=0, ymax=50): - block = number_sink_c(fg, parent, label=label, number_size=number_size, sample_rate=input_rate, - decimal_places=(ymax - ymin)/8, ref_level=ymax) - return (block, block.win) - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_app_flow_graph (stdgui.gui_flow_graph): - def __init__(self, frame, panel, vbox, argv): - stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) - - #number_size = 256 - - # build our flow graph - input_rate = 20.48e3 - - # Generate a complex sinusoid - src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) - - #sink1 = number_sink_c (self, panel, label="Complex Data", number_size=number_size, - # sample_rate=input_rate, base_value=100e3, - # ref_level=0, decimal_places=3) - #vbox.Add (sink1.win, 1, wx.EXPAND) - #self.connect (src1, thr1, sink1) - - src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = number_sink_f (self, panel, unit='Hz',label="Real Data", avg_alpha=0.001,#number_size=number_size*2, - sample_rate=input_rate, base_value=100e3, - ref_level=0, decimal_places=3) - vbox.Add (sink2.win, 1, wx.EXPAND) - sink3 = number_sink_c (self, panel, unit='V',label="Complex Data", avg_alpha=0.001,#number_size=number_size*2, - sample_rate=input_rate, base_value=0, - ref_level=0, decimal_places=3) - vbox.Add (sink3.win, 1, wx.EXPAND) - self.connect (src2, thr2, sink2) - self.connect (src1, thr1, sink3) -def main (): - app = stdgui.stdapp (test_app_flow_graph, - "Number Sink Test App") - app.MainLoop () - -if __name__ == '__main__': - main () diff --git a/gr-wxgui/src/python/scopesink.py b/gr-wxgui/src/python/scopesink.py deleted file mode 100755 index ffd4529bc..000000000 --- a/gr-wxgui/src/python/scopesink.py +++ /dev/null @@ -1,650 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003,2004,2006,2007 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 gnuradio import gr, gru, eng_notation -from gnuradio.wxgui import stdgui -import wx -import gnuradio.wxgui.plot as plot -import numpy -import threading -import struct - -default_scopesink_size = (640, 240) -default_v_scale = 1000 -default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) - -class scope_sink_f(gr.hier_block): - def __init__(self, fg, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None): - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.guts = gr.oscope_sink_f(sample_rate, msgq) - gr.hier_block.__init__(self, fg, self.guts, self.guts) - self.win = scope_window(win_info (msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -class scope_sink_c(gr.hier_block): - def __init__(self, fg, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None): - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - c2f = gr.complex_to_float() - self.guts = gr.oscope_sink_f(sample_rate, msgq) - fg.connect((c2f, 0), (self.guts, 0)) - fg.connect((c2f, 1), (self.guts, 1)) - gr.hier_block.__init__(self, fg, c2f, self.guts) - self.win = scope_window(win_info(msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -# ======================================================================== -# This is the deprecated interface, retained for compatibility... -# -# returns (block, win). -# block requires a N input stream of float -# win is a subclass of wxWindow - -def make_scope_sink_f (fg, parent, label, input_rate): - block = scope_sink_f(fg, parent, title=label, sample_rate=input_rate) - return (block, block.win) - -# ======================================================================== - - -time_base_list = [ # time / division - 1.0e-7, # 100ns / div - 2.5e-7, - 5.0e-7, - 1.0e-6, # 1us / div - 2.5e-6, - 5.0e-6, - 1.0e-5, # 10us / div - 2.5e-5, - 5.0e-5, - 1.0e-4, # 100us / div - 2.5e-4, - 5.0e-4, - 1.0e-3, # 1ms / div - 2.5e-3, - 5.0e-3, - 1.0e-2, # 10ms / div - 2.5e-2, - 5.0e-2 - ] - -v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER - 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div - 5.0e-3, - 1.0e-2, - 2.0e-2, - 5.0e-2, - 1.0e-1, - 2.0e-1, - 5.0e-1, - 1.0e+0, - 2.0e+0, - 5.0e+0, - 1.0e+1, - 2.0e+1, - 5.0e+1, - 1.0e+2, - 2.0e+2, - 5.0e+2, - 1.0e+3, - 2.0e+3, - 5.0e+3, - 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 - ] - - -wxDATA_EVENT = wx.NewEventType() - -def EVT_DATA_EVENT(win, func): - win.Connect(-1, -1, wxDATA_EVENT, func) - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (wxDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class win_info (object): - __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', - 'scopesink', 'title', - 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', - 'autorange', 'running'] - - def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, - scopesink, title = "Oscilloscope"): - self.msgq = msgq - self.sample_rate = sample_rate - self.frame_decim = frame_decim - self.scopesink = scopesink - self.title = title; - - self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) - self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) - - self.marker = 'line' - self.xy = False - if v_scale == None: # 0 and None are both False, but 0 != None - self.autorange = True - else: - self.autorange = False # 0 is a valid v_scale - self.running = True - - def get_time_per_div (self): - return self.time_scale_cursor.current () - - def get_volts_per_div (self): - return self.v_scale_cursor.current () - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - - def get_sample_rate (self): - return self.sample_rate - - def get_decimation_rate (self): - return 1.0 - - def set_marker (self, s): - self.marker = s - - def get_marker (self): - return self.marker - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, event_receiver, frame_decim, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.event_receiver = event_receiver - self.frame_decim = frame_decim - self.iscan = 0 - self.keep_running = True - self.start () - - def run (self): - # print "input_watcher: pid = ", os.getpid () - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - if self.iscan == 0: # only display at frame_decim - self.iscan = self.frame_decim - - 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 - - bytes_per_chan = nsamples * gr.sizeof_float - - records = [] - for ch in range (nchan): - - start = ch * bytes_per_chan - chan_data = s[start:start+bytes_per_chan] - rec = numpy.fromstring (chan_data, numpy.float32) - records.append (rec) - - # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - - de = DataEvent (records) - wx.PostEvent (self.event_receiver, de) - records = [] - del de - - # end if iscan == 0 - self.iscan -= 1 - - -class scope_window (wx.Panel): - - def __init__ (self, info, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): - wx.Panel.__init__ (self, parent, -1) - self.info = info - - vbox = wx.BoxSizer (wx.VERTICAL) - - self.graph = graph_window (info, self, -1) - - vbox.Add (self.graph, 1, wx.EXPAND) - vbox.Add (self.make_control_box(), 0, wx.EXPAND) - vbox.Add (self.make_control2_box(), 0, wx.EXPAND) - - self.sizer = vbox - self.SetSizer (self.sizer) - self.SetAutoLayout (True) - self.sizer.Fit (self) - self.set_autorange(self.info.autorange) - - - # second row of control buttons etc. appears BELOW control_box - def make_control2_box (self): - ctrlbox = wx.BoxSizer (wx.HORIZONTAL) - - self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT) - self.inc_v_button.SetToolTipString ("Increase vertical range") - wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above - - self.dec_v_button = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT) - self.dec_v_button.SetToolTipString ("Decrease vertical range") - wx.EVT_BUTTON (self, 1100, self.decr_v_scale) - - self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div - self.update_v_scale_label () - - self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange") - self.autorange_checkbox.SetToolTipString ("Select autorange on/off") - wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) - - ctrlbox.Add ((5,0) ,0) # left margin space - ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND) - ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND) - ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER) - ctrlbox.Add ((20,0) ,0) # spacer - ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER) - - return ctrlbox - - def make_control_box (self): - ctrlbox = wx.BoxSizer (wx.HORIZONTAL) - - tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT) - tb_left.SetToolTipString ("Increase time base") - wx.EVT_BUTTON (self, 1001, self.incr_timebase) - - - tb_right = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT) - tb_right.SetToolTipString ("Decrease time base") - wx.EVT_BUTTON (self, 1000, self.decr_timebase) - - self.time_base_label = wx.StaticText (self, 1002, "") - self.update_timebase_label () - - ctrlbox.Add ((5,0) ,0) - # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) - ctrlbox.Add (tb_left, 0, wx.EXPAND) - ctrlbox.Add (tb_right, 0, wx.EXPAND) - ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER) - - ctrlbox.Add ((10,0) ,1) # stretchy space - - ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER) - self.trig_chan_choice = wx.Choice (self, 1004, - choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) - self.trig_chan_choice.SetToolTipString ("Select channel for trigger") - wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event) - ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) - - self.trig_mode_choice = wx.Choice (self, 1005, - choices = ['Auto', 'Pos', 'Neg']) - self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") - wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) - ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) - - trig_level50 = wx.Button (self, 1006, "50%") - trig_level50.SetToolTipString ("Set trigger level to 50%") - wx.EVT_BUTTON (self, 1006, self.set_trig_level50) - ctrlbox.Add (trig_level50, 0, wx.EXPAND) - - run_stop = wx.Button (self, 1007, "Run/Stop") - run_stop.SetToolTipString ("Toggle Run/Stop mode") - wx.EVT_BUTTON (self, 1007, self.run_stop) - ctrlbox.Add (run_stop, 0, wx.EXPAND) - - ctrlbox.Add ((10, 0) ,1) # stretchy space - - ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) - self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices) - self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots") - wx.EVT_CHOICE (self, 1002, self.marker_choice_event) - ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER) - - self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y']) - self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display") - wx.EVT_CHOICE (self, 1003, self.xy_choice_event) - ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) - - return ctrlbox - - _marker_choices = ['line', 'plus', 'dot'] - - def update_timebase_label (self): - time_per_div = self.info.get_time_per_div () - s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' - self.time_base_label.SetLabel (s) - - def decr_timebase (self, evt): - self.info.time_scale_cursor.prev () - self.update_timebase_label () - - def incr_timebase (self, evt): - self.info.time_scale_cursor.next () - self.update_timebase_label () - - def update_v_scale_label (self): - volts_per_div = self.info.get_volts_per_div () - s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div - self.v_scale_label.SetLabel (s) - - def decr_v_scale (self, evt): - self.info.v_scale_cursor.prev () - self.update_v_scale_label () - - def incr_v_scale (self, evt): - self.info.v_scale_cursor.next () - self.update_v_scale_label () - - def marker_choice_event (self, evt): - s = evt.GetString () - self.set_marker (s) - - def set_autorange(self, on): - if on: - self.v_scale_label.SetLabel(" (auto)") - self.info.autorange = True - self.autorange_checkbox.SetValue(True) - self.inc_v_button.Enable(False) - self.dec_v_button.Enable(False) - else: - if self.graph.y_range: - (l,u) = self.graph.y_range # found by autorange - self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) - self.update_v_scale_label() - self.info.autorange = False - self.autorange_checkbox.SetValue(False) - self.inc_v_button.Enable(True) - self.dec_v_button.Enable(True) - - def autorange_checkbox_event(self, evt): - if evt.Checked(): - self.set_autorange(True) - else: - self.set_autorange(False) - - def set_marker (self, s): - self.info.set_marker (s) # set info for drawing routines - i = self.marker_choice.FindString (s) - assert i >= 0, "Hmmm, set_marker problem" - self.marker_choice.SetSelection (i) - - def set_format_line (self): - self.set_marker ('line') - - def set_format_dot (self): - self.set_marker ('dot') - - def set_format_plus (self): - self.set_marker ('plus') - - def xy_choice_event (self, evt): - s = evt.GetString () - self.info.xy = s == 'X:Y' - - def trig_chan_choice_event (self, evt): - s = evt.GetString () - ch = int (s[-1]) - 1 - self.info.scopesink.set_trigger_channel (ch) - - def trig_mode_choice_event (self, evt): - sink = self.info.scopesink - s = evt.GetString () - if s == 'Pos': - sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) - elif s == 'Neg': - sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) - elif s == 'Auto': - sink.set_trigger_mode (gr.gr_TRIG_AUTO) - else: - assert 0, "Bad trig_mode_choice string" - - def set_trig_level50 (self, evt): - self.info.scopesink.set_trigger_level_auto () - - def run_stop (self, evt): - self.info.running = not self.info.running - - -class graph_window (plot.PlotCanvas): - - channel_colors = ['BLUE', 'RED', - 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] - - def __init__ (self, info, parent, id = -1, - pos = wx.DefaultPosition, size = (640, 240), - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - - self.SetXUseScopeTicks (True) - self.SetEnableGrid (True) - self.SetEnableZoom (True) - self.SetEnableLegend(True) - # self.SetBackgroundColour ('black') - - self.info = info; - self.y_range = None - self.x_range = None - self.avg_y_min = None - self.avg_y_max = None - self.avg_x_min = None - self.avg_x_max = None - - EVT_DATA_EVENT (self, self.format_data) - - self.input_watcher = input_watcher (info.msgq, self, info.frame_decim) - - def channel_color (self, ch): - return self.channel_colors[ch % len(self.channel_colors)] - - def format_data (self, evt): - if not self.info.running: - return - - if self.info.xy: - self.format_xy_data (evt) - return - - info = self.info - records = evt.data - nchannels = len (records) - npoints = len (records[0]) - - objects = [] - - Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) - x_vals = Ts * numpy.arange (-npoints/2, npoints/2) - - # preliminary clipping based on time axis here, instead of in graphics code - time_per_window = self.info.get_time_per_div () * 10 - n = int (time_per_window / Ts + 0.5) - n = n & ~0x1 # make even - n = max (2, min (n, npoints)) - - self.SetXUseScopeTicks (True) # use 10 divisions, no labels - - for ch in range(nchannels): - r = records[ch] - - # plot middle n points of record - - lb = npoints/2 - n/2 - ub = npoints/2 + n/2 - # points = zip (x_vals[lb:ub], r[lb:ub]) - points = numpy.zeros ((ub-lb, 2), numpy.float64) - points[:,0] = x_vals[lb:ub] - points[:,1] = r[lb:ub] - - m = info.get_marker () - if m == 'line': - objects.append (plot.PolyLine (points, - colour=self.channel_color (ch), - legend=('Ch%d' % (ch+1,)))) - else: - objects.append (plot.PolyMarker (points, - marker=m, - colour=self.channel_color (ch), - legend=('Ch%d' % (ch+1,)))) - - graphics = plot.PlotGraphics (objects, - title=self.info.title, - xLabel = '', yLabel = '') - - time_per_div = info.get_time_per_div () - x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! - volts_per_div = info.get_volts_per_div () - if not self.info.autorange: - self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) - self.Draw (graphics, xAxis=x_range, yAxis=self.y_range) - self.update_y_range () # autorange to self.y_range - - - def format_xy_data (self, evt): - info = self.info - records = evt.data - nchannels = len (records) - npoints = len (records[0]) - - if nchannels < 2: - return - - objects = [] - # points = zip (records[0], records[1]) - points = numpy.zeros ((len(records[0]), 2), numpy.float32) - points[:,0] = records[0] - points[:,1] = records[1] - - self.SetXUseScopeTicks (False) - - m = info.get_marker () - if m == 'line': - objects.append (plot.PolyLine (points, - colour=self.channel_color (0))) - else: - objects.append (plot.PolyMarker (points, - marker=m, - colour=self.channel_color (0))) - - graphics = plot.PlotGraphics (objects, - title=self.info.title, - xLabel = 'I', yLabel = 'Q') - - self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) - self.update_y_range () - self.update_x_range () - - - def update_y_range (self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox () # min, max points of graphics - - if self.avg_y_min: # prevent vertical scale from jumping abruptly --? - self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) - self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) - else: # initial guess - self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 - self.avg_y_max = p2[1] # 500.0 - - self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) - # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ - # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) - - - def update_x_range (self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox () # min, max points of graphics - - if self.avg_x_min: - self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) - self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) - else: - self.avg_x_min = p1[0] - self.avg_x_max = p2[0] - - self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max) - - -# ---------------------------------------------------------------- -# Stand-alone test application -# ---------------------------------------------------------------- - -class test_app_flow_graph (stdgui.gui_flow_graph): - def __init__(self, frame, panel, vbox, argv): - stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) - - if len(argv) > 1: - frame_decim = int(argv[1]) - else: - frame_decim = 1 - - if len(argv) > 2: - v_scale = float(argv[2]) # start up at this v_scale value - else: - v_scale = None # start up in autorange mode, default - - if len(argv) > 3: - t_scale = float(argv[3]) # start up at this t_scale value - else: - t_scale = None # old behavior - - print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) - - input_rate = 1e6 - - # Generate a complex sinusoid - src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) - - # We add this throttle block so that this demo doesn't suck down - # all the CPU available. You normally wouldn't use it... - throttle = gr.throttle(gr.sizeof_gr_complex, input_rate) - - scope = scope_sink_c (self, panel,"Secret Data",sample_rate=input_rate, - frame_decim=frame_decim, - v_scale=v_scale, t_scale=t_scale) - vbox.Add (scope.win, 1, wx.EXPAND) - - # wire the blocks together - self.connect (src0, throttle, scope) - -def main (): - app = stdgui.stdapp (test_app_flow_graph, "O'Scope Test App") - app.MainLoop () - -if __name__ == '__main__': - main () - -# ---------------------------------------------------------------- diff --git a/gr-wxgui/src/python/stdgui.py b/gr-wxgui/src/python/stdgui.py deleted file mode 100644 index 178739547..000000000 --- a/gr-wxgui/src/python/stdgui.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Copyright 2004 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. -# - -'''A simple wx gui for GNU Radio applications''' - -import wx -import sys -from gnuradio import gr - - -class stdapp (wx.App): - def __init__ (self, flow_graph_maker, title="GNU Radio", nstatus=2): - self.flow_graph_maker = flow_graph_maker - self.title = title - self._nstatus = nstatus - # All our initialization must come before calling wx.App.__init__. - # OnInit is called from somewhere in the guts of __init__. - wx.App.__init__ (self, redirect=False) - - def OnInit (self): - frame = stdframe (self.flow_graph_maker, self.title, self._nstatus) - frame.Show (True) - self.SetTopWindow (frame) - return True - - -class stdframe (wx.Frame): - def __init__ (self, flow_graph_maker, title="GNU Radio", nstatus=2): - # print "stdframe.__init__" - wx.Frame.__init__(self, None, -1, title) - - self.CreateStatusBar (nstatus) - mainmenu = wx.MenuBar () - - menu = wx.Menu () - item = menu.Append (200, 'E&xit', 'Exit') - self.Bind (wx.EVT_MENU, self.OnCloseWindow, item) - mainmenu.Append (menu, "&File") - self.SetMenuBar (mainmenu) - - self.Bind (wx.EVT_CLOSE, self.OnCloseWindow) - self.panel = stdpanel (self, self, flow_graph_maker) - vbox = wx.BoxSizer(wx.VERTICAL) - vbox.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(vbox) - self.SetAutoLayout(True) - vbox.Fit(self) - - def OnCloseWindow (self, event): - self.flow_graph().stop() - self.Destroy () - - def flow_graph (self): - return self.panel.fg - -class stdpanel (wx.Panel): - def __init__ (self, parent, frame, flow_graph_maker): - # print "stdpanel.__init__" - wx.Panel.__init__ (self, parent, -1) - self.frame = frame - - vbox = wx.BoxSizer (wx.VERTICAL) - self.fg = flow_graph_maker (frame, self, vbox, sys.argv) - self.SetSizer (vbox) - self.SetAutoLayout (True) - vbox.Fit (self) - - self.fg.start () - -class gui_flow_graph (gr.flow_graph): - def __init__ (self, *ignore): - gr.flow_graph.__init__ (self) diff --git a/gr-wxgui/src/python/waterfallsink.py b/gr-wxgui/src/python/waterfallsink.py deleted file mode 100755 index 4803cbeb2..000000000 --- a/gr-wxgui/src/python/waterfallsink.py +++ /dev/null @@ -1,475 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2003,2004,2005,2007 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 gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui -import wx -import gnuradio.wxgui.plot as plot -import numpy -import os -import threading -import math - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class waterfall_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title=''): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.title = title - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue up to 2 messages - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - else: - self.avg.set_taps(1.0) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - -class waterfall_sink_f(gr.hier_block, waterfall_sink_base): - def __init__(self, fg, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): - - waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - mywindow = window.blackmanharris(self.fft_size) - fft = gr.fft_vfc(self.fft_size, True, mywindow) - c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - - fg.connect (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) - gr.hier_block.__init__(self, fg, s2p, sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -class waterfall_sink_c(gr.hier_block, waterfall_sink_base): - def __init__(self, fg, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): - - waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - fft = gr.fft_vcc(self.fft_size, True, mywindow) - c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - - fg.connect(s2p, self.one_in_n, fft, c2mag, self.avg, log, sink) - gr.hier_block.__init__(self, fg, s2p, sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.fft_size = fft_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - - -class waterfall_window (wx.Panel): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - wx.Panel.__init__(self, parent, id, pos, size, style, name) - - self.fftsink = fftsink - self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) - - self.scale_factor = 5.0 # FIXME should autoscale, or set this - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Clear() - - self.pens = self.make_pens() - - wx.EVT_PAINT( self, self.OnPaint ) - wx.EVT_CLOSE (self, self.on_close_window) - EVT_DATA_EVENT (self, self.set_data) - - self.build_popup_menu() - - wx.EVT_CLOSE (self, self.on_close_window) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - - def on_close_window (self, event): - print "waterfall_window: on_close_window" - self.keep_running = False - - def const_list(self,const,len): - return [const] * len - - def make_colormap(self): - r = [] - r.extend(self.const_list(0,96)) - r.extend(range(0,255,4)) - r.extend(self.const_list(255,64)) - r.extend(range(255,128,-4)) - - g = [] - g.extend(self.const_list(0,32)) - g.extend(range(0,255,4)) - g.extend(self.const_list(255,64)) - g.extend(range(255,0,-4)) - g.extend(self.const_list(0,32)) - - b = range(128,255,4) - b.extend(self.const_list(255,64)) - b.extend(range(255,0,-4)) - b.extend(self.const_list(0,96)) - return (r,g,b) - - def make_pens(self): - (r,g,b) = self.make_colormap() - pens = [] - for i in range(0,256): - colour = wx.Colour(r[i], g[i], b[i]) - pens.append( wx.Pen(colour, 2, wx.SOLID)) - return pens - - def OnPaint(self, event): - dc = wx.PaintDC(self) - self.DoDrawing(dc) - - def DoDrawing(self, dc=None): - if dc is None: - dc = wx.ClientDC(self) - dc.DrawBitmap(self.bm, 0, 0, False ) - - - def const_list(self,const,len): - a = [const] - for i in range(1,len): - a.append(const) - return a - - def make_colormap(self): - r = [] - r.extend(self.const_list(0,96)) - r.extend(range(0,255,4)) - r.extend(self.const_list(255,64)) - r.extend(range(255,128,-4)) - - g = [] - g.extend(self.const_list(0,32)) - g.extend(range(0,255,4)) - g.extend(self.const_list(255,64)) - g.extend(range(255,0,-4)) - g.extend(self.const_list(0,32)) - - b = range(128,255,4) - b.extend(self.const_list(255,64)) - b.extend(range(255,0,-4)) - b.extend(self.const_list(0,96)) - return (r,g,b) - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) - - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - sf = 1e-9 - units = "GHz" - elif x >= 1e6: - sf = 1e-6 - units = "MHz" - else: - sf = 1e-3 - units = "kHz" - - - if self.fftsink.input_is_real: # only plot 1/2 the points - d_max = L/2 - p_width = 2 - else: - d_max = L/2 - p_width = 1 - - scale_factor = self.scale_factor - if self.fftsink.input_is_real: # real fft - for x_pos in range(0, d_max): - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) - else: # complex fft - for x_pos in range(0, d_max): # positive freqs - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 1) - for x_pos in range(0 , d_max): # negative freqs - value = int(dB[x_pos+d_max] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) - - self.DoDrawing (None) - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - # menu.Append(self.id_incr_ref_level, "Incr Ref Level") - # menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - # menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average - #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Deprecated interfaces -# ---------------------------------------------------------------- - -# returns (block, win). -# block requires a single input stream of float -# win is a subclass of wxWindow - -def make_waterfall_sink_f(fg, parent, title, fft_size, input_rate): - - block = waterfall_sink_f(fg, parent, title=title, fft_size=fft_size, - sample_rate=input_rate) - return (block, block.win) - -# returns (block, win). -# block requires a single input stream of gr_complex -# win is a subclass of wxWindow - -def make_waterfall_sink_c(fg, parent, title, fft_size, input_rate): - block = waterfall_sink_c(fg, parent, title=title, fft_size=fft_size, - sample_rate=input_rate) - return (block, block.win) - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_app_flow_graph (stdgui.gui_flow_graph): - def __init__(self, frame, panel, vbox, argv): - stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) - - fft_size = 512 - - # build our flow graph - input_rate = 20.000e3 - - # Generate a complex sinusoid - src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) - #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) - - sink1 = waterfall_sink_c (self, panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect (src1, thr1, sink1) - - # generate a real sinusoid - src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) - #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) - thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = waterfall_sink_f (self, panel, title="Real Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect (src2, thr2, sink2) - -def main (): - app = stdgui.stdapp (test_app_flow_graph, - "Waterfall Sink Test App") - app.MainLoop () - -if __name__ == '__main__': - main () -- cgit From 6bf2c048d94e1c673c06c058b23213cd28292520 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Fri, 8 Feb 2008 18:45:33 +0000 Subject: Fixed missed conversion that prevented gr.wxgui from importing correctly. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7610 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/form.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index 5ca661836..3e303554d 100755 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -338,11 +338,11 @@ class form(dict): # ---------------------------------------------------------------- import sys -from gnuradio.wxgui import stdgui +from gnuradio.wxgui import stdgui2 -class demo_app_flow_graph (stdgui.gui_flow_graph): +class demo_app_flow_graph (stdgui2.std_top_block): def __init__(self, frame, panel, vbox, argv): - stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv) + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) self.frame = frame self.panel = panel @@ -384,7 +384,7 @@ class demo_app_flow_graph (stdgui.gui_flow_graph): def main (): - app = stdgui.stdapp (demo_app_flow_graph, "wxgui form demo", nstatus=1) + app = stdgui2.stdapp(demo_app_flow_graph, "wxgui form demo", nstatus=1) app.MainLoop () if __name__ == '__main__': -- cgit From 242ce170242d39a2454ae14b7124f7a48a8a9835 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 14 Feb 2008 00:07:07 +0000 Subject: Allow variable number of vertical divisions on FFT sinks git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7666 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 8e07dda41..70ec002e9 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -32,15 +32,16 @@ default_fftsink_size = (640,240) default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) class fft_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, ref_level=50, + def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, + y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', peak_hold=False): # initialize common attributes self.baseband_freq = baseband_freq - self.y_divs = 8 self.y_per_div=y_per_div + self.y_divs = y_divs self.ref_level = ref_level self.sample_rate = sample_rate self.fft_size = fft_size @@ -91,7 +92,7 @@ class fft_sink_base(object): class fft_sink_f(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -100,7 +101,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): gr.io_signature(0,0,0)) fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - y_per_div=y_per_div, ref_level=ref_level, + y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, @@ -131,7 +132,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): class fft_sink_c(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -140,7 +141,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): gr.io_signature(0,0,0)) fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - y_per_div=y_per_div, ref_level=ref_level, + y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, -- cgit From c6bb9986f005bbc2da298c637cee6748bb377ae8 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sun, 24 Feb 2008 01:42:50 +0000 Subject: Fixed waterfallsink2 display with wxGTK-2.8, remove duplicated code. Thanks to Tobias Gresch. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7801 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/waterfallsink2.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index 4b60391fb..1c5576e1a 100755 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2007 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2007,2008 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -260,25 +260,6 @@ class waterfall_window (wx.Panel): a.append(const) return a - def make_colormap(self): - r = [] - r.extend(self.const_list(0,96)) - r.extend(range(0,255,4)) - r.extend(self.const_list(255,64)) - r.extend(range(255,128,-4)) - - g = [] - g.extend(self.const_list(0,32)) - g.extend(range(0,255,4)) - g.extend(self.const_list(255,64)) - g.extend(range(255,0,-4)) - g.extend(self.const_list(0,32)) - - b = range(128,255,4) - b.extend(self.const_list(255,64)) - b.extend(range(255,0,-4)) - b.extend(self.const_list(0,96)) - return (r,g,b) def set_data (self, evt): dB = evt.data @@ -313,18 +294,18 @@ class waterfall_window (wx.Panel): value = int(dB[x_pos] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) else: # complex fft for x_pos in range(0, d_max): # positive freqs value = int(dB[x_pos] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 1) + dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) for x_pos in range(0 , d_max): # negative freqs value = int(dB[x_pos+d_max] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 1) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) self.DoDrawing (None) -- cgit From 2e61368208de7fec418dbd539e4feed45f31fc2b Mon Sep 17 00:00:00 2001 From: jcorgan Date: Tue, 26 Feb 2008 19:04:26 +0000 Subject: Fix bitmap update on Win32 (Don Ward). git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7840 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/waterfallsink2.py | 1 + 1 file changed, 1 insertion(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index 1c5576e1a..fb91d26e0 100755 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -307,6 +307,7 @@ class waterfall_window (wx.Panel): dc1.SetPen(self.pens[value]) dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) + del dc1 self.DoDrawing (None) def on_average(self, evt): -- cgit From 5ede0e2b1457d7c28ad29c5f2322305621a3b26a Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 28 Feb 2008 19:03:55 +0000 Subject: Added reference scaling to fftsink2. Default behavior is unchanged. The new parameter 'ref_scale', defaulting to 1.0, represents a 0 dB y-axis value. Updated usrp_fft.py to display dBFS by setting ref_scale to 32768.0 and ref_level to 0. This results in the full 100 dB of dynamic range being displayed. Updated some gnuradio-examples to use the new parameter. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7863 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 70ec002e9..b9e1a47f3 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -91,7 +91,7 @@ class fft_sink_base(object): class fft_sink_f(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, + def __init__(self, parent, baseband_freq=0, ref_scale=1.0, y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -122,7 +122,10 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + -20*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale)) # Adjust for reference scale + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) @@ -131,7 +134,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): class fft_sink_c(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, + def __init__(self, parent, baseband_freq=0, ref_scale=1.0, y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -162,7 +165,10 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) + -20*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale)) # Adjust for reference scale + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) -- cgit From c7da4a90c594001596ab869cfa4bfb2479c9ac6d Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 28 Feb 2008 23:14:07 +0000 Subject: Adjust ref-scale to match observed amplitude on oscope. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7871 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index b9e1a47f3..fbddd71cf 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -91,7 +91,7 @@ class fft_sink_base(object): class fft_sink_f(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, ref_scale=1.0, + 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -124,7 +124,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale)) # Adjust for reference scale + -20*math.log10(ref_scale/2)) # Adjust for reference scale self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) @@ -134,7 +134,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): class fft_sink_c(gr.hier_block2, fft_sink_base): - def __init__(self, parent, baseband_freq=0, ref_scale=1.0, + 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, peak_hold=False): @@ -167,7 +167,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale)) # Adjust for reference scale + -20*math.log10(ref_scale/2)) # Adjust for reference scale self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) -- cgit From 0388ecb65c788413c9d80e7d6a5622ecc280bd71 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Wed, 12 Mar 2008 17:52:38 +0000 Subject: Fix incorrect FFT size compensation in fftsink2 git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7997 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index fbddd71cf..3bfb8afbd 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -122,7 +122,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale @@ -165,7 +165,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -20*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale -- cgit From 7916dd86c41bdc9cf7362dbe8f20858b8b3f9a2f Mon Sep 17 00:00:00 2001 From: jcorgan Date: Mon, 24 Mar 2008 15:42:24 +0000 Subject: Add missing peak hold initialization (Josh Blum) git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8095 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/numbersink2.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 1411c9fc0..63bfe25c9 100755 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2006,2007,2008 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -122,7 +122,8 @@ class number_sink_f(gr.hier_block2, number_sink_base): self.win = number_window(self, parent, size=size,label=label) self.set_average(self.average) - + self.set_peak_hold(self.peak_hold) + class number_sink_c(gr.hier_block2, number_sink_base): def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, decimal_places=10, ref_level=50, sample_rate=1, @@ -149,6 +150,7 @@ class number_sink_c(gr.hier_block2, number_sink_base): self.win = number_window(self, parent, size=size,label=label) self.set_average(self.average) + self.set_peak_hold(self.peak_hold) # ------------------------------------------------------------------------ -- cgit From 1959159927243a585342d1be5e7b70d56ab2984b Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 24 Apr 2008 23:02:55 +0000 Subject: Changed fftsink2 peak hold to be concurrent with live trace. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8274 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 3bfb8afbd..9945293ec 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -282,7 +282,6 @@ class fft_window (plot.PlotCanvas): self.peak_vals = dB else: self.peak_vals = numpy.maximum(dB, self.peak_vals) - dB = self.peak_vals if self.fftsink.input_is_real: # only plot 1/2 the points x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate @@ -291,6 +290,10 @@ class fft_window (plot.PlotCanvas): self._points = numpy.zeros((len(x_vals), 2), numpy.float64) self._points[:,0] = x_vals self._points[:,1] = dB[0:L/2] + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = self.peak_vals[0:L/2] else: # the "negative freqs" are in the second half of the array x_vals = ((numpy.arange (-L/2, L/2) @@ -299,9 +302,16 @@ class fft_window (plot.PlotCanvas): self._points = numpy.zeros((len(x_vals), 2), numpy.float64) self._points[:,0] = x_vals self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) - lines = plot.PolyLine (self._points, colour='BLUE') - graphics = plot.PlotGraphics ([lines], + lines = [plot.PolyLine (self._points, colour='BLUE'),] + if self.peak_hold: + lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) + + graphics = plot.PlotGraphics (lines, title=self.fftsink.title, xLabel = self._units, yLabel = "dB") -- cgit From 5eefec8efb22a15bb6f127f83214fdf11a626e06 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 24 Apr 2008 23:24:04 +0000 Subject: Make fftsink2 average and peak hold modes independent. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8275 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 9945293ec..c3df18d25 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -66,14 +66,12 @@ class fft_sink_base(object): self.average = average if average: self.avg.set_taps(self.avg_alpha) - self.set_peak_hold(False) else: self.avg.set_taps(1.0) - + self.win.peak_vals = None + def set_peak_hold(self, enable): self.peak_hold = enable - if enable: - self.set_average(False) self.win.set_peak_hold(enable) def set_avg_alpha(self, avg_alpha): -- cgit From d8e2641775a0ceb4fa388c59968a75509250d923 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sat, 26 Apr 2008 03:26:58 +0000 Subject: Fix race on startup in fftsink2 (Josh Blum) git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8279 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index c3df18d25..61906bfa1 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -374,7 +374,10 @@ class fft_window (plot.PlotCanvas): self.PopupMenu(menu, event.GetPosition()) def evt_motion(self, event): - # Clip to plotted values + if not hasattr(self, "_points"): + return # Got here before first window data update + + # Clip to plotted values (ux, uy) = self.GetXY(event) # Scaled position x_vals = numpy.array(self._points[:,0]) if ux < x_vals[0] or ux > x_vals[-1]: -- cgit From 77284eb970036e6e71ef984133b5a5839d2a157c Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 12 Jun 2008 00:29:56 +0000 Subject: fft sink gui with control panel git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8581 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 196 +++++++++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 64 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 61906bfa1..276406f9c 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -68,8 +68,8 @@ class fft_sink_base(object): self.avg.set_taps(self.avg_alpha) else: self.avg.set_taps(1.0) - self.win.peak_vals = None - + self.win.peak_vals = None + def set_peak_hold(self, enable): self.peak_hold = enable self.win.set_peak_hold(enable) @@ -120,16 +120,16 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - + -10*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale/2)) # Adjust for reference scale + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) self.win = fft_window(self, parent, size=size) self.set_average(self.average) - + self.set_peak_hold(self.peak_hold) class fft_sink_c(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, ref_scale=2.0, @@ -163,15 +163,16 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - + -10*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale/2)) # Adjust for reference scale + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) self.win = fft_window(self, parent, size=size) self.set_average(self.average) + self.set_peak_hold(self.peak_hold) # ------------------------------------------------------------------------ @@ -218,54 +219,121 @@ class input_watcher (threading.Thread): de = DataEvent (complex_data) wx.PostEvent (self.event_receiver, de) del de + +class LabelText(wx.StaticText): + """Label text class for uniform labels among all controls.""" + def __init__(self, window, label): + wx.StaticText.__init__(self, window, -1, str(label)) + font = self.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + font.SetUnderlined(True) + self.SetFont(font) + +DIV_LEVELS = (1, 2, 5, 10, 20) + +class control_panel(wx.Panel): + def __init__(self, parent): + self.parent = parent + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + control_box = wx.BoxSizer(wx.VERTICAL) + + #checkboxes for average and peak hold + control_box.Add(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.SetValue(parent.fftsink.average) + self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.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.SetValue(parent.fftsink.peak_hold) + self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) + control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + + #radio buttons for div size + control_box.Add(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.SetValue(parent.fftsink.y_per_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.Add(LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) + button_box = wx.BoxSizer(wx.HORIZONTAL) + self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) + self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) + button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER) + self.ref_minus_button = wx.Button(self, -1, ' - ', style=wx.BU_EXACTFIT) + self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level) + button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER) + control_box.Add(button_box, 0, wx.ALIGN_CENTER) + #set sizer + self.SetSizerAndFit(control_box) + + def on_radio_button_change(self, evt): + selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] + index = self.radio_buttons.index(selected_radio_button) + self.parent.fftsink.set_y_per_div(DIV_LEVELS[index]) -class fft_window (plot.PlotCanvas): +class fft_window (wx.Panel): def __init__ (self, fftsink, parent, id = -1, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - - self.y_range = None + self.fftsink = fftsink + #init panel and plot + wx.Panel.__init__(self, parent, -1) + self.plot = plot.PlotCanvas(self, id, pos, size, style, name) + #setup the box with plot and controls + self.control_panel = control_panel(self) + main_box = wx.BoxSizer (wx.HORIZONTAL) + main_box.Add (self.plot, 1, wx.EXPAND) + main_box.Add (self.control_panel, 0, wx.EXPAND) + self.SetSizerAndFit(main_box) + + self.y_range = None self.peak_hold = False self.peak_vals = None - self.SetEnableGrid (True) + self.plot.SetEnableGrid (True) # self.SetEnableZoom (True) # self.SetBackgroundColour ('black') self.build_popup_menu() - self.set_baseband_freq(0.0) - + self.set_baseband_freq(0.0) + EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - self.Bind(wx.EVT_MOTION, self.evt_motion) - + self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + self.plot.Bind(wx.EVT_MOTION, self.evt_motion) + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) def set_scale(self, freq): - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) if x >= 1e9: self._scale_factor = 1e-9 self._units = "GHz" - self._format = "%3.6f" + self._format = "%3.6f" elif x >= 1e6: self._scale_factor = 1e-6 self._units = "MHz" - self._format = "%3.3f" + self._format = "%3.3f" else: self._scale_factor = 1e-3 self._units = "kHz" - self._format = "%3.3f" + self._format = "%3.3f" def set_baseband_freq(self, baseband_freq): - if self.peak_hold: - self.peak_vals = None - self.set_scale(baseband_freq) - self.fftsink.set_baseband_freq(baseband_freq) - + if self.peak_hold: + self.peak_vals = None + self.set_scale(baseband_freq) + self.fftsink.set_baseband_freq(baseband_freq) + def on_close_window (self, event): print "fft_window:on_close_window" self.keep_running = False @@ -283,15 +351,15 @@ class fft_window (plot.PlotCanvas): if self.fftsink.input_is_real: # only plot 1/2 the points x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate - * self._scale_factor / L)) + * self._scale_factor / L)) + self.fftsink.baseband_freq * self._scale_factor) self._points = numpy.zeros((len(x_vals), 2), numpy.float64) self._points[:,0] = x_vals self._points[:,1] = dB[0:L/2] - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = self.peak_vals[0:L/2] + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = self.peak_vals[0:L/2] else: # the "negative freqs" are in the second half of the array x_vals = ((numpy.arange (-L/2, L/2) @@ -300,20 +368,20 @@ class fft_window (plot.PlotCanvas): self._points = numpy.zeros((len(x_vals), 2), numpy.float64) self._points[:,0] = x_vals self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) lines = [plot.PolyLine (self._points, colour='BLUE'),] - if self.peak_hold: - lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) + if self.peak_hold: + lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) graphics = plot.PlotGraphics (lines, title=self.fftsink.title, xLabel = self._units, yLabel = "dB") - self.Draw (graphics, xAxis=None, yAxis=self.y_range) + self.plot.Draw (graphics, xAxis=None, yAxis=self.y_range) self.update_y_range () @@ -324,7 +392,7 @@ class fft_window (plot.PlotCanvas): def update_y_range (self): ymax = self.fftsink.ref_level ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) + self.y_range = self.plot._axisInterval ('min', ymin, ymax) def on_average(self, evt): # print "on_average" @@ -371,14 +439,14 @@ class fft_window (plot.PlotCanvas): for id, pred in self.checkmarks.items(): item = menu.FindItemById(id) item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) + self.plot.PopupMenu(menu, event.GetPosition()) def evt_motion(self, event): if not hasattr(self, "_points"): - return # Got here before first window data update - - # Clip to plotted values - (ux, uy) = self.GetXY(event) # Scaled position + return # Got here before first window data update + + # Clip to plotted values + (ux, uy) = self.plot.GetXY(event) # Scaled position x_vals = numpy.array(self._points[:,0]) if ux < x_vals[0] or ux > x_vals[-1]: tip = self.GetToolTip() @@ -410,18 +478,18 @@ class fft_window (plot.PlotCanvas): self.id_y_per_div_20 = wx.NewId() self.id_average = wx.NewId() self.id_peak_hold = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.plot.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + self.plot.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) # make a menu menu = wx.Menu() @@ -499,7 +567,7 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20) vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect(src1, thr1, sink1) + self.connect(src1, thr1, sink1) #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) @@ -509,7 +577,7 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20) vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect(src2, thr2, sink2) + self.connect(src2, thr2, sink2) def main (): app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") -- cgit From 1c8207ec547f0f5513e29fa6f3cc2037c407bc45 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 12 Jun 2008 19:08:59 +0000 Subject: fft stretch fix and control panel update git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8584 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 71 ++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 26 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 276406f9c..8c8d2d8f8 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -23,11 +23,13 @@ from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui2 import wx -import gnuradio.wxgui.plot as plot +import plot import numpy import threading import math +DIV_LEVELS = (1, 2, 5, 10, 20) + default_fftsink_size = (640,240) default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) @@ -220,49 +222,47 @@ class input_watcher (threading.Thread): wx.PostEvent (self.event_receiver, de) del de -class LabelText(wx.StaticText): - """Label text class for uniform labels among all controls.""" - - def __init__(self, window, label): - wx.StaticText.__init__(self, window, -1, str(label)) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - font.SetUnderlined(True) - self.SetFont(font) - -DIV_LEVELS = (1, 2, 5, 10, 20) - class control_panel(wx.Panel): + + class LabelText(wx.StaticText): + def __init__(self, window, label): + wx.StaticText.__init__(self, window, -1, label) + font = self.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + font.SetUnderlined(True) + self.SetFont(font) + def __init__(self, parent): self.parent = parent wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold - control_box.Add(LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + control_box.Add(self.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.SetValue(parent.fftsink.average) self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.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.SetValue(parent.fftsink.peak_hold) self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) #radio buttons for div size - control_box.Add(LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + control_box.Add(self.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.SetValue(parent.fftsink.y_per_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.Add(LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + control_box.Add(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(1) button_box = wx.BoxSizer(wx.HORIZONTAL) self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) @@ -273,6 +273,21 @@ class control_panel(wx.Panel): control_box.Add(button_box, 0, wx.ALIGN_CENTER) #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 checkboxes + self.average_check_box.SetValue(self.parent.fftsink.average) + self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) + #update radio buttons + try: + index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div) + self.radio_buttons[index].SetValue(True) + except: pass def on_radio_button_change(self, evt): selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] @@ -380,11 +395,10 @@ class fft_window (wx.Panel): graphics = plot.PlotGraphics (lines, title=self.fftsink.title, xLabel = self._units, yLabel = "dB") - - self.plot.Draw (graphics, xAxis=None, yAxis=self.y_range) + self.x_range = x_vals[0], x_vals[-1] + self.plot.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) self.update_y_range () - def set_peak_hold(self, enable): self.peak_hold = enable self.peak_vals = None @@ -397,10 +411,12 @@ class fft_window (wx.Panel): def on_average(self, evt): # print "on_average" self.fftsink.set_average(evt.IsChecked()) + self.control_panel.update() def on_peak_hold(self, evt): # print "on_peak_hold" self.fftsink.set_peak_hold(evt.IsChecked()) + self.control_panel.update() def on_incr_ref_level(self, evt): # print "on_incr_ref_level" @@ -414,11 +430,13 @@ class fft_window (wx.Panel): def on_incr_y_per_div(self, evt): # print "on_incr_y_per_div" - self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, (1,2,5,10,20))) + self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, DIV_LEVELS)) + self.control_panel.update() def on_decr_y_per_div(self, evt): # print "on_decr_y_per_div" - self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, (1,2,5,10,20))) + self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, DIV_LEVELS)) + self.control_panel.update() def on_y_per_div(self, evt): # print "on_y_per_div" @@ -433,6 +451,7 @@ class fft_window (wx.Panel): self.fftsink.set_y_per_div(10) elif Id == self.id_y_per_div_20: self.fftsink.set_y_per_div(20) + self.control_panel.update() def on_right_click(self, event): menu = self.popup_menu @@ -564,7 +583,7 @@ class test_app_block (stdgui2.std_top_block): sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size, sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20) + ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink1.win, 1, wx.EXPAND) self.connect(src1, thr1, sink1) @@ -574,7 +593,7 @@ class test_app_block (stdgui2.std_top_block): thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20) + ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink2.win, 1, wx.EXPAND) self.connect(src2, thr2, sink2) -- cgit From fbf6561921e39beb69caaca9f7012c17314530d7 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 12 Jun 2008 21:33:40 +0000 Subject: fftsink: y_per_div issue fixed git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8585 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 14 +++++--------- gr-wxgui/src/python/plot.py | 9 ++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 8c8d2d8f8..423097674 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -310,7 +310,6 @@ class fft_window (wx.Panel): main_box.Add (self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - self.y_range = None self.peak_hold = False self.peak_vals = None @@ -395,19 +394,16 @@ class fft_window (wx.Panel): graphics = plot.PlotGraphics (lines, title=self.fftsink.title, xLabel = self._units, yLabel = "dB") - self.x_range = x_vals[0], x_vals[-1] - self.plot.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) - self.update_y_range () + x_range = x_vals[0], x_vals[-1] + ymax = self.fftsink.ref_level + ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs + y_range = ymin, ymax + self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) def set_peak_hold(self, enable): self.peak_hold = enable self.peak_vals = None - def update_y_range (self): - ymax = self.fftsink.ref_level - ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs - self.y_range = self.plot._axisInterval ('min', ymin, ymax) - def on_average(self, evt): # print "on_average" self.fftsink.set_average(evt.IsChecked()) diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index 837ff947b..c104b0ea5 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -767,7 +767,7 @@ class PlotCanvas(wx.Window): def GetXUseScopeTicks(self): return self._xUseScopeTicks - def Draw(self, graphics, xAxis = None, yAxis = None, dc = None): + def Draw(self, graphics, xAxis = None, yAxis = None, dc = None, step=None): """Draw objects in graphics with specified x and y axis. graphics- instance of PlotGraphics with list of PolyXXX objects xAxis - tuple with (min, max) axis range to view @@ -829,7 +829,7 @@ class PlotCanvas(wx.Window): xticks = None xTextExtent= (0,0) # No text for ticks if self._ySpec is not 'none': - yticks = self._ticks(yAxis[0], yAxis[1]) + yticks = self._ticks(yAxis[0], yAxis[1], step) yTextExtentBottom= dc.GetTextExtent(yticks[0][1]) yTextExtentTop = dc.GetTextExtent(yticks[-1][1]) yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]), @@ -1277,7 +1277,7 @@ class PlotCanvas(wx.Window): pt[1]-0.5*h) text = 0 # axis values not drawn on right side - def _ticks(self, lower, upper): + def _ticks(self, lower, upper, step=None): ideal = (upper-lower)/7. log = _numpy.log10(ideal) power = _numpy.floor(log) @@ -1298,9 +1298,12 @@ class PlotCanvas(wx.Window): else: digits = -int(power) format = '%'+`digits+2`+'.'+`digits`+'f' + #force grid when step is not None + if step is not None: grid = step ticks = [] t = -grid*_numpy.floor(-lower/grid) while t <= upper: + if t == -0: t = 0 #remove neg zero condition ticks.append( (t, format % (t,)) ) t = t + grid return ticks -- cgit From 6051d8037f394e859a5980ef7ac37fa9db0cddac Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 12 Jun 2008 21:49:49 +0000 Subject: fftsink: stretch spacer for better fit git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@8586 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 423097674..f4e9143f1 100755 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -238,7 +238,7 @@ class control_panel(wx.Panel): control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold - control_box.AddSpacer(2) + control_box.AddStretchSpacer() control_box.Add(self.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, parent.on_average) @@ -248,7 +248,7 @@ class control_panel(wx.Panel): control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) #radio buttons for div size - control_box.AddSpacer(2) + control_box.AddStretchSpacer() control_box.Add(self.LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER) radio_box = wx.BoxSizer(wx.VERTICAL) self.radio_buttons = list() @@ -260,9 +260,9 @@ class control_panel(wx.Panel): control_box.Add(radio_box, 0, wx.EXPAND) #ref lvl buttons - control_box.AddSpacer(2) + control_box.AddStretchSpacer() control_box.Add(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(1) + control_box.AddSpacer(2) button_box = wx.BoxSizer(wx.HORIZONTAL) self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) @@ -271,6 +271,7 @@ class control_panel(wx.Panel): self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level) button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER) control_box.Add(button_box, 0, wx.ALIGN_CENTER) + control_box.AddStretchSpacer() #set sizer self.SetSizerAndFit(control_box) #update -- cgit From 36649d4e472172fe840444ac0268c7b6b4da94b4 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Thu, 14 Aug 2008 18:43:15 +0000 Subject: Merged changeset r9241:9289 from jblum/glwxgui into trunk. Adds OpenGL versions of fftsink, waterfallsink, and scopesink, and new constsink. See README.gl for use. (Josh Blum) git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9290 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 21 +- gr-wxgui/src/python/__init__.py | 3 +- gr-wxgui/src/python/common.py | 302 ++++++++++ gr-wxgui/src/python/const_window.py | 189 +++++++ gr-wxgui/src/python/constants.py | 64 +++ gr-wxgui/src/python/constsink_gl.py | 142 +++++ gr-wxgui/src/python/fft_window.py | 292 ++++++++++ gr-wxgui/src/python/fftsink2.py | 608 +-------------------- gr-wxgui/src/python/fftsink_gl.py | 172 ++++++ gr-wxgui/src/python/fftsink_nongl.py | 603 ++++++++++++++++++++ gr-wxgui/src/python/form.py | 0 gr-wxgui/src/python/number_window.py | 184 +++++++ gr-wxgui/src/python/numbersink2.py | 600 +++++--------------- gr-wxgui/src/python/plotter/Makefile.am | 37 ++ gr-wxgui/src/python/plotter/__init__.py | 23 + gr-wxgui/src/python/plotter/channel_plotter.py | 225 ++++++++ gr-wxgui/src/python/plotter/gltext.py | 503 +++++++++++++++++ gr-wxgui/src/python/plotter/plotter_base.py | 390 +++++++++++++ gr-wxgui/src/python/plotter/waterfall_plotter.py | 282 ++++++++++ gr-wxgui/src/python/powermate.py | 0 gr-wxgui/src/python/pubsub.py | 153 ++++++ gr-wxgui/src/python/scope_window.py | 482 ++++++++++++++++ gr-wxgui/src/python/scopesink2.py | 666 +---------------------- gr-wxgui/src/python/scopesink_gl.py | 183 +++++++ gr-wxgui/src/python/scopesink_nongl.py | 661 ++++++++++++++++++++++ gr-wxgui/src/python/slider.py | 0 gr-wxgui/src/python/waterfall_window.py | 301 ++++++++++ gr-wxgui/src/python/waterfallsink2.py | 442 +-------------- gr-wxgui/src/python/waterfallsink_gl.py | 175 ++++++ gr-wxgui/src/python/waterfallsink_nongl.py | 437 +++++++++++++++ 30 files changed, 6031 insertions(+), 2109 deletions(-) create mode 100644 gr-wxgui/src/python/common.py create mode 100644 gr-wxgui/src/python/const_window.py create mode 100644 gr-wxgui/src/python/constants.py create mode 100644 gr-wxgui/src/python/constsink_gl.py create mode 100644 gr-wxgui/src/python/fft_window.py mode change 100755 => 100644 gr-wxgui/src/python/fftsink2.py create mode 100644 gr-wxgui/src/python/fftsink_gl.py create mode 100644 gr-wxgui/src/python/fftsink_nongl.py mode change 100755 => 100644 gr-wxgui/src/python/form.py create mode 100644 gr-wxgui/src/python/number_window.py mode change 100755 => 100644 gr-wxgui/src/python/numbersink2.py create mode 100644 gr-wxgui/src/python/plotter/Makefile.am create mode 100644 gr-wxgui/src/python/plotter/__init__.py create mode 100644 gr-wxgui/src/python/plotter/channel_plotter.py create mode 100644 gr-wxgui/src/python/plotter/gltext.py create mode 100644 gr-wxgui/src/python/plotter/plotter_base.py create mode 100644 gr-wxgui/src/python/plotter/waterfall_plotter.py mode change 100755 => 100644 gr-wxgui/src/python/powermate.py create mode 100644 gr-wxgui/src/python/pubsub.py create mode 100644 gr-wxgui/src/python/scope_window.py mode change 100755 => 100644 gr-wxgui/src/python/scopesink2.py create mode 100644 gr-wxgui/src/python/scopesink_gl.py create mode 100644 gr-wxgui/src/python/scopesink_nongl.py mode change 100755 => 100644 gr-wxgui/src/python/slider.py create mode 100644 gr-wxgui/src/python/waterfall_window.py mode change 100755 => 100644 gr-wxgui/src/python/waterfallsink2.py create mode 100644 gr-wxgui/src/python/waterfallsink_gl.py create mode 100644 gr-wxgui/src/python/waterfallsink_nongl.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index bda7c362c..a85bd0920 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -21,6 +21,8 @@ include $(top_srcdir)/Makefile.common +SUBDIRS = plotter + # Install this stuff so that it ends up as the gnuradio.wxgui module # This usually ends up at: # ${prefix}/lib/python${python_version}/site-packages/gnuradio/wxgui @@ -30,12 +32,27 @@ ourlibdir = $(grpyexecdir)/wxgui ourpython_PYTHON = \ __init__.py \ + common.py \ + constants.py \ + constsink_gl.py \ + const_window.py \ form.py \ fftsink2.py \ + fftsink_nongl.py \ + fftsink_gl.py \ + fft_window.py \ + numbersink2.py \ + number_window.py \ plot.py \ powermate.py \ + pubsub.py \ scopesink2.py \ + scopesink_nongl.py \ + scopesink_gl.py \ + scope_window.py \ waterfallsink2.py \ + waterfallsink_nongl.py \ + waterfallsink_gl.py \ + waterfall_window.py \ slider.py \ - stdgui2.py \ - numbersink2.py + stdgui2.py diff --git a/gr-wxgui/src/python/__init__.py b/gr-wxgui/src/python/__init__.py index 027150db1..7be3c38aa 100644 --- a/gr-wxgui/src/python/__init__.py +++ b/gr-wxgui/src/python/__init__.py @@ -1 +1,2 @@ -# make this directory a package +import plotter + diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py new file mode 100644 index 000000000..84b4c9c4c --- /dev/null +++ b/gr-wxgui/src/python/common.py @@ -0,0 +1,302 @@ +# +# 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 + +class prop_setter(object): + def _register_set_prop(self, controller, control_key, init=None): + def set_method(value): controller[control_key] = value + if init is not None: set_method(init) + setattr(self, 'set_%s'%control_key, set_method) + +################################################## +# 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().to_string()) + +################################################## +# 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 Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + self._incr_button.Enable() + self._decr_button.Enable() + else: + self._incr_button.Disable() + self._decr_button.Disable() + +class ToggleButtonController(wx.Button): + def __init__(self, parent, controller, control_key, true_label, false_label): + self._controller = controller + self._control_key = control_key + wx.Button.__init__(self, parent, -1, '', style=wx.BU_EXACTFIT) + self.Bind(wx.EVT_BUTTON, self._evt_button) + controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label)) + + def _evt_button(self, e): + self._controller[self._control_key] = not self._controller[self._control_key] + +class CheckBoxController(wx.CheckBox): + def __init__(self, parent, label, controller, control_key): + self._controller = controller + self._control_key = control_key + wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label) + self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox) + controller.subscribe(control_key, lambda x: self.SetValue(bool(x))) + + def _evt_checkbox(self, e): + self._controller[self._control_key] = bool(e.IsChecked()) + +class LogSliderController(wx.BoxSizer): + """! + Log slider controller with display label and slider. + Gives logarithmic scaling to slider operation. + """ + def __init__(self, parent, label, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): + wx.BoxSizer.__init__(self, wx.VERTICAL) + self._label = wx.StaticText(parent, -1, label + formatter(1/3.0)) + self.Add(self._label, 0, wx.EXPAND) + self._slider = wx.Slider(parent, -1, 0, 0, slider_steps, style=wx.SL_HORIZONTAL) + self.Add(self._slider, 0, wx.EXPAND) + def _on_slider_event(event): + controller[control_key] = \ + 10**(float(max_exp-min_exp)*self._slider.GetValue()/slider_steps + min_exp) + self._slider.Bind(wx.EVT_SLIDER, _on_slider_event) + def _on_controller_set(value): + self._label.SetLabel(label + formatter(value)) + slider_value = slider_steps*(math.log10(value)-min_exp)/(max_exp-min_exp) + slider_value = min(max(0, slider_value), slider_steps) + if abs(slider_value - self._slider.GetValue()) > 1: + self._slider.SetValue(slider_value) + controller.subscribe(control_key, _on_controller_set) + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + self._slider.Enable() + self._label.Enable() + else: + self._slider.Disable() + self._label.Disable() + +class DropDownController(wx.BoxSizer): + """! + Drop down controller with label and chooser. + Srop down selection from a set of choices. + """ + def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)): + """! + @param parent the parent window + @param label the label for the drop down + @param choices a list of tuples -> (label, value) + @param controller the prop val controller + @param control_key the prop key for this control + """ + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + self._label = wx.StaticText(parent, -1, ' %s '%label) + self.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL) + self._chooser = wx.Choice(parent, -1, choices=[c[0] for c in choices], size=size) + def _on_chooser_event(event): + controller[control_key] = choices[self._chooser.GetSelection()][1] + self._chooser.Bind(wx.EVT_CHOICE, _on_chooser_event) + self.Add(self._chooser, 0, wx.ALIGN_CENTER_VERTICAL) + def _on_controller_set(value): + #only set the chooser if the value is a possible choice + for i, choice in enumerate(choices): + if value == choice[1]: self._chooser.SetSelection(i) + controller.subscribe(control_key, _on_controller_set) + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + self._chooser.Enable() + self._label.Enable() + else: + self._chooser.Disable() + self._label.Disable() + +################################################## +# 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 + """ + if num == 0: return 0 + if num > 0: sign = 1 + else: sign = -1 + exp = get_exp(num) + nums = numpy.array((1, 2, 5, 10))*(10**exp) + return sign*nums[numpy.argmin(numpy.abs(nums - abs(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 + """ + num = get_clean_num(num) + exp = get_exp(num) + coeff = int(round(num/10**exp)) + return { + -5: -2, + -2: -1, + -1: -.5, + 1: 2, + 2: 5, + 5: 10, + }[coeff]*(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 + """ + num = get_clean_num(num) + exp = get_exp(num) + coeff = int(round(num/10**exp)) + return { + -5: -10, + -2: -5, + -1: -2, + 1: .5, + 2: 1, + 5: 2, + }[coeff]*(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 + +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. + @param num the floating point number + @return the tuple coeff, exp, prefix + """ + 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 label_format(num): + """! + Format a floating point number into a presentable string. + If the number has an small enough exponent, use regular decimal. + Otherwise, format the number with floating point notation. + Exponents are normalized to multiples of 3. + In the case where the exponent was found to be -3, + it is best to display this as a regular decimal, with a 0 to the left. + @param num the number to format + @return a label string + """ + coeff, exp, prefix = get_si_components(num) + if -3 <= exp < 3: return '%g'%num + return '%se%d'%('%.3g'%coeff, exp) + +if __name__ == '__main__': + import random + for i in range(-25, 25): + num = random.random()*10**i + print num, ':', get_si_components(num) diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py new file mode 100644 index 000000000..9510416be --- /dev/null +++ b/gr-wxgui/src/python/const_window.py @@ -0,0 +1,189 @@ +# +# 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 +################################################## +import plotter +import common +import wx +import numpy +import math +import pubsub +from constants import * + +################################################## +# Constants +################################################## +SLIDER_STEPS = 200 +ALPHA_MIN_EXP, ALPHA_MAX_EXP = -6, -0.301 +GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP = -6, -0.301 +DEFAULT_FRAME_RATE = 5 +DEFAULT_WIN_SIZE = (500, 400) +DEFAULT_CONST_SIZE = 2048 +CONST_PLOT_COLOR_SPEC = (0, 0, 1) +MARKER_TYPES = ( + ('Dot Small', 1.0), + ('Dot Medium', 2.0), + ('Dot Large', 3.0), + ('Line Link', None), +) +DEFAULT_MARKER_TYPE = MARKER_TYPES[1][1] + +################################################## +# 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) + self.marker_index = 2 + #begin control box + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + #marker + control_box.AddStretchSpacer() + self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) + control_box.Add(self.marker_chooser, 0, wx.EXPAND) + #alpha + control_box.AddStretchSpacer() + self.alpha_slider = common.LogSliderController( + self, 'Alpha', + ALPHA_MIN_EXP, ALPHA_MAX_EXP, SLIDER_STEPS, + parent.ext_controller, parent.alpha_key, + ) + control_box.Add(self.alpha_slider, 0, wx.EXPAND) + #gain_mu + control_box.AddStretchSpacer() + self.gain_mu_slider = common.LogSliderController( + self, 'Gain Mu', + GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP, SLIDER_STEPS, + parent.ext_controller, parent.gain_mu_key, + ) + control_box.Add(self.gain_mu_slider, 0, wx.EXPAND) + #run/stop + control_box.AddStretchSpacer() + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + +################################################## +# Constellation window with plotter and control panel +################################################## +class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): + def __init__( + self, + parent, + controller, + size, + title, + msg_key, + alpha_key, + beta_key, + gain_mu_key, + gain_omega_key, + ): + pubsub.pubsub.__init__(self) + #setup + self.ext_controller = controller + self.alpha_key = alpha_key + self.beta_key = beta_key + self.gain_mu_key = gain_mu_key + self.gain_omega_key = gain_omega_key + #init panel and plot + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + self.plotter = plotter.channel_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.set_x_label('Inphase') + self.plotter.set_y_label('Quadrature') + self.plotter.enable_point_label(True) + #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) + #alpha and gain mu 2nd orders + def set_beta(alpha): self.ext_controller[self.beta_key] = .25*alpha**2 + self.ext_controller.subscribe(self.alpha_key, set_beta) + def set_gain_omega(gain_mu): self.ext_controller[self.gain_omega_key] = .25*gain_mu**2 + self.ext_controller.subscribe(self.gain_mu_key, set_gain_omega) + #initial setup + self.ext_controller[self.alpha_key] = self.ext_controller[self.alpha_key] + self.ext_controller[self.gain_mu_key] = self.ext_controller[self.gain_mu_key] + self._register_set_prop(self, RUNNING_KEY, True) + self._register_set_prop(self, X_DIVS_KEY, 8) + self._register_set_prop(self, Y_DIVS_KEY, 8) + self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) + #register events + self.ext_controller.subscribe(msg_key, self.handle_msg) + for key in ( + X_DIVS_KEY, Y_DIVS_KEY, + ): self.subscribe(key, self.update_grid) + #initial update + self.update_grid() + + def handle_msg(self, msg): + """! + Plot the samples onto the complex grid. + @param msg the array of complex samples + """ + if not self[RUNNING_KEY]: return + #convert to complex floating point numbers + samples = numpy.fromstring(msg, numpy.complex64) + 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=self[MARKER_KEY], + ) + #update the plotter + self.plotter.update() + + def update_grid(self): + #grid parameters + x_divs = self[X_DIVS_KEY] + y_divs = self[Y_DIVS_KEY] + #update the x axis + x_max = 2.0 + self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/x_divs)) + #update the y axis + y_max = 2.0 + self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/y_divs)) + #update plotter + self.plotter.update() + + + + diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py new file mode 100644 index 000000000..06c5a44f1 --- /dev/null +++ b/gr-wxgui/src/python/constants.py @@ -0,0 +1,64 @@ +# +# 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. +# + +################################################## +# Controller Keys +################################################## +AC_COUPLE_KEY = 'ac_couple' +ALPHA_KEY = 'alpha' +AUTORANGE_KEY = 'autorange' +AVERAGE_KEY = 'average' +AVG_ALPHA_KEY = 'avg_alpha' +BASEBAND_FREQ_KEY = 'baseband_freq' +BETA_KEY = 'beta' +COLOR_MODE_KEY = 'color_mode' +DECIMATION_KEY = 'decimation' +DYNAMIC_RANGE_KEY = 'dynamic_range' +FRAME_RATE_KEY = 'frame_rate' +GAIN_MU_KEY = 'gain_mu' +GAIN_OMEGA_KEY = 'gain_omega' +MARKER_KEY = 'marker' +MSG_KEY = 'msg' +NUM_LINES_KEY = 'num_lines' +OMEGA_KEY = 'omega' +PEAK_HOLD_KEY = 'peak_hold' +REF_LEVEL_KEY = 'ref_level' +RUNNING_KEY = 'running' +SAMPLE_RATE_KEY = 'sample_rate' +SCOPE_TRIGGER_CHANNEL_KEY = 'scope_trigger_channel' +SCOPE_TRIGGER_LEVEL_KEY = 'scope_trigger_level' +SCOPE_TRIGGER_MODE_KEY = 'scope_trigger_mode' +SCOPE_X_CHANNEL_KEY = 'scope_x_channel' +SCOPE_Y_CHANNEL_KEY = 'scope_y_channel' +SCOPE_XY_MODE_KEY = 'scope_xy_mode' +TRIGGER_CHANNEL_KEY = 'trigger_channel' +TRIGGER_LEVEL_KEY = 'trigger_level' +TRIGGER_MODE_KEY = 'trigger_mode' +T_DIVS_KEY = 't_divs' +T_OFF_KEY = 't_off' +T_PER_DIV_KEY = 't_per_div' +X_DIVS_KEY = 'x_divs' +X_OFF_KEY = 'x_off' +X_PER_DIV_KEY = 'x_per_div' +Y_DIVS_KEY = 'y_divs' +Y_OFF_KEY = 'y_off' +Y_PER_DIV_KEY = 'y_per_div' + diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py new file mode 100644 index 000000000..2e1f3c5a0 --- /dev/null +++ b/gr-wxgui/src/python/constsink_gl.py @@ -0,0 +1,142 @@ +# +# 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 +################################################## +import const_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# Constellation sink block (wrapper for old wxgui) +################################################## +class const_sink_c(gr.hier_block2, common.prop_setter): + """! + A constellation block with a gui window. + """ + + def __init__( + self, + parent, + title='', + sample_rate=1, + size=const_window.DEFAULT_WIN_SIZE, + frame_rate=const_window.DEFAULT_FRAME_RATE, + const_size=const_window.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_limit=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 + 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 + # Costas frequency/phase recovery loop + # Critically damped 2nd order PLL + self._costas = gr.costas_loop_cc(alpha, beta, fmax, fmin, M) + # Timing recovery loop + # Critically damped 2nd order DLL + self._retime = gr.clock_recovery_mm_cc(omega, gain_omega, mu, gain_mu, omega_limit) + #sync = gr.mpsk_receiver_cc( + # M, #psk order + # theta, + # alpha, + # beta, + # fmin, + # fmax, + # mu, + # gain_mu, + # omega, + # gain_omega, + # omega_limit, + #) + 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._costas, self._retime, agc, sd, sink) + #controller + def setter(p, k, x): # lambdas can't have assignments :( + p[k] = x + self.controller = pubsub() + self.controller.subscribe(ALPHA_KEY, self._costas.set_alpha) + self.controller.publish(ALPHA_KEY, self._costas.alpha) + self.controller.subscribe(BETA_KEY, self._costas.set_beta) + self.controller.publish(BETA_KEY, self._costas.beta) + self.controller.subscribe(GAIN_MU_KEY, self._retime.set_gain_mu) + self.controller.publish(GAIN_MU_KEY, self._retime.gain_mu) + self.controller.subscribe(OMEGA_KEY, self._retime.set_omega) + self.controller.publish(OMEGA_KEY, self._retime.omega) + self.controller.subscribe(GAIN_OMEGA_KEY, self._retime.set_gain_omega) + self.controller.publish(GAIN_OMEGA_KEY, self._retime.gain_omega) + self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) + self.controller.subscribe(SAMPLE_RATE_KEY, lambda x: setter(self.controller, OMEGA_KEY, float(x)/symbol_rate)) + self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) + #initial update + self.controller[SAMPLE_RATE_KEY] = sample_rate + #start input watcher + common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + #create window + self.win = const_window.const_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + msg_key=MSG_KEY, + alpha_key=ALPHA_KEY, + beta_key=BETA_KEY, + gain_mu_key=GAIN_MU_KEY, + gain_omega_key=GAIN_OMEGA_KEY, + ) + #register callbacks from window for external use + for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): + setattr(self, attr, getattr(self.win, attr)) + self._register_set_prop(self.controller, ALPHA_KEY) + self._register_set_prop(self.controller, BETA_KEY) + self._register_set_prop(self.controller, GAIN_MU_KEY) + self._register_set_prop(self.controller, OMEGA_KEY) + self._register_set_prop(self.controller, GAIN_OMEGA_KEY) + self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + + diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py new file mode 100644 index 000000000..5f48e8324 --- /dev/null +++ b/gr-wxgui/src/python/fft_window.py @@ -0,0 +1,292 @@ +# +# 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 +################################################## +import plotter +import common +import wx +import numpy +import math +import pubsub +from constants import * + +################################################## +# Constants +################################################## +SLIDER_STEPS = 100 +AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 +DEFAULT_WIN_SIZE = (600, 300) +DEFAULT_FRAME_RATE = 30 +DIV_LEVELS = (1, 2, 5, 10, 20) +FFT_PLOT_COLOR_SPEC = (0, 0, 1) +PEAK_VALS_COLOR_SPEC = (0, 1, 0) +NO_PEAK_VALS = list() + +################################################## +# 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 = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) + control_box.Add(self.average_check_box, 0, wx.EXPAND) + self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) + control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + control_box.AddSpacer(2) + self.avg_alpha_slider = common.LogSliderController( + self, 'Avg Alpha', + AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, + parent.ext_controller, parent.avg_alpha_key, + formatter=lambda x: ': %.4f'%x, + ) + parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) + 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_y_per_div) + self.radio_buttons.append(radio_button) + radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) + parent.subscribe(Y_PER_DIV_KEY, self._on_set_y_per_div) + control_box.Add(radio_box, 0, wx.EXPAND) + #ref lvl buttons + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Set Ref Level'), 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) + #autoscale + control_box.AddStretchSpacer() + self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(self.autoscale_button, 0, wx.EXPAND) + #run/stop + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + + ################################################## + # Event handlers + ################################################## + def _on_set_y_per_div(self, y_per_div): + try: + index = list(DIV_LEVELS).index(y_per_div) + self.radio_buttons[index].SetValue(True) + except: pass + def _on_y_per_div(self, event): + selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] + index = self.radio_buttons.index(selected_radio_button) + self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index] + def _on_incr_ref_level(self, event): + self.parent.set_ref_level( + self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY]) + def _on_decr_ref_level(self, event): + self.parent.set_ref_level( + self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY]) + +################################################## +# FFT window with plotter and control panel +################################################## +class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): + def __init__( + self, + parent, + controller, + size, + title, + real, + fft_size, + baseband_freq, + sample_rate_key, + y_per_div, + y_divs, + ref_level, + average_key, + avg_alpha_key, + peak_hold, + msg_key, + ): + pubsub.pubsub.__init__(self) + #ensure y_per_div + if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0] + #setup + self.ext_controller = controller + self.real = real + self.fft_size = fft_size + self.sample_rate_key = sample_rate_key + self.average_key = average_key + self.avg_alpha_key = avg_alpha_key + self._reset_peak_vals() + #init panel and plot + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + self.plotter = plotter.channel_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_point_label(True) + #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) + #initial setup + self.ext_controller[self.average_key] = self.ext_controller[self.average_key] + self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] + self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) + self._register_set_prop(self, Y_PER_DIV_KEY, y_per_div) + self._register_set_prop(self, Y_DIVS_KEY, y_divs) + self._register_set_prop(self, X_DIVS_KEY, 8) #approximate + self._register_set_prop(self, REF_LEVEL_KEY, ref_level) + self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) + self._register_set_prop(self, RUNNING_KEY, True) + #register events + self.subscribe(PEAK_HOLD_KEY, self.plotter.enable_legend) + self.ext_controller.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) + self.ext_controller.subscribe(msg_key, self.handle_msg) + self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) + for key in ( + BASEBAND_FREQ_KEY, + Y_PER_DIV_KEY, X_DIVS_KEY, + Y_DIVS_KEY, REF_LEVEL_KEY, + ): self.subscribe(key, self.update_grid) + #initial update + self.plotter.enable_legend(self[PEAK_HOLD_KEY]) + self.update_grid() + + def autoscale(self, *args): + """! + Autoscale the fft plot to the last frame. + Set the dynamic range and reference level. + """ + #get the peak level (max of the samples) + peak_level = numpy.max(self.samples) + #get the noise floor (averge the smallest samples) + noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4]) + #padding + noise_floor -= abs(noise_floor)*.5 + peak_level += abs(peak_level)*.1 + #set the reference level to a multiple of y divs + self.set_ref_level(self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY])) + #set the range to a clean number of the dynamic range + self.set_y_per_div(common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY])) + + def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS + + 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. + Plot the samples onto the grid as channel 1. + If peak hold is enabled, plot peak vals as channel 2. + @param msg the fft array as a character array + """ + if not self[RUNNING_KEY]: return + #convert to floating point numbers + samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame + num_samps = len(samples) + #reorder fft + if self.real: samples = samples[:num_samps/2] + else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2])) + self.samples = samples + #peak hold calculation + if self[PEAK_HOLD_KEY]: + if len(self.peak_vals) != len(samples): self.peak_vals = samples + self.peak_vals = numpy.maximum(samples, self.peak_vals) + else: self._reset_peak_vals() + #plot the fft + self.plotter.set_waveform( + channel='FFT', + samples=samples, + color_spec=FFT_PLOT_COLOR_SPEC, + ) + #plot the peak hold + self.plotter.set_waveform( + channel='Peak', + samples=self.peak_vals, + color_spec=PEAK_VALS_COLOR_SPEC, + ) + #update the plotter + self.plotter.update() + + def update_grid(self, *args): + """! + Update the plotter grid. + This update method is dependent on the variables below. + Determine the x and y axis grid parameters. + The x axis depends on sample rate, baseband freq, and x divs. + The y axis depends on y per div, y divs, and ref level. + """ + #grid parameters + sample_rate = self.ext_controller[self.sample_rate_key] + baseband_freq = self[BASEBAND_FREQ_KEY] + y_per_div = self[Y_PER_DIV_KEY] + y_divs = self[Y_DIVS_KEY] + x_divs = self[X_DIVS_KEY] + ref_level = self[REF_LEVEL_KEY] + #determine best fitting x_per_div + if self.real: x_width = sample_rate/2.0 + else: x_width = sample_rate/1.0 + x_per_div = common.get_clean_num(x_width/x_divs) + coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) + #update the x grid + if self.real: + self.plotter.set_x_grid( + baseband_freq, + baseband_freq + sample_rate/2.0, + x_per_div, + 10**(-exp), + ) + else: + self.plotter.set_x_grid( + baseband_freq - sample_rate/2.0, + baseband_freq + sample_rate/2.0, + x_per_div, + 10**(-exp), + ) + #update x units + self.plotter.set_x_label('Frequency', prefix+'Hz') + #update y grid + self.plotter.set_y_grid(ref_level-y_per_div*y_divs, ref_level, y_per_div) + #update y units + self.plotter.set_y_label('Amplitude', 'dB') + #update plotter + self.plotter.update() diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py old mode 100755 new mode 100644 index f4e9143f1..ad2a09c96 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -1,603 +1,45 @@ -#!/usr/bin/env python # -# Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc. -# +# 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 gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui2 -import wx -import plot -import numpy -import threading -import math - -DIV_LEVELS = (1, 2, 5, 10, 20) - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class fft_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, - y_divs=8, ref_level=50, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', peak_hold=False): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.y_per_div=y_per_div - self.y_divs = y_divs - self.ref_level = ref_level - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.title = title - self.peak_hold = peak_hold - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages - - def set_y_per_div(self, y_per_div): - self.y_per_div = y_per_div - - def set_ref_level(self, ref_level): - self.ref_level = ref_level - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - else: - self.avg.set_taps(1.0) - self.win.peak_vals = None - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.win.set_peak_hold(enable) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - -class fft_sink_f(gr.hier_block2, fft_sink_base): - 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, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): - - gr.hier_block2.__init__(self, "fft_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(0,0,0)) - - fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) - - self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = gr.fft_vfc(self.fft_size, True, mywindow) - power = 0 - for tap in mywindow: - power += tap*tap - - self.c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - - # FIXME We need to add 3dB to all bins but the DC bin - self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - - self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - self.set_peak_hold(self.peak_hold) - -class fft_sink_c(gr.hier_block2, fft_sink_base): - 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, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): - - gr.hier_block2.__init__(self, "fft_sink_c", - gr.io_signature(1, 1, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) - - self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = gr.fft_vcc(self.fft_size, True, mywindow) - power = 0 - for tap in mywindow: - power += tap*tap - - self.c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - - # FIXME We need to add 3dB to all bins but the DC bin - self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins - -10*math.log10(power/self.fft_size) # Adjust for windowing loss - -20*math.log10(ref_scale/2)) # Adjust for reference scale - - self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = fft_window(self, parent, size=size) - self.set_average(self.average) - self.set_peak_hold(self.peak_hold) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.fft_size = fft_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - -class control_panel(wx.Panel): - - class LabelText(wx.StaticText): - def __init__(self, window, label): - wx.StaticText.__init__(self, window, -1, label) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - font.SetUnderlined(True) - self.SetFont(font) - - def __init__(self, parent): - self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) - control_box = wx.BoxSizer(wx.VERTICAL) - - #checkboxes for average and peak hold - control_box.AddStretchSpacer() - control_box.Add(self.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, parent.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, parent.on_peak_hold) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) - - #radio buttons for div size - control_box.AddStretchSpacer() - control_box.Add(self.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(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - button_box = wx.BoxSizer(wx.HORIZONTAL) - self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) - self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) - button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER) - self.ref_minus_button = wx.Button(self, -1, ' - ', style=wx.BU_EXACTFIT) - self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level) - button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER) - control_box.Add(button_box, 0, wx.ALIGN_CENTER) - control_box.AddStretchSpacer() - #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 checkboxes - self.average_check_box.SetValue(self.parent.fftsink.average) - self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) - #update radio buttons - try: - index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div) - self.radio_buttons[index].SetValue(True) - except: pass - - def on_radio_button_change(self, evt): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] - index = self.radio_buttons.index(selected_radio_button) - self.parent.fftsink.set_y_per_div(DIV_LEVELS[index]) - -class fft_window (wx.Panel): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - - self.fftsink = fftsink - #init panel and plot - wx.Panel.__init__(self, parent, -1) - self.plot = plot.PlotCanvas(self, id, pos, size, style, name) - #setup the box with plot and controls - self.control_panel = control_panel(self) - main_box = wx.BoxSizer (wx.HORIZONTAL) - main_box.Add (self.plot, 1, wx.EXPAND) - main_box.Add (self.control_panel, 0, wx.EXPAND) - self.SetSizerAndFit(main_box) - - self.peak_hold = False - self.peak_vals = None - - self.plot.SetEnableGrid (True) - # self.SetEnableZoom (True) - # self.SetBackgroundColour ('black') - - self.build_popup_menu() - self.set_baseband_freq(0.0) - - EVT_DATA_EVENT (self, self.set_data) - wx.EVT_CLOSE (self, self.on_close_window) - self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - self.plot.Bind(wx.EVT_MOTION, self.evt_motion) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - def set_scale(self, freq): - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - self._scale_factor = 1e-9 - self._units = "GHz" - self._format = "%3.6f" - elif x >= 1e6: - self._scale_factor = 1e-6 - self._units = "MHz" - self._format = "%3.3f" - else: - self._scale_factor = 1e-3 - self._units = "kHz" - self._format = "%3.3f" - - def set_baseband_freq(self, baseband_freq): - if self.peak_hold: - self.peak_vals = None - self.set_scale(baseband_freq) - self.fftsink.set_baseband_freq(baseband_freq) - - def on_close_window (self, event): - print "fft_window:on_close_window" - self.keep_running = False - - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - if self.peak_hold: - if self.peak_vals is None: - self.peak_vals = dB - else: - self.peak_vals = numpy.maximum(dB, self.peak_vals) - - if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate - * self._scale_factor / L)) - + self.fftsink.baseband_freq * self._scale_factor) - self._points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._points[:,0] = x_vals - self._points[:,1] = dB[0:L/2] - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = self.peak_vals[0:L/2] - else: - # the "negative freqs" are in the second half of the array - x_vals = ((numpy.arange (-L/2, L/2) - * (self.fftsink.sample_rate * self._scale_factor / L)) - + self.fftsink.baseband_freq * self._scale_factor) - self._points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._points[:,0] = x_vals - self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) - if self.peak_hold: - self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) - self._peak_points[:,0] = x_vals - self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) - - lines = [plot.PolyLine (self._points, colour='BLUE'),] - if self.peak_hold: - lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) - - graphics = plot.PlotGraphics (lines, - title=self.fftsink.title, - xLabel = self._units, yLabel = "dB") - x_range = x_vals[0], x_vals[-1] - ymax = self.fftsink.ref_level - ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs - y_range = ymin, ymax - self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - self.control_panel.update() - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.fftsink.set_peak_hold(evt.IsChecked()) - self.control_panel.update() - - def on_incr_ref_level(self, evt): - # print "on_incr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - + self.fftsink.y_per_div) - - def on_decr_ref_level(self, evt): - # print "on_decr_ref_level" - self.fftsink.set_ref_level(self.fftsink.ref_level - - self.fftsink.y_per_div) - - def on_incr_y_per_div(self, evt): - # print "on_incr_y_per_div" - self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, DIV_LEVELS)) - self.control_panel.update() - - def on_decr_y_per_div(self, evt): - # print "on_decr_y_per_div" - self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, DIV_LEVELS)) - self.control_panel.update() - - def on_y_per_div(self, evt): - # print "on_y_per_div" - Id = evt.GetId() - if Id == self.id_y_per_div_1: - self.fftsink.set_y_per_div(1) - elif Id == self.id_y_per_div_2: - self.fftsink.set_y_per_div(2) - elif Id == self.id_y_per_div_5: - self.fftsink.set_y_per_div(5) - elif Id == self.id_y_per_div_10: - self.fftsink.set_y_per_div(10) - elif Id == self.id_y_per_div_20: - self.fftsink.set_y_per_div(20) - self.control_panel.update() - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.plot.PopupMenu(menu, event.GetPosition()) - - def evt_motion(self, event): - if not hasattr(self, "_points"): - return # Got here before first window data update - - # Clip to plotted values - (ux, uy) = self.plot.GetXY(event) # Scaled position - x_vals = numpy.array(self._points[:,0]) - if ux < x_vals[0] or ux > x_vals[-1]: - tip = self.GetToolTip() - if tip: - tip.Enable(False) - return - - # Get nearest X value (is there a better way)? - ind = numpy.argmin(numpy.abs(x_vals-ux)) - x_val = x_vals[ind] - db_val = self._points[ind, 1] - text = (self._format+" %s dB=%3.3f") % (x_val, self._units, db_val) - - # Display the tooltip - tip = wx.ToolTip(text) - tip.Enable(True) - tip.SetDelay(0) - self.SetToolTip(tip) - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - self.id_peak_hold = wx.NewId() - - self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.plot.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - self.plot.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") - menu.Append(self.id_incr_ref_level, "Incr Ref Level") - menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average, - self.id_peak_hold : lambda : self.fftsink.peak_hold, - self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_app_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - fft_size = 256 - - # build our flow graph - input_rate = 20.48e3 - - # Generate a complex sinusoid - #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) +# - sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add (sink1.win, 1, wx.EXPAND) +from gnuradio import gr - self.connect(src1, thr1, sink1) +p = gr.prefs() +style = p.get_string('wxgui', 'style', 'auto') - #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) - thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, - sample_rate=input_rate, baseband_freq=100e3, - ref_level=0, y_per_div=20, y_divs=10) - vbox.Add (sink2.win, 1, wx.EXPAND) +# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback +if style == 'auto': + style = 'nongl' - self.connect(src2, thr2, sink2) +if style == 'nongl': + from fftsink_nongl import fft_sink_f, fft_sink_c +elif style == 'gl': + try: + import wx + wx.glcanvas.GLCanvas + except AttributeError: + raise RuntimeError("wxPython doesn't support glcanvas") -def main (): - app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") - app.MainLoop () + try: + from OpenGL.GL import * + except ImportError: + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") -if __name__ == '__main__': - main () + from fftsink_gl import fft_sink_f, fft_sink_c diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py new file mode 100644 index 000000000..db402618e --- /dev/null +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -0,0 +1,172 @@ +# +# 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 +################################################## +import fft_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# FFT sink block (wrapper for old wxgui) +################################################## +class _fft_sink_base(gr.hier_block2, common.prop_setter): + """! + 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, + fft_rate=gr.prefs().get_long('wxgui', 'fft_rate', fft_window.DEFAULT_FRAME_RATE), + average=False, + avg_alpha=None, + title='', + size=fft_window.DEFAULT_WIN_SIZE, + peak_hold=False, + ): + #ensure avg alpha + if avg_alpha is None: avg_alpha = 2.0/fft_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=fft_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) + #controller + self.controller = pubsub() + self.controller.subscribe(AVERAGE_KEY, fft.set_average) + self.controller.publish(AVERAGE_KEY, fft.average) + self.controller.subscribe(AVG_ALPHA_KEY, fft.set_avg_alpha) + self.controller.publish(AVG_ALPHA_KEY, fft.avg_alpha) + self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) + self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) + #start input watcher + def setter(p, k, x): # lambdas can't have assignments :( + p[k] = x + common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + #create window + self.win = fft_window.fft_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + real=self._real, + fft_size=fft_size, + baseband_freq=baseband_freq, + sample_rate_key=SAMPLE_RATE_KEY, + y_per_div=y_per_div, + y_divs=y_divs, + ref_level=ref_level, + average_key=AVERAGE_KEY, + avg_alpha_key=AVG_ALPHA_KEY, + peak_hold=peak_hold, + msg_key=MSG_KEY, + ) + #register callbacks from window for external use + for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): + setattr(self, attr, getattr(self.win, attr)) + self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + self._register_set_prop(self.controller, AVERAGE_KEY) + self._register_set_prop(self.controller, AVG_ALPHA_KEY) + +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 + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import stdgui2 + +class test_app_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 256 + + # build our flow graph + input_rate = 20.48e3 + + # Generate a complex sinusoid + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20, y_divs=10) + vbox.Add (sink1.win, 1, wx.EXPAND) + + self.connect(src1, thr1, sink1) + + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20, y_divs=10) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.connect(src2, thr2, sink2) + +def main (): + app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py new file mode 100644 index 000000000..f4e9143f1 --- /dev/null +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -0,0 +1,603 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2006,2007 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 gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui2 +import wx +import plot +import numpy +import threading +import math + +DIV_LEVELS = (1, 2, 5, 10, 20) + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class fft_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, + y_divs=8, ref_level=50, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title='', peak_hold=False): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.y_per_div=y_per_div + self.y_divs = y_divs + self.ref_level = ref_level + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.peak_hold = peak_hold + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages + + def set_y_per_div(self, y_per_div): + self.y_per_div = y_per_div + + def set_ref_level(self, ref_level): + self.ref_level = ref_level + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + else: + self.avg.set_taps(1.0) + self.win.peak_vals = None + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.win.set_peak_hold(enable) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + +class fft_sink_f(gr.hier_block2, fft_sink_base): + 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, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "fft_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0,0,0)) + + fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + self.fft = gr.fft_vfc(self.fft_size, True, mywindow) + power = 0 + for tap in mywindow: + power += tap*tap + + self.c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + + # FIXME We need to add 3dB to all bins but the DC bin + self.log = gr.nlog10_ff(20, self.fft_size, + -10*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale/2)) # Adjust for reference scale + + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + self.set_peak_hold(self.peak_hold) + +class fft_sink_c(gr.hier_block2, fft_sink_base): + 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, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size, peak_hold=False): + + gr.hier_block2.__init__(self, "fft_sink_c", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title, + peak_hold=peak_hold) + + self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + self.fft = gr.fft_vcc(self.fft_size, True, mywindow) + power = 0 + for tap in mywindow: + power += tap*tap + + self.c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + + # FIXME We need to add 3dB to all bins but the DC bin + self.log = gr.nlog10_ff(20, self.fft_size, + -10*math.log10(self.fft_size) # Adjust for number of bins + -10*math.log10(power/self.fft_size) # Adjust for windowing loss + -20*math.log10(ref_scale/2)) # Adjust for reference scale + + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + + self.win = fft_window(self, parent, size=size) + self.set_average(self.average) + self.set_peak_hold(self.peak_hold) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = numpy.fromstring (s, numpy.float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + +class control_panel(wx.Panel): + + class LabelText(wx.StaticText): + def __init__(self, window, label): + wx.StaticText.__init__(self, window, -1, label) + font = self.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + font.SetUnderlined(True) + self.SetFont(font) + + def __init__(self, parent): + self.parent = parent + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + control_box = wx.BoxSizer(wx.VERTICAL) + + #checkboxes for average and peak hold + control_box.AddStretchSpacer() + control_box.Add(self.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, parent.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, parent.on_peak_hold) + control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + + #radio buttons for div size + control_box.AddStretchSpacer() + control_box.Add(self.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(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + button_box = wx.BoxSizer(wx.HORIZONTAL) + self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) + self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) + button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER) + self.ref_minus_button = wx.Button(self, -1, ' - ', style=wx.BU_EXACTFIT) + self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level) + button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER) + control_box.Add(button_box, 0, wx.ALIGN_CENTER) + control_box.AddStretchSpacer() + #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 checkboxes + self.average_check_box.SetValue(self.parent.fftsink.average) + self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) + #update radio buttons + try: + index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div) + self.radio_buttons[index].SetValue(True) + except: pass + + def on_radio_button_change(self, evt): + selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] + index = self.radio_buttons.index(selected_radio_button) + self.parent.fftsink.set_y_per_div(DIV_LEVELS[index]) + +class fft_window (wx.Panel): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + + self.fftsink = fftsink + #init panel and plot + wx.Panel.__init__(self, parent, -1) + self.plot = plot.PlotCanvas(self, id, pos, size, style, name) + #setup the box with plot and controls + self.control_panel = control_panel(self) + main_box = wx.BoxSizer (wx.HORIZONTAL) + main_box.Add (self.plot, 1, wx.EXPAND) + main_box.Add (self.control_panel, 0, wx.EXPAND) + self.SetSizerAndFit(main_box) + + self.peak_hold = False + self.peak_vals = None + + self.plot.SetEnableGrid (True) + # self.SetEnableZoom (True) + # self.SetBackgroundColour ('black') + + self.build_popup_menu() + self.set_baseband_freq(0.0) + + EVT_DATA_EVENT (self, self.set_data) + wx.EVT_CLOSE (self, self.on_close_window) + self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + self.plot.Bind(wx.EVT_MOTION, self.evt_motion) + + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + + def set_scale(self, freq): + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + self._scale_factor = 1e-9 + self._units = "GHz" + self._format = "%3.6f" + elif x >= 1e6: + self._scale_factor = 1e-6 + self._units = "MHz" + self._format = "%3.3f" + else: + self._scale_factor = 1e-3 + self._units = "kHz" + self._format = "%3.3f" + + def set_baseband_freq(self, baseband_freq): + if self.peak_hold: + self.peak_vals = None + self.set_scale(baseband_freq) + self.fftsink.set_baseband_freq(baseband_freq) + + def on_close_window (self, event): + print "fft_window:on_close_window" + self.keep_running = False + + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + if self.peak_hold: + if self.peak_vals is None: + self.peak_vals = dB + else: + self.peak_vals = numpy.maximum(dB, self.peak_vals) + + if self.fftsink.input_is_real: # only plot 1/2 the points + x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate + * self._scale_factor / L)) + + self.fftsink.baseband_freq * self._scale_factor) + self._points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._points[:,0] = x_vals + self._points[:,1] = dB[0:L/2] + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = self.peak_vals[0:L/2] + else: + # the "negative freqs" are in the second half of the array + x_vals = ((numpy.arange (-L/2, L/2) + * (self.fftsink.sample_rate * self._scale_factor / L)) + + self.fftsink.baseband_freq * self._scale_factor) + self._points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._points[:,0] = x_vals + self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2])) + if self.peak_hold: + self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64) + self._peak_points[:,0] = x_vals + self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2])) + + lines = [plot.PolyLine (self._points, colour='BLUE'),] + if self.peak_hold: + lines.append(plot.PolyLine (self._peak_points, colour='GREEN')) + + graphics = plot.PlotGraphics (lines, + title=self.fftsink.title, + xLabel = self._units, yLabel = "dB") + x_range = x_vals[0], x_vals[-1] + ymax = self.fftsink.ref_level + ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs + y_range = ymin, ymax + self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) + + def set_peak_hold(self, enable): + self.peak_hold = enable + self.peak_vals = None + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + self.control_panel.update() + + def on_peak_hold(self, evt): + # print "on_peak_hold" + self.fftsink.set_peak_hold(evt.IsChecked()) + self.control_panel.update() + + def on_incr_ref_level(self, evt): + # print "on_incr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + + self.fftsink.y_per_div) + + def on_decr_ref_level(self, evt): + # print "on_decr_ref_level" + self.fftsink.set_ref_level(self.fftsink.ref_level + - self.fftsink.y_per_div) + + def on_incr_y_per_div(self, evt): + # print "on_incr_y_per_div" + self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, DIV_LEVELS)) + self.control_panel.update() + + def on_decr_y_per_div(self, evt): + # print "on_decr_y_per_div" + self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, DIV_LEVELS)) + self.control_panel.update() + + def on_y_per_div(self, evt): + # print "on_y_per_div" + Id = evt.GetId() + if Id == self.id_y_per_div_1: + self.fftsink.set_y_per_div(1) + elif Id == self.id_y_per_div_2: + self.fftsink.set_y_per_div(2) + elif Id == self.id_y_per_div_5: + self.fftsink.set_y_per_div(5) + elif Id == self.id_y_per_div_10: + self.fftsink.set_y_per_div(10) + elif Id == self.id_y_per_div_20: + self.fftsink.set_y_per_div(20) + self.control_panel.update() + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.plot.PopupMenu(menu, event.GetPosition()) + + def evt_motion(self, event): + if not hasattr(self, "_points"): + return # Got here before first window data update + + # Clip to plotted values + (ux, uy) = self.plot.GetXY(event) # Scaled position + x_vals = numpy.array(self._points[:,0]) + if ux < x_vals[0] or ux > x_vals[-1]: + tip = self.GetToolTip() + if tip: + tip.Enable(False) + return + + # Get nearest X value (is there a better way)? + ind = numpy.argmin(numpy.abs(x_vals-ux)) + x_val = x_vals[ind] + db_val = self._points[ind, 1] + text = (self._format+" %s dB=%3.3f") % (x_val, self._units, db_val) + + # Display the tooltip + tip = wx.ToolTip(text) + tip.Enable(True) + tip.SetDelay(0) + self.SetToolTip(tip) + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + self.id_peak_hold = wx.NewId() + + self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) + self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + self.plot.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + self.plot.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") + menu.Append(self.id_incr_ref_level, "Incr Ref Level") + menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average, + self.id_peak_hold : lambda : self.fftsink.peak_hold, + self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_app_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 256 + + # build our flow graph + input_rate = 20.48e3 + + # Generate a complex sinusoid + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20, y_divs=10) + vbox.Add (sink1.win, 1, wx.EXPAND) + + self.connect(src1, thr1, sink1) + + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, + sample_rate=input_rate, baseband_freq=100e3, + ref_level=0, y_per_div=20, y_divs=10) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.connect(src2, thr2, sink2) + +def main (): + app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py old mode 100755 new mode 100644 diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py new file mode 100644 index 000000000..8572b7c4a --- /dev/null +++ b/gr-wxgui/src/python/number_window.py @@ -0,0 +1,184 @@ +# +# 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 +################################################## +import common +import numpy +import wx +import pubsub +from constants import * + +################################################## +# Constants +################################################## +NEG_INF = float('-inf') +SLIDER_STEPS = 100 +AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 +DEFAULT_NUMBER_RATE = 5 +DEFAULT_WIN_SIZE = (300, 300) +DEFAULT_GAUGE_RANGE = 1000 + +################################################## +# Number window control panel +################################################## +class control_panel(wx.Panel): + """! + A control panel with wx widgits to control the averaging. + """ + + 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 = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) + control_box.Add(self.average_check_box, 0, wx.EXPAND) + self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) + control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + control_box.AddSpacer(2) + self.avg_alpha_slider = common.LogSliderController( + self, 'Avg Alpha', + AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, + parent.ext_controller, parent.avg_alpha_key, + formatter=lambda x: ': %.4f'%x, + ) + parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) + control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + #run/stop + control_box.AddStretchSpacer() + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + +################################################## +# Numbersink window with label and gauges +################################################## +class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): + def __init__( + self, + parent, + controller, + size, + title, + units, + show_gauge, + real, + minval, + maxval, + decimal_places, + average_key, + avg_alpha_key, + peak_hold, + msg_key, + ): + pubsub.pubsub.__init__(self) + wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + #setup + self.peak_val_real = NEG_INF + self.peak_val_imag = NEG_INF + self.ext_controller = controller + self.real = real + self.units = units + self.minval = minval + self.maxval = maxval + self.decimal_places = decimal_places + self.average_key = average_key + self.avg_alpha_key = avg_alpha_key + #setup the box with display and controls + self.control_panel = control_panel(self) + main_box = wx.BoxSizer(wx.HORIZONTAL) + sizer = wx.BoxSizer(wx.VERTICAL) + main_box.Add(sizer, 1, wx.EXPAND) + main_box.Add(self.control_panel, 0, wx.EXPAND) + sizer.Add(common.LabelText(self, title), 1, wx.ALIGN_CENTER) + self.text = wx.StaticText(self, size=(size[0], -1)) + sizer.Add(self.text, 1, wx.EXPAND) + self.gauge_real = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) + self.gauge_imag = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) + #hide/show gauges + self.show_gauges(show_gauge) + sizer.Add(self.gauge_real, 1, wx.EXPAND) + sizer.Add(self.gauge_imag, 1, wx.EXPAND) + self.SetSizerAndFit(main_box) + #initial setup + self.ext_controller[self.average_key] = self.ext_controller[self.average_key] + self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] + self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) + self._register_set_prop(self, RUNNING_KEY, True) + #register events + self.ext_controller.subscribe(msg_key, self.handle_msg) + + def show_gauges(self, show_gauge): + """! + Show or hide the gauges. + If this is real, never show the imaginary gauge. + @param show_gauge true to show + """ + if show_gauge: self.gauge_real.Show() + else: self.gauge_real.Hide() + if show_gauge and not self.real: self.gauge_imag.Show() + else: self.gauge_imag.Hide() + + def handle_msg(self, msg): + """! + Handle a message from the message queue. + Convert the string based message into a float or complex. + If more than one number was read, only take the last number. + Perform peak hold operations, set the gauges and display. + @param msg the number sample as a character array + """ + if not self[RUNNING_KEY]: return + #set gauge + def set_gauge_value(gauge, value): + gauge_val = DEFAULT_GAUGE_RANGE*(value-self.minval)/(self.maxval-self.minval) + gauge_val = max(0, gauge_val) #clip + gauge_val = min(DEFAULT_GAUGE_RANGE, gauge_val) #clip + gauge.SetValue(gauge_val) + format_string = "%%.%df"%self.decimal_places + if self.real: + sample = numpy.fromstring(msg, numpy.float32)[-1] + if self[PEAK_HOLD_KEY]: sample = self.peak_val_real = max(self.peak_val_real, sample) + label_text = "%s %s"%(format_string%sample, self.units) + set_gauge_value(self.gauge_real, sample) + else: + sample = numpy.fromstring(msg, numpy.complex64)[-1] + if self[PEAK_HOLD_KEY]: + self.peak_val_real = max(self.peak_val_real, sample.real) + self.peak_val_imag = max(self.peak_val_imag, sample.imag) + sample = self.peak_val_real + self.peak_val_imag*1j + label_text = "%s + %sj %s"%(format_string%sample.real, format_string%sample.imag, self.units) + set_gauge_value(self.gauge_real, sample.real) + set_gauge_value(self.gauge_imag, sample.imag) + #set label text + self.text.SetLabel(label_text) + #clear peak hold + if not self[PEAK_HOLD_KEY]: + self.peak_val_real = NEG_INF + self.peak_val_imag = NEG_INF diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py old mode 100755 new mode 100644 index 63bfe25c9..43eb9e493 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -1,481 +1,150 @@ -#!/usr/bin/env python # -# Copyright 2003,2004,2005,2006,2007,2008 Free Software Foundation, Inc. -# +# 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., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. -# - -from gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui2 -import wx -import gnuradio.wxgui.plot as plot -import numpy -import threading -import math - -default_numbersink_size = (640,240) -default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15) - -class number_sink_base(object): - def __init__(self, input_is_real=False, unit='',base_value=0, minval=-100.0,maxval=100.0,factor=1.0,decimal_places=10, ref_level=50, - sample_rate=1, - number_rate=default_number_rate, - average=False, avg_alpha=None, label='', peak_hold=False): - - # initialize common attributes - self.unit=unit - self.base_value = base_value - self.minval=minval - self.maxval=maxval - self.factor=factor - self.y_divs = 8 - self.decimal_places=decimal_places - self.ref_level = ref_level - self.sample_rate = sample_rate - number_size=1 - self.number_size = number_size - self.number_rate = number_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / number_rate - else: - self.avg_alpha = avg_alpha - self.label = label - self.peak_hold = peak_hold - self.show_gauge = True - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages - - def set_decimal_places(self, decimal_places): - self.decimal_places = decimal_places - - def set_ref_level(self, ref_level): - self.ref_level = ref_level - - def print_current_value(self, comment): - print comment,self.win.current_value - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - self.set_peak_hold(False) - else: - self.avg.set_taps(1.0) - - def set_peak_hold(self, enable): - self.peak_hold = enable - if enable: - self.set_average(False) - self.win.set_peak_hold(enable) - - def set_show_gauge(self, enable): - self.show_gauge = enable - self.win.set_show_gauge(enable) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_base_value(self, base_value): - self.base_value = base_value - - -class number_sink_f(gr.hier_block2, number_sink_base): - def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, - decimal_places=10, ref_level=50, sample_rate=1, - number_rate=default_number_rate, average=False, avg_alpha=None, - label='', size=default_numbersink_size, peak_hold=False): - - gr.hier_block2.__init__(self, "number_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), # Input signature - gr.io_signature(0, 0, 0)) # Output signature - - number_sink_base.__init__(self, unit=unit, input_is_real=True, base_value=base_value, - minval=minval,maxval=maxval,factor=factor, - decimal_places=decimal_places, ref_level=ref_level, - sample_rate=sample_rate, number_rate=number_rate, - average=average, avg_alpha=avg_alpha, label=label, - peak_hold=peak_hold) - - number_size=1 - one_in_n = gr.keep_one_in_n(gr.sizeof_float, - max(1, int(sample_rate/number_rate))) - - self.avg = gr.single_pole_iir_filter_ff(1.0, number_size) - sink = gr.message_sink(gr.sizeof_float , self.msgq, True) - self.connect(self, self.avg, one_in_n, sink) - - self.win = number_window(self, parent, size=size,label=label) - self.set_average(self.average) - self.set_peak_hold(self.peak_hold) - -class number_sink_c(gr.hier_block2, number_sink_base): - def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0, - decimal_places=10, ref_level=50, sample_rate=1, - number_rate=default_number_rate, average=False, avg_alpha=None, - label='', size=default_numbersink_size, peak_hold=False): - - gr.hier_block2.__init__(self, "number_sink_c", - gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature - gr.io_signature(0, 0, 0)) # Output signature - - number_sink_base.__init__(self, unit=unit, input_is_real=False, base_value=base_value,factor=factor, - minval=minval,maxval=maxval,decimal_places=decimal_places, ref_level=ref_level, - sample_rate=sample_rate, number_rate=number_rate, - average=average, avg_alpha=avg_alpha, label=label, - peak_hold=peak_hold) - - number_size=1 - one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex, - max(1, int(sample_rate/number_rate))) - - self.avg = gr.single_pole_iir_filter_cc(1.0, number_size) - sink = gr.message_sink(gr.sizeof_gr_complex , self.msgq, True) - self.connect(self, self.avg, one_in_n, sink) - - self.win = number_window(self, parent, size=size,label=label) - self.set_average(self.average) - self.set_peak_hold(self.peak_hold) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, number_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.number_size = number_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one number in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - -#======================================================================================== -class static_text_window (wx.StaticText): #plot.PlotCanvas): - def __init__ (self, parent, numbersink,id = -1,label="number", - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - wx.StaticText.__init__(self, parent, id, label, pos, size, style, name) - self.parent=parent - self.label=label - self.numbersink = numbersink - self.peak_hold = False - self.peak_vals = None - self.build_popup_menu() - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - def on_close_window (self, event): - print "number_window:on_close_window" - self.keep_running = False - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def update_y_range (self): - ymax = self.numbersink.ref_level - ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) - - def on_average(self, evt): - # print "on_average" - self.numbersink.set_average(evt.IsChecked()) - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.numbersink.set_peak_hold(evt.IsChecked()) - - def on_show_gauge(self, evt): - # print "on_show_gauge" - self.numbersink.set_show_gauge(evt.IsChecked()) - print evt.IsChecked() - - def on_incr_ref_level(self, evt): - # print "on_incr_ref_level" - self.numbersink.set_ref_level(self.numbersink.ref_level - + self.numbersink.decimal_places) - - def on_decr_ref_level(self, evt): - # print "on_decr_ref_level" - self.numbersink.set_ref_level(self.numbersink.ref_level - - self.numbersink.decimal_places) - - def on_incr_decimal_places(self, evt): - # print "on_incr_decimal_places" - self.numbersink.set_decimal_places(self.numbersink.decimal_places+1) - - def on_decr_decimal_places(self, evt): - # print "on_decr_decimal_places" - self.numbersink.set_decimal_places(max(self.numbersink.decimal_places-1,0)) - - def on_decimal_places(self, evt): - # print "on_decimal_places" - Id = evt.GetId() - if Id == self.id_decimal_places_0: - self.numbersink.set_decimal_places(0) - elif Id == self.id_decimal_places_1: - self.numbersink.set_decimal_places(1) - elif Id == self.id_decimal_places_2: - self.numbersink.set_decimal_places(2) - elif Id == self.id_decimal_places_3: - self.numbersink.set_decimal_places(3) - elif Id == self.id_decimal_places_6: - self.numbersink.set_decimal_places(6) - elif Id == self.id_decimal_places_9: - self.numbersink.set_decimal_places(9) - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - def build_popup_menu(self): - self.id_show_gauge = wx.NewId() - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_decimal_places = wx.NewId() - self.id_decr_decimal_places = wx.NewId() - self.id_decimal_places_0 = wx.NewId() - self.id_decimal_places_1 = wx.NewId() - self.id_decimal_places_2 = wx.NewId() - self.id_decimal_places_3 = wx.NewId() - self.id_decimal_places_6 = wx.NewId() - self.id_decimal_places_9 = wx.NewId() - self.id_average = wx.NewId() - self.id_peak_hold = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) - #self.Bind(wx.EVT_MENU, self.on_hide_gauge, id=self.id_hide_gauge) - self.Bind(wx.EVT_MENU, self.on_show_gauge, id=self.id_show_gauge) - self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - self.Bind(wx.EVT_MENU, self.on_incr_decimal_places, id=self.id_incr_decimal_places) - self.Bind(wx.EVT_MENU, self.on_decr_decimal_places, id=self.id_decr_decimal_places) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_0) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_1) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_2) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_3) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_6) - self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_9) - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") - menu.AppendCheckItem(self.id_show_gauge, "Show gauge") - menu.Append(self.id_incr_ref_level, "Incr Ref Level") - menu.Append(self.id_decr_ref_level, "Decr Ref Level") - menu.Append(self.id_incr_decimal_places, "Incr decimal places") - menu.Append(self.id_decr_decimal_places, "Decr decimal places") - menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - menu.AppendCheckItem(self.id_decimal_places_0, "0 decimal places") - menu.AppendCheckItem(self.id_decimal_places_1, "1 decimal places") - menu.AppendCheckItem(self.id_decimal_places_2, "2 decimal places") - menu.AppendCheckItem(self.id_decimal_places_3, "3 decimal places") - menu.AppendCheckItem(self.id_decimal_places_6, "6 decimal places") - menu.AppendCheckItem(self.id_decimal_places_9, "9 decimal places") - - self.checkmarks = { - self.id_average : lambda : self.numbersink.average, - self.id_peak_hold : lambda : self.numbersink.peak_hold, - self.id_show_gauge : lambda : self.numbersink.show_gauge, - self.id_decimal_places_0 : lambda : self.numbersink.decimal_places == 0, - self.id_decimal_places_1 : lambda : self.numbersink.decimal_places == 1, - self.id_decimal_places_2 : lambda : self.numbersink.decimal_places == 2, - self.id_decimal_places_3 : lambda : self.numbersink.decimal_places == 3, - self.id_decimal_places_6 : lambda : self.numbersink.decimal_places == 6, - self.id_decimal_places_9 : lambda : self.numbersink.decimal_places == 9, - } - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -#======================================================================================== -class number_window (plot.PlotCanvas): - def __init__ (self, numbersink, parent, id = -1,label="number", - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - self.static_text=static_text_window( self, numbersink,id, label, pos, (size[0]/2,size[1]/2), style, name) - gauge_style = wx.GA_HORIZONTAL - vbox=wx.BoxSizer(wx.VERTICAL) - vbox.Add (self.static_text, 0, wx.EXPAND) - self.current_value=None - if numbersink.input_is_real: - self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style, name = "gauge") - vbox.Add (self.gauge, 1, wx.EXPAND) - else: - self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge") - self.gauge_imag=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]*2/3),size=(size[0]/2,size[1]/3), style=gauge_style, name = "gauge_imag") - vbox.Add (self.gauge, 1, wx.EXPAND) - vbox.Add (self.gauge_imag, 1, wx.EXPAND) - self.sizer = vbox - self.SetSizer (self.sizer) - self.SetAutoLayout (True) - self.sizer.Fit (self) - - self.label=label - self.numbersink = numbersink - self.peak_hold = False - self.peak_vals = None - - EVT_DATA_EVENT (self, self.set_data) - wx.EVT_CLOSE (self, self.on_close_window) - self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self) - - def on_close_window (self, event): - # print "number_window:on_close_window" - self.keep_running = False - - def set_show_gauge(self, enable): - self.show_gauge = enable - if enable: - self.gauge.Show() - if not self.numbersink.input_is_real: - self.gauge_imag.Show() - #print 'show' - else: - self.gauge.Hide() - if not self.numbersink.input_is_real: - self.gauge_imag.Hide() - #print 'hide' - - def set_data (self, evt): - numbers = evt.data - L = len (numbers) - - if self.peak_hold: - if self.peak_vals is None: - self.peak_vals = numbers - else: - self.peak_vals = numpy.maximum(numbers, self.peak_vals) - numbers = self.peak_vals - - if self.numbersink.input_is_real: - real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value - imag_value=0.0 - self.current_value=real_value - else: - real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value - imag_value=numbers[1]*self.numbersink.factor + self.numbersink.base_value - self.current_value=complex(real_value,imag_value) - x = max(real_value, imag_value) - if x >= 1e9: - sf = 1e-9 - unit_prefix = "G" - elif x >= 1e6: - sf = 1e-6 - unit_prefix = "M" - elif x>= 1e3: - sf = 1e-3 - unit_prefix = "k" - else : - sf = 1 - unit_prefix = "" - if self.numbersink.input_is_real: - showtext = "%s: %.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,unit_prefix,self.numbersink.unit) - else: - showtext = "%s: %.*f,%.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf, - self.numbersink.decimal_places,imag_value*sf,unit_prefix,self.numbersink.unit) - self.static_text.SetLabel(showtext) - self.gauge.SetValue(int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) - if not self.numbersink.input_is_real: - self.gauge.SetValue(int(float((imag_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500) - - def set_peak_hold(self, enable): - self.peak_hold = enable - self.peak_vals = None - - def update_y_range (self): - ymax = self.numbersink.ref_level - ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs - self.y_range = self._axisInterval ('min', ymin, ymax) - - def on_average(self, evt): - # print "on_average" - self.numbersink.set_average(evt.IsChecked()) - - def on_peak_hold(self, evt): - # print "on_peak_hold" - self.numbersink.set_peak_hold(evt.IsChecked()) +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# +################################################## +# Imports +################################################## +import number_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# Number sink block (wrapper for old wxgui) +################################################## +class _number_sink_base(gr.hier_block2, common.prop_setter): + """! + An decimator block with a number window display + """ + + def __init__( + self, + parent, + unit='units', + base_value=None, #ignore (old wrapper) + minval=0, + maxval=1, + factor=1, + decimal_places=3, + ref_level=0, + sample_rate=1, + number_rate=number_window.DEFAULT_NUMBER_RATE, + average=False, + avg_alpha=None, + label='Number Plot', + size=number_window.DEFAULT_WIN_SIZE, + peak_hold=False, + show_gauge=True, + ): + #ensure avg alpha + if avg_alpha is None: avg_alpha = 2.0/number_rate + #init + gr.hier_block2.__init__( + self, + "number_sink", + gr.io_signature(1, 1, self._item_size), + gr.io_signature(0, 0, 0), + ) + #blocks + sd = blks2.stream_to_vector_decimator( + item_size=self._item_size, + sample_rate=sample_rate, + vec_rate=number_rate, + vec_len=1, + ) + if self._real: + mult = gr.multiply_const_ff(factor) + add = gr.add_const_ff(ref_level) + self._avg = gr.single_pole_iir_filter_ff(1.0) + else: + mult = gr.multiply_const_cc(factor) + add = gr.add_const_cc(ref_level) + self._avg = gr.single_pole_iir_filter_cc(1.0) + msgq = gr.msg_queue(2) + sink = gr.message_sink(self._item_size, msgq, True) + #connect + self.connect(self, sd, mult, add, self._avg, sink) + #setup averaging + self._avg_alpha = avg_alpha + self.set_average(average) + self.set_avg_alpha(avg_alpha) + #controller + self.controller = pubsub() + self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) + self.controller.subscribe(AVERAGE_KEY, self.set_average) + self.controller.publish(AVERAGE_KEY, self.get_average) + self.controller.subscribe(AVG_ALPHA_KEY, self.set_avg_alpha) + self.controller.publish(AVG_ALPHA_KEY, self.get_avg_alpha) + #start input watcher + def set_msg(msg): self.controller[MSG_KEY] = msg + common.input_watcher(msgq, set_msg) + #create window + self.win = number_window.number_window( + parent=parent, + controller=self.controller, + size=size, + title=label, + units=unit, + real=self._real, + minval=minval, + maxval=maxval, + decimal_places=decimal_places, + show_gauge=show_gauge, + average_key=AVERAGE_KEY, + avg_alpha_key=AVG_ALPHA_KEY, + peak_hold=peak_hold, + msg_key=MSG_KEY, + ) + #register callbacks from window for external use + for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): + setattr(self, attr, getattr(self.win, attr)) + self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + + def get_average(self): return self._average + def set_average(self, average): + self._average = average + if self.get_average(): self._avg.set_taps(self.get_avg_alpha()) + else: self._avg.set_taps(1.0) + + def get_avg_alpha(self): return self._avg_alpha + def set_avg_alpha(self, avg_alpha): + self._avg_alpha = avg_alpha + self.set_average(self.get_average()) + +class number_sink_f(_number_sink_base): + _item_size = gr.sizeof_float + _real = True + +class number_sink_c(_number_sink_base): + _item_size = gr.sizeof_gr_complex + _real = False # ---------------------------------------------------------------- # Standalone test app # ---------------------------------------------------------------- +import wx +from gnuradio.wxgui import stdgui2 + class test_app_flow_graph (stdgui2.std_top_block): def __init__(self, frame, panel, vbox, argv): stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) @@ -484,20 +153,20 @@ class test_app_flow_graph (stdgui2.std_top_block): input_rate = 20.48e3 # Generate a real and complex sinusoids - src1 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + src1 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2.21e3, 1) + src2 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2.21e3, 1) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. thr1 = gr.throttle(gr.sizeof_float, input_rate) thr2 = gr.throttle(gr.sizeof_gr_complex, input_rate) - sink1 = number_sink_f (panel, unit='Hz',label="Real Data", avg_alpha=0.001, - sample_rate=input_rate, base_value=100e3, + sink1 = number_sink_f (panel, unit='V',label="Real Data", avg_alpha=0.001, + sample_rate=input_rate, minval=-1, maxval=1, ref_level=0, decimal_places=3) vbox.Add (sink1.win, 1, wx.EXPAND) sink2 = number_sink_c (panel, unit='V',label="Complex Data", avg_alpha=0.001, - sample_rate=input_rate, base_value=0, + sample_rate=input_rate, minval=-1, maxval=1, ref_level=0, decimal_places=3) vbox.Add (sink2.win, 1, wx.EXPAND) @@ -510,3 +179,4 @@ def main (): if __name__ == '__main__': main () + diff --git a/gr-wxgui/src/python/plotter/Makefile.am b/gr-wxgui/src/python/plotter/Makefile.am new file mode 100644 index 000000000..ada506794 --- /dev/null +++ b/gr-wxgui/src/python/plotter/Makefile.am @@ -0,0 +1,37 @@ +# +# Copyright 2004,2005,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. +# + +include $(top_srcdir)/Makefile.common + +# Install this stuff so that it ends up as the gnuradio.wxgui module +# This usually ends up at: +# ${prefix}/lib/python${python_version}/site-packages/gnuradio/wxgui + +ourpythondir = $(grpythondir)/wxgui/plotter +ourlibdir = $(grpyexecdir)/wxgui/plotter + +ourpython_PYTHON = \ + __init__.py \ + channel_plotter.py \ + gltext.py \ + plotter_base.py \ + waterfall_plotter.py + diff --git a/gr-wxgui/src/python/plotter/__init__.py b/gr-wxgui/src/python/plotter/__init__.py new file mode 100644 index 000000000..12f8b3450 --- /dev/null +++ b/gr-wxgui/src/python/plotter/__init__.py @@ -0,0 +1,23 @@ +# +# 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 diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py new file mode 100644 index 000000000..22126bd0b --- /dev/null +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -0,0 +1,225 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +from plotter_base import grid_plotter_base +from OpenGL.GL import * +from gnuradio.wxgui import common +import numpy +import gltext +import math + +LEGEND_TEXT_FONT_SIZE = 8 +LEGEND_BOX_PADDING = 3 +PADDING = 35, 15, 40, 60 #top, right, bottom, left +#constants for the waveform storage +SAMPLES_KEY = 'samples' +COLOR_SPEC_KEY = 'color_spec' +MARKERY_KEY = 'marker' + +################################################## +# 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, PADDING) + self._channels = dict() + self.enable_legend(False) + + def _gl_init(self): + """! + Run gl initialization tasks. + """ + glEnableClientState(GL_VERTEX_ARRAY) + self._grid_compiled_list_id = glGenLists(1) + + def enable_legend(self, enable=None): + """! + Enable/disable the legend. + @param enable true to enable + @return the enable state when None + """ + if enable is None: return self._enable_legend + self.lock() + self._enable_legend = enable + self.changed(True) + self.unlock() + + def draw(self): + """! + Draw the grid and waveforms. + """ + self.lock() + self.clear() + #store the grid drawing operations + if self.changed(): + glNewList(self._grid_compiled_list_id, GL_COMPILE) + self._draw_grid() + self._draw_legend() + glEndList() + self.changed(False) + #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) + self._draw_point_label() + #swap buffer into display + self.SwapBuffers() + self.unlock() + + def _draw_waveforms(self): + """! + Draw the waveforms for each channel. + Scale the waveform data to the grid using gl matrix operations. + """ + for channel in reversed(sorted(self._channels.keys())): + samples = self._channels[channel][SAMPLES_KEY] + 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(*self._channels[channel][COLOR_SPEC_KEY]) + marker = self._channels[channel][MARKERY_KEY] + if marker: glPointSize(marker) + glVertexPointer(2, GL_FLOAT, 0, points) + glDrawArrays(marker is None and GL_LINE_STRIP or GL_POINTS, 0, len(points)) + glPopMatrix() + + 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. + @param x_val the current x value + @param y_val the current y value + @return a string with newlines + """ + #create text + label_str = '%s: %s %s\n%s: %s %s'%( + self.x_label, + common.label_format(x_val), + self.x_units, self.y_label, + common.label_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_scalar-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)) + y_value = (samples[x_index_high] - samples[x_index_low])*(x_index - x_index_low) + samples[x_index_low] + label_str += '\n%s: %s %s'%(channel, common.label_format(y_value), self.y_units) + return label_str + + 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 + 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 set_waveform(self, channel, samples, color_spec, marker=None): + """! + Set the waveform for a given channel. + @param channel the channel key + @param samples the waveform samples + @param color_spec the 3-tuple for line color + @param marker None for line + """ + self.lock() + if channel not in self._channels.keys(): self.changed(True) + self._channels[channel] = { + SAMPLES_KEY: samples, + COLOR_SPEC_KEY: color_spec, + MARKERY_KEY: marker, + } + 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/src/python/plotter/gltext.py b/gr-wxgui/src/python/plotter/gltext.py new file mode 100644 index 000000000..67f62ca56 --- /dev/null +++ b/gr-wxgui/src/python/plotter/gltext.py @@ -0,0 +1,503 @@ +#!/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, + 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/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py new file mode 100644 index 000000000..96a1869da --- /dev/null +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -0,0 +1,390 @@ +# +# 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 * +from gnuradio.wxgui import common +import threading +import gltext +import math +import time + +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 +TICK_LABEL_PADDING = 5 +POINT_LABEL_FONT_SIZE = 8 +POINT_LABEL_COLOR_SPEC = (1, 1, .5) +POINT_LABEL_PADDING = 3 + +################################################## +# OpenGL WX Plotter Canvas +################################################## +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 + """ + self._semaphore = threading.Semaphore(1) + wx.glcanvas.GLCanvas.__init__(self, parent, -1) + self.changed(False) + self._gl_init_flag = False + self._resized_flag = True + self._update_ts = 0 + self.Bind(wx.EVT_PAINT, self._on_paint) + self.Bind(wx.EVT_SIZE, self._on_size) + + def lock(self): self._semaphore.acquire(True) + def unlock(self): self._semaphore.release() + + def _on_size(self, event): + """! + Flag the resize event. + The paint event will handle the actual resizing. + """ + self._resized_flag = True + + def _on_paint(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) + self._gl_init() + self._gl_init_flag = True + #check for a change in window size + if self._resized_flag: + self.lock() + 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) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + glViewport(0, 0, self.width, self.height) + self._resized_flag = False + self.changed(True) + self.unlock() + self.draw() + + def update(self): + """! + Force a paint event. + Record the timestamp. + """ + wx.PostEvent(self, wx.PaintEvent()) + self._update_ts = time.time() + + def clear(self): glClear(GL_COLOR_BUFFER_BIT) + + def changed(self, state=None): + """! + Set the changed flag if state is not None. + Otherwise return the changed flag. + """ + if state is not None: self._changed = state + else: return self._changed + +################################################## +# Grid Plotter Base Class +################################################## +class grid_plotter_base(_plotter_base): + + def __init__(self, parent, padding): + _plotter_base.__init__(self, parent) + self.padding_top, self.padding_right, self.padding_bottom, self.padding_left = 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 + self.enable_point_label(False) + self._mouse_coordinate = None + self.Bind(wx.EVT_MOTION, self._on_motion) + self.Bind(wx.EVT_LEAVE_WINDOW, self._on_leave_window) + + def _on_motion(self, event): + """! + Mouse motion, record the position X, Y. + """ + self.lock() + self._mouse_coordinate = event.GetPosition() + #update based on last known update time + if time.time() - self._update_ts > 0.03: self.update() + self.unlock() + + def _on_leave_window(self, event): + """! + Mouse leave window, set the position to None. + """ + self.lock() + self._mouse_coordinate = None + self.update() + self.unlock() + + def enable_point_label(self, enable=None): + """! + Enable/disable the point label. + @param enable true to enable + @return the enable state when None + """ + if enable is None: return self._enable_point_label + self.lock() + self._enable_point_label = enable + self.changed(True) + self.unlock() + + def set_title(self, title): + """! + Set the title. + @param title the title string + """ + self.lock() + self.title = title + self.changed(True) + self.unlock() + + def set_x_label(self, x_label, x_units=''): + """! + Set the x label and units. + @param x_label the x label string + @param x_units the x units string + """ + self.lock() + self.x_label = x_label + self.x_units = x_units + self.changed(True) + self.unlock() + + def set_y_label(self, y_label, y_units=''): + """! + Set the y label and units. + @param y_label the y label string + @param y_units the y units string + """ + self.lock() + self.y_label = y_label + self.y_units = y_units + self.changed(True) + self.unlock() + + def set_x_grid(self, x_min, x_max, x_step, x_scalar=1.0): + """! + 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 + @param x_scalar the scalar factor + """ + self.lock() + self.x_min = float(x_min) + self.x_max = float(x_max) + self.x_step = float(x_step) + self.x_scalar = float(x_scalar) + self.changed(True) + self.unlock() + + def set_y_grid(self, y_min, y_max, y_step, y_scalar=1.0): + """! + 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 + @param y_scalar the scalar factor + """ + self.lock() + self.y_min = float(y_min) + self.y_max = float(y_max) + self.y_step = float(y_step) + self.y_scalar = float(y_scalar) + self.changed(True) + self.unlock() + + def _draw_grid(self): + """! + Draw the border, grid, title, and units. + """ + ################################################## + # 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, self.x_scalar): + 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 + glColor3f(*GRID_LINE_COLOR_SPEC) + self._draw_line( + (scaled_tick, self.padding_top, 0), + (scaled_tick, self.height-self.padding_bottom, 0), + ) + txt = self._get_tick_label(tick) + w, h = txt.get_size() + txt.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) + ################################################## + # Draw Grid Y + ################################################## + for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar): + 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 + glColor3f(*GRID_LINE_COLOR_SPEC) + self._draw_line( + (self.padding_left, scaled_tick, 0), + (self.width-self.padding_right, scaled_tick, 0), + ) + txt = self._get_tick_label(tick) + w, h = txt.get_size() + txt.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) + ################################################## + # Draw Title + ################################################## + #draw x units + txt = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) + txt.draw_text(wx.Point(self.width/2.0, .5*self.padding_top)) + ################################################## + # Draw Labels + ################################################## + #draw x labels + x_label_str = self.x_units and "%s (%s)"%(self.x_label, self.x_units) or self.x_label + txt = gltext.Text(x_label_str, bold=True, 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 labels + y_label_str = self.y_units and "%s (%s)"%(self.y_label, self.y_units) or self.y_label + txt = gltext.Text(y_label_str, bold=True, 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, + ) + + def _get_tick_label(self, tick): + """! + Format the tick value and create a gl text. + @param tick the floating point tick value + @return the tick label text + """ + tick_str = common.label_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. + @param min the lower bound + @param max the upper bound + @param step the grid spacing + @param scalar the grid scaling + @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*scalar 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, fill=True): + """! + 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 + @param fill true to color inside of rectangle + """ + glBegin(fill and GL_QUADS or GL_LINE_LOOP) + glVertex2f(x, y) + glVertex2f(x+width, y) + glVertex2f(x+width, y+height) + glVertex2f(x, y+height) + 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._mouse_coordinate: return + x, y = self._mouse_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 = self.x_scalar*(x_win_scalar*(self.x_max-self.x_min) + self.x_min) + y_val = self.y_scalar*(y_win_scalar*(self.y_max-self.y_min) + self.y_min) + #create text + label_str = self._populate_point_label(x_val, y_val) + txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) + w, h = txt.get_size() + #draw rect + text + glColor3f(*POINT_LABEL_COLOR_SPEC) + if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + 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/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py new file mode 100644 index 000000000..88e2b4dc1 --- /dev/null +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -0,0 +1,282 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +from plotter_base import grid_plotter_base +from OpenGL.GL import * +from gnuradio.wxgui import common +import numpy +import gltext +import math + +LEGEND_LEFT_PAD = 7 +LEGEND_NUM_BLOCKS = 256 +LEGEND_NUM_LABELS = 9 +LEGEND_WIDTH = 8 +LEGEND_FONT_SIZE = 8 +LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black +PADDING = 35, 60, 40, 60 #top, right, bottom, left + +ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2))) + +def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]): + """! + Get an array of 256 rgba values where each index maps to a color. + The scaling for red, green, blue, alpha are specified in piece-wise functions. + The piece-wise functions consist of a set of x, y coordinates. + The x and y values of the coordinates range from 0 to 1. + The coordinates must be specified so that x increases with the index value. + Resulting values are calculated along the line formed between 2 coordinates. + @param *_pts an array of x,y coordinates for each color element + @return array of rbga values (4 bytes) each + """ + def _fcn(x, pw): + for (x1, y1), (x2, y2) in zip(pw, pw[1:]): + #linear interpolation + if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1 + raise Exception + return [numpy.array(map( + lambda pw: int(255*_fcn(i/255.0, pw)), + (red_pts, green_pts, blue_pts, alpha_pts), + ), numpy.uint8).tostring() for i in range(0, 256) + ] + +COLORS = { + 'rgb1': _get_rbga( #http://www.ks.uiuc.edu/Research/vmd/vmd-1.7.1/ug/img47.gif + red_pts = [(0, 0), (.5, 0), (1, 1)], + green_pts = [(0, 0), (.5, 1), (1, 0)], + blue_pts = [(0, 1), (.5, 0), (1, 0)], + ), + 'rgb2': _get_rbga( #http://xtide.ldeo.columbia.edu/~krahmann/coledit/screen.jpg + red_pts = [(0, 0), (3.0/8, 0), (5.0/8, 1), (7.0/8, 1), (1, .5)], + green_pts = [(0, 0), (1.0/8, 0), (3.0/8, 1), (5.0/8, 1), (7.0/8, 0), (1, 0)], + blue_pts = [(0, .5), (1.0/8, 1), (3.0/8, 1), (5.0/8, 0), (1, 0)], + ), + 'rgb3': _get_rbga( + red_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 0), (1, 1)], + green_pts = [(0, 0), (1.0/3.0, 0), (2.0/3.0, 1), (1, 0)], + blue_pts = [(0, 0), (1.0/3.0, 1), (2.0/3.0, 0), (1, 0)], + ), + 'gray': _get_rbga( + red_pts = [(0, 0), (1, 1)], + green_pts = [(0, 0), (1, 1)], + blue_pts = [(0, 0), (1, 1)], + ), +} + +################################################## +# Waterfall Plotter +################################################## +class waterfall_plotter(grid_plotter_base): + def __init__(self, parent): + """! + Create a new channel plotter. + """ + #init + grid_plotter_base.__init__(self, parent, PADDING) + self._resize_texture(False) + self._minimum = 0 + self._maximum = 0 + self._fft_size = 1 + self._buffer = list() + self._pointer = 0 + self._counter = 0 + self.set_num_lines(0) + self.set_color_mode(COLORS.keys()[0]) + + def _gl_init(self): + """! + Run gl initialization tasks. + """ + self._grid_compiled_list_id = glGenLists(1) + self._waterfall_texture = glGenTextures(1) + + def draw(self): + """! + Draw the grid and waveforms. + """ + self.lock() + #resize texture + self._resize_texture() + #store the grid drawing operations + if self.changed(): + glNewList(self._grid_compiled_list_id, GL_COMPILE) + self._draw_grid() + self._draw_legend() + glEndList() + self.changed(False) + self.clear() + #draw the grid + glCallList(self._grid_compiled_list_id) + self._draw_waterfall() + self._draw_point_label() + #swap buffer into display + self.SwapBuffers() + self.unlock() + + def _draw_waterfall(self): + """! + Draw the waterfall from the texture. + The texture is circularly filled and will wrap around. + Use matrix modeling to shift and scale the texture onto the coordinate plane. + """ + #setup texture + glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) + #write the buffer to the texture + while self._buffer: + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL_RGBA, GL_UNSIGNED_BYTE, self._buffer.pop(0)) + self._pointer = (self._pointer + 1)%self._num_lines + #begin drawing + glEnable(GL_TEXTURE_2D) + glPushMatrix() + #matrix scaling + glTranslatef(self.padding_left+1, self.padding_top, 0) + glScalef( + float(self.width-self.padding_left-self.padding_right-1), + float(self.height-self.padding_top-self.padding_bottom-1), + 1.0, + ) + #draw texture with wrapping + glBegin(GL_QUADS) + prop_y = float(self._pointer)/(self._num_lines-1) + prop_x = float(self._fft_size)/ceil_log2(self._fft_size) + off = 1.0/(self._num_lines-1) + glTexCoord2f(0, prop_y+1-off) + glVertex2f(0, 1) + glTexCoord2f(prop_x, prop_y+1-off) + glVertex2f(1, 1) + glTexCoord2f(prop_x, prop_y) + glVertex2f(1, 0) + glTexCoord2f(0, prop_y) + glVertex2f(0, 0) + glEnd() + glPopMatrix() + glDisable(GL_TEXTURE_2D) + + def _populate_point_label(self, x_val, y_val): + """! + Get the text the will populate the point label. + Give the X value for the current point. + @param x_val the current x value + @param y_val the current y value + @return a value string with units + """ + return '%s: %s %s'%(self.x_label, common.label_format(x_val), self.x_units) + + def _draw_legend(self): + """! + Draw the color scale legend. + """ + if not self._color_mode: return + legend_height = self.height-self.padding_top-self.padding_bottom + #draw each legend block + block_height = float(legend_height)/LEGEND_NUM_BLOCKS + x = self.width - self.padding_right + LEGEND_LEFT_PAD + for i in range(LEGEND_NUM_BLOCKS): + color = COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))] + glColor4f(*map(lambda c: ord(c)/255.0, color)) + y = self.height - (i+1)*block_height - self.padding_bottom + self._draw_rect(x, y, LEGEND_WIDTH, block_height) + #draw rectangle around color scale border + glColor3f(*LEGEND_BORDER_COLOR_SPEC) + self._draw_rect(x, self.padding_top, LEGEND_WIDTH, legend_height, fill=False) + #draw each legend label + label_spacing = float(legend_height)/(LEGEND_NUM_LABELS-1) + x = self.width - (self.padding_right - LEGEND_LEFT_PAD - LEGEND_WIDTH)/2 + for i in range(LEGEND_NUM_LABELS): + proportion = i/float(LEGEND_NUM_LABELS-1) + dB = proportion*(self._maximum - self._minimum) + self._minimum + y = self.height - i*label_spacing - self.padding_bottom + txt = gltext.Text('%ddB'%int(dB), font_size=LEGEND_FONT_SIZE, centered=True) + txt.draw_text(wx.Point(x, y)) + + def _resize_texture(self, flag=None): + """! + Create the texture to fit the fft_size X num_lines. + @param flag the set/unset or update flag + """ + if flag is not None: + self._resize_texture_flag = flag + return + if not self._resize_texture_flag: return + self._buffer = list() + self._pointer = 0 + if self._num_lines and self._fft_size: + glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) + data = numpy.zeros(self._num_lines*self._fft_size*4, numpy.uint8).tostring() + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) + self._resize_texture_flag = False + + def set_color_mode(self, color_mode): + """! + Set the color mode. + New samples will be converted to the new color mode. + Old samples will not be recolorized. + @param color_mode the new color mode string + """ + self.lock() + if color_mode in COLORS.keys(): + self._color_mode = color_mode + self.changed(True) + self.update() + self.unlock() + + def set_num_lines(self, num_lines): + """! + Set number of lines. + Powers of two only. + @param num_lines the new number of lines + """ + self.lock() + self._num_lines = num_lines + self._resize_texture(True) + self.update() + self.unlock() + + def set_samples(self, samples, minimum, maximum): + """! + Set the samples to the waterfall. + Convert the samples to color data. + @param samples the array of floats + @param minimum the minimum value to scale + @param maximum the maximum value to scale + """ + self.lock() + #set the min, max values + if self._minimum != minimum or self._maximum != maximum: + self._minimum = minimum + self._maximum = maximum + self.changed(True) + if self._fft_size != len(samples): + self._fft_size = len(samples) + self._resize_texture(True) + #normalize the samples to min/max + samples = (samples - minimum)*float(255/(maximum-minimum)) + samples = numpy.clip(samples, 0, 255) #clip + samples = numpy.array(samples, numpy.uint8) + #convert the samples to RGBA data + data = numpy.choose(samples, COLORS[self._color_mode]).tostring() + self._buffer.append(data) + self.unlock() diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py old mode 100755 new mode 100644 diff --git a/gr-wxgui/src/python/pubsub.py b/gr-wxgui/src/python/pubsub.py new file mode 100644 index 000000000..18aa60603 --- /dev/null +++ b/gr-wxgui/src/python/pubsub.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# +# 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. +# + +"""! +Abstract GNU Radio publisher/subscriber interface + +This is a proof of concept implementation, will likely change significantly. +""" + +class pubsub(dict): + def __init__(self): + self._publishers = { } + self._subscribers = { } + self._proxies = { } + + def __missing__(self, key, value=None): + dict.__setitem__(self, key, value) + self._publishers[key] = None + self._subscribers[key] = [] + self._proxies[key] = None + + def __setitem__(self, key, val): + if not self.has_key(key): + self.__missing__(key, val) + elif self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + p[newkey] = val + else: + dict.__setitem__(self, key, val) + for sub in self._subscribers[key]: + # Note this means subscribers will get called in the thread + # context of the 'set' caller. + sub(val) + + def __getitem__(self, key): + if not self.has_key(key): self.__missing__(key) + if self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + return p[newkey] + elif self._publishers[key] is not None: + return self._publishers[key]() + else: + return dict.__getitem__(self, key) + + def publish(self, key, publisher): + if not self.has_key(key): self.__missing__(key) + if self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + p.publish(newkey, publisher) + else: + self._publishers[key] = publisher + + def subscribe(self, key, subscriber): + if not self.has_key(key): self.__missing__(key) + if self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + p.subscribe(newkey, subscriber) + else: + self._subscribers[key].append(subscriber) + + def unpublish(self, key): + if self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + p.unpublish(newkey) + else: + self._publishers[key] = None + + def unsubscribe(self, key, subscriber): + if self._proxies[key] is not None: + (p, newkey) = self._proxies[key] + p.unsubscribe(newkey, subscriber) + else: + self._subscribers[key].remove(subscriber) + + def proxy(self, key, p, newkey=None): + if not self.has_key(key): self.__missing__(key) + if newkey is None: newkey = key + self._proxies[key] = (p, newkey) + + def unproxy(self, key): + self._proxies[key] = None + +# Test code +if __name__ == "__main__": + import sys + o = pubsub() + + # Non-existent key gets auto-created with None value + print "Auto-created key 'foo' value:", o['foo'] + + # Add some subscribers + # First is a bare function + def print_len(x): + print "len=%i" % (len(x), ) + o.subscribe('foo', print_len) + + # The second is a class member function + class subber(object): + def __init__(self, param): + self._param = param + def printer(self, x): + print self._param, `x` + s = subber('param') + o.subscribe('foo', s.printer) + + # The third is a lambda function + o.subscribe('foo', lambda x: sys.stdout.write('val='+`x`+'\n')) + + # Update key 'foo', will notify subscribers + print "Updating 'foo' with three subscribers:" + o['foo'] = 'bar'; + + # Remove first subscriber + o.unsubscribe('foo', print_len) + + # Update now will only trigger second and third subscriber + print "Updating 'foo' after removing a subscriber:" + o['foo'] = 'bar2'; + + # Publish a key as a function, in this case, a lambda function + o.publish('baz', lambda : 42) + print "Published value of 'baz':", o['baz'] + + # Unpublish the key + o.unpublish('baz') + + # This will return None, as there is no publisher + print "Value of 'baz' with no publisher:", o['baz'] + + # Set 'baz' key, it gets cached + o['baz'] = 'bazzz' + + # Now will return cached value, since no provider + print "Cached value of 'baz' after being set:", o['baz'] diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py new file mode 100644 index 000000000..0c7326b1d --- /dev/null +++ b/gr-wxgui/src/python/scope_window.py @@ -0,0 +1,482 @@ +# +# 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 +################################################## +import plotter +import common +import wx +import numpy +import time +import pubsub +from constants import * + +################################################## +# Constants +################################################## +DEFAULT_FRAME_RATE = 30 +DEFAULT_WIN_SIZE = (600, 300) +DEFAULT_V_SCALE = 1000 +TRIGGER_MODES = ( + ('Off', 0), + ('Neg', -1), + ('Pos', +1), +) +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, style=wx.SUNKEN_BORDER) + self.control_box = 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 + self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY) + control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND) + #trigger level + self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0)) + control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND) + #trigger channel + choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] + self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0)) + control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND) + #axes options + SPACING = 15 + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + ################################################## + # Scope Mode Box + ################################################## + self.scope_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(self.scope_mode_box, 0, wx.EXPAND) + #x axis divs + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.scope_mode_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) + hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(SPACING) + #y axis divs + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.scope_mode_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) + parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) + hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(SPACING) + #y axis ref lvl + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.scope_mode_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL) + y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) + parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) + hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(SPACING) + ################################################## + # XY Mode Box + ################################################## + self.xy_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(self.xy_mode_box, 0, wx.EXPAND) + #x and y channel + CHOOSER_WIDTH = 60 + CENTER_SPACING = 10 + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.xy_mode_box.Add(hbox, 0, wx.EXPAND) + choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] + self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) + hbox.Add(self.channel_x_chooser, 0, wx.EXPAND) + hbox.AddSpacer(CENTER_SPACING) + self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) + hbox.Add(self.channel_y_chooser, 0, wx.EXPAND) + #div controls + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.xy_mode_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs) + parent.subscribe(AUTORANGE_KEY, x_buttons.Disable) + hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(CENTER_SPACING) + hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) + parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) + hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + #offset controls + hbox = wx.BoxSizer(wx.HORIZONTAL) + self.xy_mode_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL) + x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off) + parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable) + hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(CENTER_SPACING) + hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL) + y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) + parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) + hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + ################################################## + # End Special Boxes + ################################################## + #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 = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY) + control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT) + #autorange check box + self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) + control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) + #run/stop + control_box.AddStretchSpacer() + self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode') + parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode) + control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND) + #run/stop + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + + ################################################## + # Event handlers + ################################################## + def _on_scope_xy_mode(self, mode): + self.scope_mode_box.ShowItems(not mode) + self.xy_mode_box.ShowItems(mode) + self.control_box.Layout() + #incr/decr divs + def _on_incr_t_divs(self, event): + self.parent.set_t_per_div( + common.get_clean_incr(self.parent[T_PER_DIV_KEY])) + def _on_decr_t_divs(self, event): + self.parent.set_t_per_div( + common.get_clean_decr(self.parent[T_PER_DIV_KEY])) + def _on_incr_x_divs(self, event): + self.parent.set_x_per_div( + common.get_clean_incr(self.parent[X_PER_DIV_KEY])) + def _on_decr_x_divs(self, event): + self.parent.set_x_per_div( + common.get_clean_decr(self.parent[X_PER_DIV_KEY])) + def _on_incr_y_divs(self, event): + self.parent.set_y_per_div( + common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) + def _on_decr_y_divs(self, event): + self.parent.set_y_per_div( + common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) + #incr/decr offset + def _on_incr_t_off(self, event): + self.parent.set_t_off( + self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY]) + def _on_decr_t_off(self, event): + self.parent.set_t_off( + self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY]) + def _on_incr_x_off(self, event): + self.parent.set_x_off( + self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]) + def _on_decr_x_off(self, event): + self.parent.set_x_off( + self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]) + def _on_incr_y_off(self, event): + self.parent.set_y_off( + self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]) + def _on_decr_y_off(self, event): + self.parent.set_y_off( + self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]) + +################################################## +# Scope window with plotter and control panel +################################################## +class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): + def __init__( + self, + parent, + controller, + size, + title, + frame_rate, + num_inputs, + sample_rate_key, + t_scale, + v_scale, + ac_couple, + xy_mode, + scope_trigger_level_key, + scope_trigger_mode_key, + scope_trigger_channel_key, + msg_key, + ): + pubsub.pubsub.__init__(self) + #check num inputs + assert num_inputs <= len(CHANNEL_COLOR_SPECS) + #setup + self.ext_controller = controller + self.num_inputs = num_inputs + self.sample_rate_key = sample_rate_key + autorange = v_scale is None + self.autorange_ts = 0 + if v_scale is None: v_scale = 1 + self.frame_rate_ts = 0 + self._init = False #HACK + #scope keys + self.scope_trigger_level_key = scope_trigger_level_key + self.scope_trigger_mode_key = scope_trigger_mode_key + self.scope_trigger_channel_key = scope_trigger_channel_key + #init panel and plot + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + self.plotter = plotter.channel_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_legend(True) + self.plotter.enable_point_label(True) + #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) + #initial setup + self._register_set_prop(self, RUNNING_KEY, True) + self._register_set_prop(self, AC_COUPLE_KEY, ac_couple) + self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode) + self._register_set_prop(self, AUTORANGE_KEY, autorange) + self._register_set_prop(self, T_PER_DIV_KEY, t_scale) + self._register_set_prop(self, X_PER_DIV_KEY, v_scale) + self._register_set_prop(self, Y_PER_DIV_KEY, v_scale) + self._register_set_prop(self, T_OFF_KEY, 0) + self._register_set_prop(self, X_OFF_KEY, 0) + self._register_set_prop(self, Y_OFF_KEY, 0) + self._register_set_prop(self, T_DIVS_KEY, 8) + self._register_set_prop(self, X_DIVS_KEY, 8) + self._register_set_prop(self, Y_DIVS_KEY, 8) + self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0) + self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1) + self._register_set_prop(self, FRAME_RATE_KEY, frame_rate) + self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0) + self._register_set_prop(self, TRIGGER_MODE_KEY, 1) + self._register_set_prop(self, TRIGGER_LEVEL_KEY, None) + #register events + self.ext_controller.subscribe(msg_key, self.handle_msg) + for key in ( + T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY, + T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY, + T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY, + SCOPE_XY_MODE_KEY, + SCOPE_X_CHANNEL_KEY, + SCOPE_Y_CHANNEL_KEY, + AUTORANGE_KEY, + AC_COUPLE_KEY, + ): self.subscribe(key, self.update_grid) + #initial update, dont do this here, wait for handle_msg #HACK + #self.update_grid() + + def handle_msg(self, msg): + """! + Handle the message from the scope sink message queue. + Plot the list of arrays of samples onto the grid. + Each samples array gets its own channel. + @param msg the time domain data as a character array + """ + if not self[RUNNING_KEY]: return + #check time elapsed + if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return + #convert to floating point numbers + samples = numpy.fromstring(msg, numpy.float32) + samps_per_ch = len(samples)/self.num_inputs + self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)] + if not self._init: #HACK + self._init = True + self.update_grid() + #handle samples + self.handle_samples() + self.frame_rate_ts = time.time() + + def handle_samples(self): + """! + Handle the cached samples from the scope input. + Perform ac coupling, triggering, and auto ranging. + """ + sampleses = self.sampleses + #trigger level (must do before ac coupling) + self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY] + self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY] + trigger_level = self[TRIGGER_LEVEL_KEY] + if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = '' + else: + samples = sampleses[self[TRIGGER_CHANNEL_KEY]] + self.ext_controller[self.scope_trigger_level_key] = \ + trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples) + #ac coupling + if self[AC_COUPLE_KEY]: + sampleses = [samples - numpy.average(samples) for samples in sampleses] + if self[SCOPE_XY_MODE_KEY]: + x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]] + y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]] + #autorange + if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: + x_min, x_max = common.get_min_max(x_samples) + y_min, y_max = common.get_min_max(y_samples) + #adjust the x per div + x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY]) + if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div) + #adjust the x offset + x_off = x_per_div*round((x_max+x_min)/2/x_per_div) + if x_off != self[X_OFF_KEY]: self.set_x_off(x_off) + #adjust the y per div + y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) + if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + #adjust the y offset + y_off = y_per_div*round((y_max+y_min)/2/y_per_div) + if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off) + self.autorange_ts = time.time() + #plot xy channel + self.plotter.set_waveform( + channel='XY', + samples=(x_samples, y_samples), + color_spec=CHANNEL_COLOR_SPECS[0], + ) + #turn off each waveform + for i, samples in enumerate(sampleses): + self.plotter.set_waveform( + channel='Ch%d'%(i+1), + samples=[], + color_spec=CHANNEL_COLOR_SPECS[i], + ) + else: + #autorange + if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: + bounds = [common.get_min_max(samples) for samples in sampleses] + y_min = numpy.min(*[bound[0] for bound in bounds]) + y_max = numpy.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_KEY]) + if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + #adjust the y offset + y_off = y_per_div*round((y_max+y_min)/2/y_per_div) + if y_off != self[Y_OFF_KEY]: 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[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key]) + #handle num samps out of bounds + if num_samps > len(samples): + self.set_t_per_div( + common.get_clean_decr(self[T_PER_DIV_KEY])) + elif num_samps < 2: + self.set_t_per_div( + common.get_clean_incr(self[T_PER_DIV_KEY])) + num_samps = 0 + else: + #plot samples + self.plotter.set_waveform( + channel='Ch%d'%(i+1), + samples=samples[:num_samps], + color_spec=CHANNEL_COLOR_SPECS[i], + ) + #turn XY channel off + self.plotter.set_waveform( + channel='XY', + samples=[], + color_spec=CHANNEL_COLOR_SPECS[0], + ) + #update the plotter + self.plotter.update() + + def update_grid(self, *args): + """! + Update the grid to reflect the current settings: + xy divisions, xy offset, xy mode setting + """ + #grid parameters + t_per_div = self[T_PER_DIV_KEY] + x_per_div = self[X_PER_DIV_KEY] + y_per_div = self[Y_PER_DIV_KEY] + t_off = self[T_OFF_KEY] + x_off = self[X_OFF_KEY] + y_off = self[Y_OFF_KEY] + t_divs = self[T_DIVS_KEY] + x_divs = self[X_DIVS_KEY] + y_divs = self[Y_DIVS_KEY] + if self[SCOPE_XY_MODE_KEY]: + #update the x axis + self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1)) + self.plotter.set_x_grid( + -1*x_per_div*x_divs/2.0 + x_off, + x_per_div*x_divs/2.0 + x_off, + x_per_div, + ) + #update the y axis + self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1)) + self.plotter.set_y_grid( + -1*y_per_div*y_divs/2.0 + y_off, + y_per_div*y_divs/2.0 + y_off, + y_per_div, + ) + else: + #update the t axis + coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off) + self.plotter.set_x_label('Time', prefix+'s') + self.plotter.set_x_grid( + t_off, + t_per_div*t_divs + t_off, + t_per_div, + 10**(-exp), + ) + #update the y axis + self.plotter.set_y_label('Counts') + self.plotter.set_y_grid( + -1*y_per_div*y_divs/2.0 + y_off, + y_per_div*y_divs/2.0 + y_off, + y_per_div, + ) + #redraw current sample + self.handle_samples() diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py old mode 100755 new mode 100644 index 71fd7e128..5eee3efd5 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -1,661 +1,45 @@ -#!/usr/bin/env python # -# Copyright 2003,2004,2006,2007 Free Software Foundation, Inc. -# +# 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 gnuradio import gr, gru, eng_notation -from gnuradio.wxgui import stdgui2 -import wx -import gnuradio.wxgui.plot as plot -import numpy -import threading -import struct - -default_scopesink_size = (640, 240) -default_v_scale = 1000 -default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) - -class scope_sink_f(gr.hier_block2): - def __init__(self, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): - - gr.hier_block2.__init__(self, "scope_sink_f", - gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), - gr.io_signature(0,0,0)) - - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.guts = gr.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): - self.connect((self, i), (self.guts, i)) - - self.win = scope_window(win_info (msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -class scope_sink_c(gr.hier_block2): - def __init__(self, parent, title='', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): - - gr.hier_block2.__init__(self, "scope_sink_c", - gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - msgq = gr.msg_queue(2) # message queue that holds at most 2 messages - self.guts = gr.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): - c2f = gr.complex_to_float() - self.connect((self, i), c2f) - self.connect((c2f, 0), (self.guts, 2*i+0)) - self.connect((c2f, 1), (self.guts, 2*i+1)) - - self.win = scope_window(win_info(msgq, sample_rate, frame_decim, - v_scale, t_scale, self.guts, title), parent) - - def set_sample_rate(self, sample_rate): - self.guts.set_sample_rate(sample_rate) - self.win.info.set_sample_rate(sample_rate) - -class constellation_sink(scope_sink_c): - def __init__(self, parent, title='Constellation', sample_rate=1, - size=default_scopesink_size, frame_decim=default_frame_decim): - scope_sink_c.__init__(self, parent=parent, title=title, sample_rate=sample_rate, - size=size, frame_decim=frame_decim) - self.win.info.xy = True #constellation mode - -# ======================================================================== - - -time_base_list = [ # time / division - 1.0e-7, # 100ns / div - 2.5e-7, - 5.0e-7, - 1.0e-6, # 1us / div - 2.5e-6, - 5.0e-6, - 1.0e-5, # 10us / div - 2.5e-5, - 5.0e-5, - 1.0e-4, # 100us / div - 2.5e-4, - 5.0e-4, - 1.0e-3, # 1ms / div - 2.5e-3, - 5.0e-3, - 1.0e-2, # 10ms / div - 2.5e-2, - 5.0e-2 - ] - -v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER - 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div - 5.0e-3, - 1.0e-2, - 2.0e-2, - 5.0e-2, - 1.0e-1, - 2.0e-1, - 5.0e-1, - 1.0e+0, - 2.0e+0, - 5.0e+0, - 1.0e+1, - 2.0e+1, - 5.0e+1, - 1.0e+2, - 2.0e+2, - 5.0e+2, - 1.0e+3, - 2.0e+3, - 5.0e+3, - 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 - ] - - -wxDATA_EVENT = wx.NewEventType() - -def EVT_DATA_EVENT(win, func): - win.Connect(-1, -1, wxDATA_EVENT, func) - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (wxDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class win_info (object): - __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', - 'scopesink', 'title', - 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', - 'autorange', 'running'] - - def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, - scopesink, title = "Oscilloscope", xy=False): - self.msgq = msgq - self.sample_rate = sample_rate - self.frame_decim = frame_decim - self.scopesink = scopesink - self.title = title; - - self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) - self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) - - self.marker = 'line' - self.xy = xy - if v_scale == None: # 0 and None are both False, but 0 != None - self.autorange = True - else: - self.autorange = False # 0 is a valid v_scale - self.running = True - - def get_time_per_div (self): - return self.time_scale_cursor.current () - - def get_volts_per_div (self): - return self.v_scale_cursor.current () - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - - def get_sample_rate (self): - return self.sample_rate - - def get_decimation_rate (self): - return 1.0 - - def set_marker (self, s): - self.marker = s - - def get_marker (self): - return self.marker - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, event_receiver, frame_decim, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.event_receiver = event_receiver - self.frame_decim = frame_decim - self.iscan = 0 - self.keep_running = True - self.start () - - def run (self): - # print "input_watcher: pid = ", os.getpid () - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - if self.iscan == 0: # only display at frame_decim - self.iscan = self.frame_decim - - 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 - - bytes_per_chan = nsamples * gr.sizeof_float - - records = [] - for ch in range (nchan): - - start = ch * bytes_per_chan - chan_data = s[start:start+bytes_per_chan] - rec = numpy.fromstring (chan_data, numpy.float32) - records.append (rec) - - # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - - de = DataEvent (records) - wx.PostEvent (self.event_receiver, de) - records = [] - del de - - # end if iscan == 0 - self.iscan -= 1 - - -class scope_window (wx.Panel): - - def __init__ (self, info, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): - wx.Panel.__init__ (self, parent, -1) - self.info = info - - vbox = wx.BoxSizer (wx.VERTICAL) - - self.graph = graph_window (info, self, -1) - - vbox.Add (self.graph, 1, wx.EXPAND) - vbox.Add (self.make_control_box(), 0, wx.EXPAND) - vbox.Add (self.make_control2_box(), 0, wx.EXPAND) - - self.sizer = vbox - self.SetSizer (self.sizer) - self.SetAutoLayout (True) - self.sizer.Fit (self) - self.set_autorange(self.info.autorange) - - - # second row of control buttons etc. appears BELOW control_box - def make_control2_box (self): - ctrlbox = wx.BoxSizer (wx.HORIZONTAL) - - self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT) - self.inc_v_button.SetToolTipString ("Increase vertical range") - wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above - - self.dec_v_button = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT) - self.dec_v_button.SetToolTipString ("Decrease vertical range") - wx.EVT_BUTTON (self, 1100, self.decr_v_scale) - - self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div - self.update_v_scale_label () - - self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange") - self.autorange_checkbox.SetToolTipString ("Select autorange on/off") - wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) - - ctrlbox.Add ((5,0) ,0) # left margin space - ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND) - ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND) - ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER) - ctrlbox.Add ((20,0) ,0) # spacer - ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER) - - return ctrlbox - - def make_control_box (self): - ctrlbox = wx.BoxSizer (wx.HORIZONTAL) - - tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT) - tb_left.SetToolTipString ("Increase time base") - wx.EVT_BUTTON (self, 1001, self.incr_timebase) - - - tb_right = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT) - tb_right.SetToolTipString ("Decrease time base") - wx.EVT_BUTTON (self, 1000, self.decr_timebase) - - self.time_base_label = wx.StaticText (self, 1002, "") - self.update_timebase_label () - - ctrlbox.Add ((5,0) ,0) - # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) - ctrlbox.Add (tb_left, 0, wx.EXPAND) - ctrlbox.Add (tb_right, 0, wx.EXPAND) - ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER) - - ctrlbox.Add ((10,0) ,1) # stretchy space - - ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER) - self.trig_chan_choice = wx.Choice (self, 1004, - choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) - self.trig_chan_choice.SetToolTipString ("Select channel for trigger") - wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event) - ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) - - self.trig_mode_choice = wx.Choice (self, 1005, - choices = ['Auto', 'Pos', 'Neg']) - self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") - wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) - ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) - - trig_level50 = wx.Button (self, 1006, "50%") - trig_level50.SetToolTipString ("Set trigger level to 50%") - wx.EVT_BUTTON (self, 1006, self.set_trig_level50) - ctrlbox.Add (trig_level50, 0, wx.EXPAND) - - run_stop = wx.Button (self, 1007, "Run/Stop") - run_stop.SetToolTipString ("Toggle Run/Stop mode") - wx.EVT_BUTTON (self, 1007, self.run_stop) - ctrlbox.Add (run_stop, 0, wx.EXPAND) - - ctrlbox.Add ((10, 0) ,1) # stretchy space - - ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) - self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices) - self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots") - wx.EVT_CHOICE (self, 1002, self.marker_choice_event) - ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER) - - self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y']) - self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display") - wx.EVT_CHOICE (self, 1003, self.xy_choice_event) - ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) - - return ctrlbox - - _marker_choices = ['line', 'plus', 'dot'] - - def update_timebase_label (self): - time_per_div = self.info.get_time_per_div () - s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' - self.time_base_label.SetLabel (s) - - def decr_timebase (self, evt): - self.info.time_scale_cursor.prev () - self.update_timebase_label () - - def incr_timebase (self, evt): - self.info.time_scale_cursor.next () - self.update_timebase_label () - - def update_v_scale_label (self): - volts_per_div = self.info.get_volts_per_div () - s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div - self.v_scale_label.SetLabel (s) - - def decr_v_scale (self, evt): - self.info.v_scale_cursor.prev () - self.update_v_scale_label () - - def incr_v_scale (self, evt): - self.info.v_scale_cursor.next () - self.update_v_scale_label () - - def marker_choice_event (self, evt): - s = evt.GetString () - self.set_marker (s) - - def set_autorange(self, on): - if on: - self.v_scale_label.SetLabel(" (auto)") - self.info.autorange = True - self.autorange_checkbox.SetValue(True) - self.inc_v_button.Enable(False) - self.dec_v_button.Enable(False) - else: - if self.graph.y_range: - (l,u) = self.graph.y_range # found by autorange - self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) - self.update_v_scale_label() - self.info.autorange = False - self.autorange_checkbox.SetValue(False) - self.inc_v_button.Enable(True) - self.dec_v_button.Enable(True) - - def autorange_checkbox_event(self, evt): - if evt.Checked(): - self.set_autorange(True) - else: - self.set_autorange(False) - - def set_marker (self, s): - self.info.set_marker (s) # set info for drawing routines - i = self.marker_choice.FindString (s) - assert i >= 0, "Hmmm, set_marker problem" - self.marker_choice.SetSelection (i) - - def set_format_line (self): - self.set_marker ('line') - - def set_format_dot (self): - self.set_marker ('dot') - - def set_format_plus (self): - self.set_marker ('plus') - - def xy_choice_event (self, evt): - s = evt.GetString () - self.info.xy = s == 'X:Y' - - def trig_chan_choice_event (self, evt): - s = evt.GetString () - ch = int (s[-1]) - 1 - self.info.scopesink.set_trigger_channel (ch) - - def trig_mode_choice_event (self, evt): - sink = self.info.scopesink - s = evt.GetString () - if s == 'Pos': - sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) - elif s == 'Neg': - sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) - elif s == 'Auto': - sink.set_trigger_mode (gr.gr_TRIG_AUTO) - else: - assert 0, "Bad trig_mode_choice string" - - def set_trig_level50 (self, evt): - self.info.scopesink.set_trigger_level_auto () - - def run_stop (self, evt): - self.info.running = not self.info.running - - -class graph_window (plot.PlotCanvas): - - channel_colors = ['BLUE', 'RED', - 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] - - def __init__ (self, info, parent, id = -1, - pos = wx.DefaultPosition, size = (640, 240), - style = wx.DEFAULT_FRAME_STYLE, name = ""): - plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) - - self.SetXUseScopeTicks (True) - self.SetEnableGrid (True) - self.SetEnableZoom (True) - self.SetEnableLegend(True) - # self.SetBackgroundColour ('black') - - self.info = info; - self.y_range = None - self.x_range = None - self.avg_y_min = None - self.avg_y_max = None - self.avg_x_min = None - self.avg_x_max = None - - EVT_DATA_EVENT (self, self.format_data) - - self.input_watcher = input_watcher (info.msgq, self, info.frame_decim) - - def channel_color (self, ch): - return self.channel_colors[ch % len(self.channel_colors)] - - def format_data (self, evt): - if not self.info.running: - return - - if self.info.xy: - self.format_xy_data (evt) - return - - info = self.info - records = evt.data - nchannels = len (records) - npoints = len (records[0]) - - objects = [] - - Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) - x_vals = Ts * numpy.arange (-npoints/2, npoints/2) - - # preliminary clipping based on time axis here, instead of in graphics code - time_per_window = self.info.get_time_per_div () * 10 - n = int (time_per_window / Ts + 0.5) - n = n & ~0x1 # make even - n = max (2, min (n, npoints)) - - self.SetXUseScopeTicks (True) # use 10 divisions, no labels - - for ch in range(nchannels): - r = records[ch] - - # plot middle n points of record - - lb = npoints/2 - n/2 - ub = npoints/2 + n/2 - # points = zip (x_vals[lb:ub], r[lb:ub]) - points = numpy.zeros ((ub-lb, 2), numpy.float64) - points[:,0] = x_vals[lb:ub] - points[:,1] = r[lb:ub] - - m = info.get_marker () - if m == 'line': - objects.append (plot.PolyLine (points, - colour=self.channel_color (ch), - legend=('Ch%d' % (ch+1,)))) - else: - objects.append (plot.PolyMarker (points, - marker=m, - colour=self.channel_color (ch), - legend=('Ch%d' % (ch+1,)))) - - graphics = plot.PlotGraphics (objects, - title=self.info.title, - xLabel = '', yLabel = '') - - time_per_div = info.get_time_per_div () - x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! - volts_per_div = info.get_volts_per_div () - if not self.info.autorange: - self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) - self.Draw (graphics, xAxis=x_range, yAxis=self.y_range) - self.update_y_range () # autorange to self.y_range - - - def format_xy_data (self, evt): - info = self.info - records = evt.data - nchannels = len (records) - npoints = len (records[0]) - - if nchannels < 2: - return - - objects = [] - # points = zip (records[0], records[1]) - points = numpy.zeros ((len(records[0]), 2), numpy.float32) - points[:,0] = records[0] - points[:,1] = records[1] - - self.SetXUseScopeTicks (False) - - m = info.get_marker () - if m == 'line': - objects.append (plot.PolyLine (points, - colour=self.channel_color (0))) - else: - objects.append (plot.PolyMarker (points, - marker=m, - colour=self.channel_color (0))) - - graphics = plot.PlotGraphics (objects, - title=self.info.title, - xLabel = 'I', yLabel = 'Q') - - self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) - self.update_y_range () - self.update_x_range () - - - def update_y_range (self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox () # min, max points of graphics - - if self.avg_y_min: # prevent vertical scale from jumping abruptly --? - self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) - self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) - else: # initial guess - self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 - self.avg_y_max = p2[1] # 500.0 - - self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) - # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ - # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) - - - def update_x_range (self): - alpha = 1.0/25 - graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox () # min, max points of graphics - - if self.avg_x_min: - self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) - self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) - else: - self.avg_x_min = p1[0] - self.avg_x_max = p2[0] - - self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max) - - -# ---------------------------------------------------------------- -# Stand-alone test application -# ---------------------------------------------------------------- - -class test_top_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - if len(argv) > 1: - frame_decim = int(argv[1]) - else: - frame_decim = 1 - - if len(argv) > 2: - v_scale = float(argv[2]) # start up at this v_scale value - else: - v_scale = None # start up in autorange mode, default - - if len(argv) > 3: - t_scale = float(argv[3]) # start up at this t_scale value - else: - t_scale = None # old behavior - - print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) - - input_rate = 1e6 - - # Generate a complex sinusoid - self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) +# - # We add this throttle block so that this demo doesn't suck down - # all the CPU available. You normally wouldn't use it... - self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) +from gnuradio import gr - scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, - frame_decim=frame_decim, - v_scale=v_scale, t_scale=t_scale) - vbox.Add (scope.win, 1, wx.EXPAND) +p = gr.prefs() +style = p.get_string('wxgui', 'style', 'auto') - # Ultimately this will be - # self.connect("src0 throttle scope") - self.connect(self.src0, self.thr, scope) +# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback +if style == 'auto': + style = 'nongl' -def main (): - app = stdgui2.stdapp (test_top_block, "O'Scope Test App") - app.MainLoop () +if style == 'nongl': + from scopesink_nongl import scope_sink_f, scope_sink_c +elif style == 'gl': + try: + import wx + wx.glcanvas.GLCanvas + except AttributeError: + raise RuntimeError("wxPython doesn't support glcanvas") -if __name__ == '__main__': - main () + try: + from OpenGL.GL import * + except ImportError: + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") -# ---------------------------------------------------------------- + from scopesink_gl import scope_sink_f, scope_sink_c diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py new file mode 100644 index 000000000..727e1dc0a --- /dev/null +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -0,0 +1,183 @@ +# +# 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 +################################################## +import scope_window +import common +from gnuradio import gr +from pubsub import pubsub +from constants import * + +################################################## +# Scope sink block (wrapper for old wxgui) +################################################## +class _scope_sink_base(gr.hier_block2, common.prop_setter): + """! + A scope block with a gui window. + """ + + def __init__( + self, + parent, + title='', + sample_rate=1, + size=scope_window.DEFAULT_WIN_SIZE, + frame_decim=None, #ignore (old wrapper) + v_scale=scope_window.DEFAULT_V_SCALE, + t_scale=None, + num_inputs=1, + ac_couple=False, + xy_mode=False, + frame_rate=scope_window.DEFAULT_FRAME_RATE, + ): + if t_scale is None: t_scale = 0.001 + #init + gr.hier_block2.__init__( + self, + "scope_sink", + gr.io_signature(num_inputs, num_inputs, self._item_size), + gr.io_signature(0, 0, 0), + ) + #scope + msgq = gr.msg_queue(2) + scope = gr.oscope_sink_f(sample_rate, msgq) + #connect + if self._real: + for i in range(num_inputs): + self.connect((self, i), (scope, i)) + else: + for i in range(num_inputs): + c2f = gr.complex_to_float() + self.connect((self, i), c2f) + self.connect((c2f, 0), (scope, 2*i+0)) + self.connect((c2f, 1), (scope, 2*i+1)) + num_inputs *= 2 + #controller + self.controller = pubsub() + self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) + self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) + def set_trigger_level(level): + if level == '': scope.set_trigger_level_auto() + else: scope.set_trigger_level(level) + self.controller.subscribe(SCOPE_TRIGGER_LEVEL_KEY, set_trigger_level) + def set_trigger_mode(mode): + if mode == 0: mode = gr.gr_TRIG_AUTO + elif mode < 0: mode = gr.gr_TRIG_NEG_SLOPE + elif mode > 0: mode = gr.gr_TRIG_POS_SLOPE + else: return + scope.set_trigger_mode(mode) + self.controller.subscribe(SCOPE_TRIGGER_MODE_KEY, set_trigger_mode) + self.controller.subscribe(SCOPE_TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) + #start input watcher + def setter(p, k, x): # lambdas can't have assignments :( + p[k] = x + common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + #create window + self.win = scope_window.scope_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + frame_rate=frame_rate, + num_inputs=num_inputs, + sample_rate_key=SAMPLE_RATE_KEY, + t_scale=t_scale, + v_scale=v_scale, + ac_couple=ac_couple, + xy_mode=xy_mode, + scope_trigger_level_key=SCOPE_TRIGGER_LEVEL_KEY, + scope_trigger_mode_key=SCOPE_TRIGGER_MODE_KEY, + scope_trigger_channel_key=SCOPE_TRIGGER_CHANNEL_KEY, + msg_key=MSG_KEY, + ) + #register callbacks from window for external use + for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): + setattr(self, attr, getattr(self.win, attr)) + self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + +class scope_sink_f(_scope_sink_base): + _item_size = gr.sizeof_float + _real = True + +class scope_sink_c(_scope_sink_base): + _item_size = gr.sizeof_gr_complex + _real = False + +#backwards compadible wrapper (maybe only grc uses this) +class constellation_sink(scope_sink_c): + def __init__(self, **kwargs): + kwargs['xy_mode'] = True + scope_sink_c.__init__(self, **kwargs) + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import stdgui2 + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + if len(argv) > 1: + frame_decim = int(argv[1]) + else: + frame_decim = 1 + + if len(argv) > 2: + v_scale = float(argv[2]) # start up at this v_scale value + else: + v_scale = None # start up in autorange mode, default + + if len(argv) > 3: + t_scale = float(argv[3]) # start up at this t_scale value + else: + t_scale = .00003 # old behavior + + print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + + input_rate = 1e6 + + # Generate a complex sinusoid + self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) + + # We add this throttle block so that this demo doesn't suck down + # all the CPU available. You normally wouldn't use it... + self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) + + scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, + frame_decim=frame_decim, + v_scale=v_scale, t_scale=t_scale) + vbox.Add (scope.win, 1, wx.EXPAND) + + # Ultimately this will be + # self.connect("src0 throttle scope") + self.connect(self.src0, self.thr, scope) + +def main (): + app = stdgui2.stdapp (test_top_block, "O'Scope Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py new file mode 100644 index 000000000..71fd7e128 --- /dev/null +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -0,0 +1,661 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2006,2007 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 gnuradio import gr, gru, eng_notation +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import numpy +import threading +import struct + +default_scopesink_size = (640, 240) +default_v_scale = 1000 +default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) + +class scope_sink_f(gr.hier_block2): + def __init__(self, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None, num_inputs=1): + + gr.hier_block2.__init__(self, "scope_sink_f", + gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), + gr.io_signature(0,0,0)) + + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + self.guts = gr.oscope_sink_f(sample_rate, msgq) + for i in range(num_inputs): + self.connect((self, i), (self.guts, i)) + + self.win = scope_window(win_info (msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +class scope_sink_c(gr.hier_block2): + def __init__(self, parent, title='', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim, + v_scale=default_v_scale, t_scale=None, num_inputs=1): + + gr.hier_block2.__init__(self, "scope_sink_c", + gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + msgq = gr.msg_queue(2) # message queue that holds at most 2 messages + self.guts = gr.oscope_sink_f(sample_rate, msgq) + for i in range(num_inputs): + c2f = gr.complex_to_float() + self.connect((self, i), c2f) + self.connect((c2f, 0), (self.guts, 2*i+0)) + self.connect((c2f, 1), (self.guts, 2*i+1)) + + self.win = scope_window(win_info(msgq, sample_rate, frame_decim, + v_scale, t_scale, self.guts, title), parent) + + def set_sample_rate(self, sample_rate): + self.guts.set_sample_rate(sample_rate) + self.win.info.set_sample_rate(sample_rate) + +class constellation_sink(scope_sink_c): + def __init__(self, parent, title='Constellation', sample_rate=1, + size=default_scopesink_size, frame_decim=default_frame_decim): + scope_sink_c.__init__(self, parent=parent, title=title, sample_rate=sample_rate, + size=size, frame_decim=frame_decim) + self.win.info.xy = True #constellation mode + +# ======================================================================== + + +time_base_list = [ # time / division + 1.0e-7, # 100ns / div + 2.5e-7, + 5.0e-7, + 1.0e-6, # 1us / div + 2.5e-6, + 5.0e-6, + 1.0e-5, # 10us / div + 2.5e-5, + 5.0e-5, + 1.0e-4, # 100us / div + 2.5e-4, + 5.0e-4, + 1.0e-3, # 1ms / div + 2.5e-3, + 5.0e-3, + 1.0e-2, # 10ms / div + 2.5e-2, + 5.0e-2 + ] + +v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER + 2.0e-3, # 2m / div, don't call it V/div it's actually counts/div + 5.0e-3, + 1.0e-2, + 2.0e-2, + 5.0e-2, + 1.0e-1, + 2.0e-1, + 5.0e-1, + 1.0e+0, + 2.0e+0, + 5.0e+0, + 1.0e+1, + 2.0e+1, + 5.0e+1, + 1.0e+2, + 2.0e+2, + 5.0e+2, + 1.0e+3, + 2.0e+3, + 5.0e+3, + 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 + ] + + +wxDATA_EVENT = wx.NewEventType() + +def EVT_DATA_EVENT(win, func): + win.Connect(-1, -1, wxDATA_EVENT, func) + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (wxDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class win_info (object): + __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', + 'scopesink', 'title', + 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', + 'autorange', 'running'] + + def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale, + scopesink, title = "Oscilloscope", xy=False): + self.msgq = msgq + self.sample_rate = sample_rate + self.frame_decim = frame_decim + self.scopesink = scopesink + self.title = title; + + self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale) + self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale) + + self.marker = 'line' + self.xy = xy + if v_scale == None: # 0 and None are both False, but 0 != None + self.autorange = True + else: + self.autorange = False # 0 is a valid v_scale + self.running = True + + def get_time_per_div (self): + return self.time_scale_cursor.current () + + def get_volts_per_div (self): + return self.v_scale_cursor.current () + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + + def get_sample_rate (self): + return self.sample_rate + + def get_decimation_rate (self): + return 1.0 + + def set_marker (self, s): + self.marker = s + + def get_marker (self): + return self.marker + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, event_receiver, frame_decim, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.event_receiver = event_receiver + self.frame_decim = frame_decim + self.iscan = 0 + self.keep_running = True + self.start () + + def run (self): + # print "input_watcher: pid = ", os.getpid () + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + if self.iscan == 0: # only display at frame_decim + self.iscan = self.frame_decim + + 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 + + bytes_per_chan = nsamples * gr.sizeof_float + + records = [] + for ch in range (nchan): + + start = ch * bytes_per_chan + chan_data = s[start:start+bytes_per_chan] + rec = numpy.fromstring (chan_data, numpy.float32) + records.append (rec) + + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) + + de = DataEvent (records) + wx.PostEvent (self.event_receiver, de) + records = [] + del de + + # end if iscan == 0 + self.iscan -= 1 + + +class scope_window (wx.Panel): + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""): + wx.Panel.__init__ (self, parent, -1) + self.info = info + + vbox = wx.BoxSizer (wx.VERTICAL) + + self.graph = graph_window (info, self, -1) + + vbox.Add (self.graph, 1, wx.EXPAND) + vbox.Add (self.make_control_box(), 0, wx.EXPAND) + vbox.Add (self.make_control2_box(), 0, wx.EXPAND) + + self.sizer = vbox + self.SetSizer (self.sizer) + self.SetAutoLayout (True) + self.sizer.Fit (self) + self.set_autorange(self.info.autorange) + + + # second row of control buttons etc. appears BELOW control_box + def make_control2_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT) + self.inc_v_button.SetToolTipString ("Increase vertical range") + wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above + + self.dec_v_button = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT) + self.dec_v_button.SetToolTipString ("Decrease vertical range") + wx.EVT_BUTTON (self, 1100, self.decr_v_scale) + + self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div + self.update_v_scale_label () + + self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange") + self.autorange_checkbox.SetToolTipString ("Select autorange on/off") + wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event) + + ctrlbox.Add ((5,0) ,0) # left margin space + ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND) + ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER) + ctrlbox.Add ((20,0) ,0) # spacer + ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER) + + return ctrlbox + + def make_control_box (self): + ctrlbox = wx.BoxSizer (wx.HORIZONTAL) + + tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT) + tb_left.SetToolTipString ("Increase time base") + wx.EVT_BUTTON (self, 1001, self.incr_timebase) + + + tb_right = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT) + tb_right.SetToolTipString ("Decrease time base") + wx.EVT_BUTTON (self, 1000, self.decr_timebase) + + self.time_base_label = wx.StaticText (self, 1002, "") + self.update_timebase_label () + + ctrlbox.Add ((5,0) ,0) + # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER) + ctrlbox.Add (tb_left, 0, wx.EXPAND) + ctrlbox.Add (tb_right, 0, wx.EXPAND) + ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER) + + ctrlbox.Add ((10,0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER) + self.trig_chan_choice = wx.Choice (self, 1004, + choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4']) + self.trig_chan_choice.SetToolTipString ("Select channel for trigger") + wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event) + ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) + + self.trig_mode_choice = wx.Choice (self, 1005, + choices = ['Auto', 'Pos', 'Neg']) + self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") + wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) + ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) + + trig_level50 = wx.Button (self, 1006, "50%") + trig_level50.SetToolTipString ("Set trigger level to 50%") + wx.EVT_BUTTON (self, 1006, self.set_trig_level50) + ctrlbox.Add (trig_level50, 0, wx.EXPAND) + + run_stop = wx.Button (self, 1007, "Run/Stop") + run_stop.SetToolTipString ("Toggle Run/Stop mode") + wx.EVT_BUTTON (self, 1007, self.run_stop) + ctrlbox.Add (run_stop, 0, wx.EXPAND) + + ctrlbox.Add ((10, 0) ,1) # stretchy space + + ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER) + self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices) + self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots") + wx.EVT_CHOICE (self, 1002, self.marker_choice_event) + ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER) + + self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y']) + self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display") + wx.EVT_CHOICE (self, 1003, self.xy_choice_event) + ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) + + return ctrlbox + + _marker_choices = ['line', 'plus', 'dot'] + + def update_timebase_label (self): + time_per_div = self.info.get_time_per_div () + s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' + self.time_base_label.SetLabel (s) + + def decr_timebase (self, evt): + self.info.time_scale_cursor.prev () + self.update_timebase_label () + + def incr_timebase (self, evt): + self.info.time_scale_cursor.next () + self.update_timebase_label () + + def update_v_scale_label (self): + volts_per_div = self.info.get_volts_per_div () + s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div + self.v_scale_label.SetLabel (s) + + def decr_v_scale (self, evt): + self.info.v_scale_cursor.prev () + self.update_v_scale_label () + + def incr_v_scale (self, evt): + self.info.v_scale_cursor.next () + self.update_v_scale_label () + + def marker_choice_event (self, evt): + s = evt.GetString () + self.set_marker (s) + + def set_autorange(self, on): + if on: + self.v_scale_label.SetLabel(" (auto)") + self.info.autorange = True + self.autorange_checkbox.SetValue(True) + self.inc_v_button.Enable(False) + self.dec_v_button.Enable(False) + else: + if self.graph.y_range: + (l,u) = self.graph.y_range # found by autorange + self.info.v_scale_cursor.set_index_by_value((u-l)/8.0) + self.update_v_scale_label() + self.info.autorange = False + self.autorange_checkbox.SetValue(False) + self.inc_v_button.Enable(True) + self.dec_v_button.Enable(True) + + def autorange_checkbox_event(self, evt): + if evt.Checked(): + self.set_autorange(True) + else: + self.set_autorange(False) + + def set_marker (self, s): + self.info.set_marker (s) # set info for drawing routines + i = self.marker_choice.FindString (s) + assert i >= 0, "Hmmm, set_marker problem" + self.marker_choice.SetSelection (i) + + def set_format_line (self): + self.set_marker ('line') + + def set_format_dot (self): + self.set_marker ('dot') + + def set_format_plus (self): + self.set_marker ('plus') + + def xy_choice_event (self, evt): + s = evt.GetString () + self.info.xy = s == 'X:Y' + + def trig_chan_choice_event (self, evt): + s = evt.GetString () + ch = int (s[-1]) - 1 + self.info.scopesink.set_trigger_channel (ch) + + def trig_mode_choice_event (self, evt): + sink = self.info.scopesink + s = evt.GetString () + if s == 'Pos': + sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) + elif s == 'Neg': + sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) + elif s == 'Auto': + sink.set_trigger_mode (gr.gr_TRIG_AUTO) + else: + assert 0, "Bad trig_mode_choice string" + + def set_trig_level50 (self, evt): + self.info.scopesink.set_trigger_level_auto () + + def run_stop (self, evt): + self.info.running = not self.info.running + + +class graph_window (plot.PlotCanvas): + + channel_colors = ['BLUE', 'RED', + 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] + + def __init__ (self, info, parent, id = -1, + pos = wx.DefaultPosition, size = (640, 240), + style = wx.DEFAULT_FRAME_STYLE, name = ""): + plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name) + + self.SetXUseScopeTicks (True) + self.SetEnableGrid (True) + self.SetEnableZoom (True) + self.SetEnableLegend(True) + # self.SetBackgroundColour ('black') + + self.info = info; + self.y_range = None + self.x_range = None + self.avg_y_min = None + self.avg_y_max = None + self.avg_x_min = None + self.avg_x_max = None + + EVT_DATA_EVENT (self, self.format_data) + + self.input_watcher = input_watcher (info.msgq, self, info.frame_decim) + + def channel_color (self, ch): + return self.channel_colors[ch % len(self.channel_colors)] + + def format_data (self, evt): + if not self.info.running: + return + + if self.info.xy: + self.format_xy_data (evt) + return + + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + objects = [] + + Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ()) + x_vals = Ts * numpy.arange (-npoints/2, npoints/2) + + # preliminary clipping based on time axis here, instead of in graphics code + time_per_window = self.info.get_time_per_div () * 10 + n = int (time_per_window / Ts + 0.5) + n = n & ~0x1 # make even + n = max (2, min (n, npoints)) + + self.SetXUseScopeTicks (True) # use 10 divisions, no labels + + for ch in range(nchannels): + r = records[ch] + + # plot middle n points of record + + lb = npoints/2 - n/2 + ub = npoints/2 + n/2 + # points = zip (x_vals[lb:ub], r[lb:ub]) + points = numpy.zeros ((ub-lb, 2), numpy.float64) + points[:,0] = x_vals[lb:ub] + points[:,1] = r[lb:ub] + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (ch), + legend=('Ch%d' % (ch+1,)))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = '', yLabel = '') + + time_per_div = info.get_time_per_div () + x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples! + volts_per_div = info.get_volts_per_div () + if not self.info.autorange: + self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div) + self.Draw (graphics, xAxis=x_range, yAxis=self.y_range) + self.update_y_range () # autorange to self.y_range + + + def format_xy_data (self, evt): + info = self.info + records = evt.data + nchannels = len (records) + npoints = len (records[0]) + + if nchannels < 2: + return + + objects = [] + # points = zip (records[0], records[1]) + points = numpy.zeros ((len(records[0]), 2), numpy.float32) + points[:,0] = records[0] + points[:,1] = records[1] + + self.SetXUseScopeTicks (False) + + m = info.get_marker () + if m == 'line': + objects.append (plot.PolyLine (points, + colour=self.channel_color (0))) + else: + objects.append (plot.PolyMarker (points, + marker=m, + colour=self.channel_color (0))) + + graphics = plot.PlotGraphics (objects, + title=self.info.title, + xLabel = 'I', yLabel = 'Q') + + self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range) + self.update_y_range () + self.update_x_range () + + + def update_y_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_y_min: # prevent vertical scale from jumping abruptly --? + self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) + self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha) + else: # initial guess + self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35 + self.avg_y_max = p2[1] # 500.0 + + self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) + # print "p1 %s p2 %s y_min %s y_max %s y_range %s" \ + # % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range) + + + def update_x_range (self): + alpha = 1.0/25 + graphics = self.last_draw[0] + p1, p2 = graphics.boundingBox () # min, max points of graphics + + if self.avg_x_min: + self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha) + self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha) + else: + self.avg_x_min = p1[0] + self.avg_x_max = p2[0] + + self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max) + + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + if len(argv) > 1: + frame_decim = int(argv[1]) + else: + frame_decim = 1 + + if len(argv) > 2: + v_scale = float(argv[2]) # start up at this v_scale value + else: + v_scale = None # start up in autorange mode, default + + if len(argv) > 3: + t_scale = float(argv[3]) # start up at this t_scale value + else: + t_scale = None # old behavior + + print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + + input_rate = 1e6 + + # Generate a complex sinusoid + self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) + + # We add this throttle block so that this demo doesn't suck down + # all the CPU available. You normally wouldn't use it... + self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) + + scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, + frame_decim=frame_decim, + v_scale=v_scale, t_scale=t_scale) + vbox.Add (scope.win, 1, wx.EXPAND) + + # Ultimately this will be + # self.connect("src0 throttle scope") + self.connect(self.src0, self.thr, scope) + +def main (): + app = stdgui2.stdapp (test_top_block, "O'Scope Test App") + app.MainLoop () + +if __name__ == '__main__': + main () + +# ---------------------------------------------------------------- diff --git a/gr-wxgui/src/python/slider.py b/gr-wxgui/src/python/slider.py old mode 100755 new mode 100644 diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py new file mode 100644 index 000000000..3831fca90 --- /dev/null +++ b/gr-wxgui/src/python/waterfall_window.py @@ -0,0 +1,301 @@ +# +# 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 +################################################## +import plotter +import common +import wx +import numpy +import math +import pubsub +from constants import * + +################################################## +# Constants +################################################## +SLIDER_STEPS = 100 +AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 +DEFAULT_FRAME_RATE = 30 +DEFAULT_WIN_SIZE = (600, 300) +DIV_LEVELS = (1, 2, 5, 10, 20) +MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200 +COLOR_MODES = ( + ('RGB1', 'rgb1'), + ('RGB2', 'rgb2'), + ('RGB3', 'rgb3'), + ('Gray', 'gray'), +) + +################################################## +# Waterfall 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) + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + #color mode + control_box.AddStretchSpacer() + self.color_mode_chooser = common.DropDownController(self, 'Color', COLOR_MODES, parent, COLOR_MODE_KEY) + control_box.Add(self.color_mode_chooser, 0, wx.EXPAND) + #average + control_box.AddStretchSpacer() + self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) + control_box.Add(self.average_check_box, 0, wx.EXPAND) + control_box.AddSpacer(2) + self.avg_alpha_slider = common.LogSliderController( + self, 'Avg Alpha', + AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, + parent.ext_controller, parent.avg_alpha_key, + formatter=lambda x: ': %.4f'%x, + ) + parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) + control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + #dyanmic range buttons + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + self._dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) + control_box.Add(self._dynamic_range_buttons, 0, wx.ALIGN_CENTER) + #ref lvl buttons + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Set Ref Level'), 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) + #num lines buttons + control_box.AddStretchSpacer() + control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + self._time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) + control_box.Add(self._time_scale_buttons, 0, wx.ALIGN_CENTER) + #autoscale + control_box.AddStretchSpacer() + self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(self.autoscale_button, 0, wx.EXPAND) + #clear + self.clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) + self.clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) + control_box.Add(self.clear_button, 0, wx.EXPAND) + #run/stop + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + + ################################################## + # Event handlers + ################################################## + def _on_clear_button(self, event): + self.parent.set_num_lines(self.parent[NUM_LINES_KEY]) + def _on_incr_dynamic_range(self, event): + self.parent.set_dynamic_range( + min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE)) + def _on_decr_dynamic_range(self, event): + self.parent.set_dynamic_range( + max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE)) + def _on_incr_ref_level(self, event): + self.parent.set_ref_level( + self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1) + def _on_decr_ref_level(self, event): + self.parent.set_ref_level( + self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1) + def _on_incr_time_scale(self, event): + old_rate = self.parent.ext_controller[self.parent.frame_rate_key] + self.parent.ext_controller[self.parent.frame_rate_key] *= 0.75 + if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: + self.parent.ext_controller[self.parent.decimation_key] += 1 + def _on_decr_time_scale(self, event): + old_rate = self.parent.ext_controller[self.parent.frame_rate_key] + self.parent.ext_controller[self.parent.frame_rate_key] *= 1.25 + if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: + self.parent.ext_controller[self.parent.decimation_key] -= 1 + +################################################## +# Waterfall window with plotter and control panel +################################################## +class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): + def __init__( + self, + parent, + controller, + size, + title, + real, + fft_size, + num_lines, + decimation_key, + baseband_freq, + sample_rate_key, + frame_rate_key, + dynamic_range, + ref_level, + average_key, + avg_alpha_key, + msg_key, + ): + pubsub.pubsub.__init__(self) + #setup + self.ext_controller = controller + self.real = real + self.fft_size = fft_size + self.decimation_key = decimation_key + self.sample_rate_key = sample_rate_key + self.frame_rate_key = frame_rate_key + self.average_key = average_key + self.avg_alpha_key = avg_alpha_key + #init panel and plot + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + self.plotter = plotter.waterfall_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_point_label(True) + #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) + #plotter listeners + self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode) + self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines) + #initial setup + self.ext_controller[self.average_key] = self.ext_controller[self.average_key] + self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] + self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range) + self._register_set_prop(self, NUM_LINES_KEY, num_lines) + self._register_set_prop(self, Y_DIVS_KEY, 8) + self._register_set_prop(self, X_DIVS_KEY, 8) #approximate + self._register_set_prop(self, REF_LEVEL_KEY, ref_level) + self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) + self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1]) + self._register_set_prop(self, RUNNING_KEY, True) + #register events + self.ext_controller.subscribe(msg_key, self.handle_msg) + self.ext_controller.subscribe(self.decimation_key, self.update_grid) + self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) + self.ext_controller.subscribe(self.frame_rate_key, self.update_grid) + self.subscribe(BASEBAND_FREQ_KEY, self.update_grid) + self.subscribe(NUM_LINES_KEY, self.update_grid) + self.subscribe(Y_DIVS_KEY, self.update_grid) + self.subscribe(X_DIVS_KEY, self.update_grid) + #initial update + self.update_grid() + + def autoscale(self, *args): + """! + Autoscale the waterfall plot to the last frame. + Set the dynamic range and reference level. + Does not affect the current data in the waterfall. + """ + #get the peak level (max of the samples) + peak_level = numpy.max(self.samples) + #get the noise floor (averge the smallest samples) + noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4]) + #padding + noise_floor -= abs(noise_floor)*.5 + peak_level += abs(peak_level)*.1 + #set the range and level + self.set_ref_level(peak_level) + self.set_dynamic_range(peak_level - noise_floor) + + 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. + Send the data to the plotter. + @param msg the fft array as a character array + """ + if not self[RUNNING_KEY]: return + #convert to floating point numbers + self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame + num_samps = len(samples) + #reorder fft + if self.real: samples = samples[:num_samps/2] + else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2])) + #plot the fft + self.plotter.set_samples( + samples=samples, + minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY], + maximum=self[REF_LEVEL_KEY], + ) + #update the plotter + self.plotter.update() + + def update_grid(self, *args): + """! + Update the plotter grid. + This update method is dependent on the variables below. + Determine the x and y axis grid parameters. + The x axis depends on sample rate, baseband freq, and x divs. + The y axis depends on y per div, y divs, and ref level. + """ + #grid parameters + sample_rate = self.ext_controller[self.sample_rate_key] + frame_rate = self.ext_controller[self.frame_rate_key] + baseband_freq = self[BASEBAND_FREQ_KEY] + num_lines = self[NUM_LINES_KEY] + y_divs = self[Y_DIVS_KEY] + x_divs = self[X_DIVS_KEY] + #determine best fitting x_per_div + if self.real: x_width = sample_rate/2.0 + else: x_width = sample_rate/1.0 + x_per_div = common.get_clean_num(x_width/x_divs) + coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) + #update the x grid + if self.real: + self.plotter.set_x_grid( + baseband_freq, + baseband_freq + sample_rate/2.0, + x_per_div, + 10**(-exp), + ) + else: + self.plotter.set_x_grid( + baseband_freq - sample_rate/2.0, + baseband_freq + sample_rate/2.0, + x_per_div, + 10**(-exp), + ) + #update x units + self.plotter.set_x_label('Frequency', prefix+'Hz') + #update y grid + duration = float(num_lines)/frame_rate + y_per_div = common.get_clean_num(duration/y_divs) + self.plotter.set_y_grid(0, duration, y_per_div) + #update y units + self.plotter.set_y_label('Time', 's') + #update plotter + self.plotter.update() diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py old mode 100755 new mode 100644 index fb91d26e0..215193044 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -1,437 +1,45 @@ -#!/usr/bin/env python # -# Copyright 2003,2004,2005,2007,2008 Free Software Foundation, Inc. -# +# 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 gnuradio import gr, gru, window -from gnuradio.wxgui import stdgui2 -import wx -import gnuradio.wxgui.plot as plot -import numpy -import os -import threading -import math - -default_fftsink_size = (640,240) -default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) - -class waterfall_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, - sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, - average=False, avg_alpha=None, title=''): - - # initialize common attributes - self.baseband_freq = baseband_freq - self.sample_rate = sample_rate - self.fft_size = fft_size - self.fft_rate = fft_rate - self.average = average - if avg_alpha is None: - self.avg_alpha = 2.0 / fft_rate - else: - self.avg_alpha = avg_alpha - self.title = title - self.input_is_real = input_is_real - self.msgq = gr.msg_queue(2) # queue up to 2 messages - - def set_average(self, average): - self.average = average - if average: - self.avg.set_taps(self.avg_alpha) - else: - self.avg.set_taps(1.0) - - def set_avg_alpha(self, avg_alpha): - self.avg_alpha = avg_alpha - - def set_baseband_freq(self, baseband_freq): - self.baseband_freq = baseband_freq - - def set_sample_rate(self, sample_rate): - self.sample_rate = sample_rate - self._set_n() - - def _set_n(self): - self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - -class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): - def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): - - gr.hier_block2.__init__(self, "waterfall_sink_f", - gr.io_signature(1, 1, gr.sizeof_float), - gr.io_signature(0,0,0)) - - waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = gr.fft_vfc(self.fft_size, True, mywindow) - self.c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): - def __init__(self, parent, baseband_freq=0, - y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): - - gr.hier_block2.__init__(self, "waterfall_sink_f", - gr.io_signature(1, 1, gr.sizeof_gr_complex), - gr.io_signature(0,0,0)) - - waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, - sample_rate=sample_rate, fft_size=fft_size, - fft_rate=fft_rate, - average=average, avg_alpha=avg_alpha, title=title) - - self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) - self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, - max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - - mywindow = window.blackmanharris(self.fft_size) - self.fft = gr.fft_vcc(self.fft_size, True, mywindow) - self.c2mag = gr.complex_to_mag(self.fft_size) - self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) - self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) - self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) - self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) - - self.win = waterfall_window(self, parent, size=size) - self.set_average(self.average) - - -# ------------------------------------------------------------------------ - -myDATA_EVENT = wx.NewEventType() -EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) - - -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self) - self.SetEventType (myDATA_EVENT) - self.data = data - - def Clone (self): - self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): - def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq - self.fft_size = fft_size - self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - - -class waterfall_window (wx.Panel): - def __init__ (self, fftsink, parent, id = -1, - pos = wx.DefaultPosition, size = wx.DefaultSize, - style = wx.DEFAULT_FRAME_STYLE, name = ""): - wx.Panel.__init__(self, parent, id, pos, size, style, name) - - self.fftsink = fftsink - self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) - - self.scale_factor = 5.0 # FIXME should autoscale, or set this - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Clear() - - self.pens = self.make_pens() - - wx.EVT_PAINT( self, self.OnPaint ) - wx.EVT_CLOSE (self, self.on_close_window) - EVT_DATA_EVENT (self, self.set_data) - - self.build_popup_menu() - - wx.EVT_CLOSE (self, self.on_close_window) - self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) - - self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) - - - def on_close_window (self, event): - print "waterfall_window: on_close_window" - self.keep_running = False - - def const_list(self,const,len): - return [const] * len - - def make_colormap(self): - r = [] - r.extend(self.const_list(0,96)) - r.extend(range(0,255,4)) - r.extend(self.const_list(255,64)) - r.extend(range(255,128,-4)) - - g = [] - g.extend(self.const_list(0,32)) - g.extend(range(0,255,4)) - g.extend(self.const_list(255,64)) - g.extend(range(255,0,-4)) - g.extend(self.const_list(0,32)) - - b = range(128,255,4) - b.extend(self.const_list(255,64)) - b.extend(range(255,0,-4)) - b.extend(self.const_list(0,96)) - return (r,g,b) - - def make_pens(self): - (r,g,b) = self.make_colormap() - pens = [] - for i in range(0,256): - colour = wx.Colour(r[i], g[i], b[i]) - pens.append( wx.Pen(colour, 2, wx.SOLID)) - return pens - - def OnPaint(self, event): - dc = wx.PaintDC(self) - self.DoDrawing(dc) - - def DoDrawing(self, dc=None): - if dc is None: - dc = wx.ClientDC(self) - dc.DrawBitmap(self.bm, 0, 0, False ) - - - def const_list(self,const,len): - a = [const] - for i in range(1,len): - a.append(const) - return a - - - def set_data (self, evt): - dB = evt.data - L = len (dB) - - dc1 = wx.MemoryDC() - dc1.SelectObject(self.bm) - dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) - - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) - if x >= 1e9: - sf = 1e-9 - units = "GHz" - elif x >= 1e6: - sf = 1e-6 - units = "MHz" - else: - sf = 1e-3 - units = "kHz" - - - if self.fftsink.input_is_real: # only plot 1/2 the points - d_max = L/2 - p_width = 2 - else: - d_max = L/2 - p_width = 1 - - scale_factor = self.scale_factor - if self.fftsink.input_is_real: # real fft - for x_pos in range(0, d_max): - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) - else: # complex fft - for x_pos in range(0, d_max): # positive freqs - value = int(dB[x_pos] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) - for x_pos in range(0 , d_max): # negative freqs - value = int(dB[x_pos+d_max] * scale_factor) - value = min(255, max(0, value)) - dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) - - del dc1 - self.DoDrawing (None) - - def on_average(self, evt): - # print "on_average" - self.fftsink.set_average(evt.IsChecked()) - - def on_right_click(self, event): - menu = self.popup_menu - for id, pred in self.checkmarks.items(): - item = menu.FindItemById(id) - item.Check(pred()) - self.PopupMenu(menu, event.GetPosition()) - - - def build_popup_menu(self): - self.id_incr_ref_level = wx.NewId() - self.id_decr_ref_level = wx.NewId() - self.id_incr_y_per_div = wx.NewId() - self.id_decr_y_per_div = wx.NewId() - self.id_y_per_div_1 = wx.NewId() - self.id_y_per_div_2 = wx.NewId() - self.id_y_per_div_5 = wx.NewId() - self.id_y_per_div_10 = wx.NewId() - self.id_y_per_div_20 = wx.NewId() - self.id_average = wx.NewId() - - self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) - #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) - #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - - - # make a menu - menu = wx.Menu() - self.popup_menu = menu - menu.AppendCheckItem(self.id_average, "Average") - # menu.Append(self.id_incr_ref_level, "Incr Ref Level") - # menu.Append(self.id_decr_ref_level, "Decr Ref Level") - # menu.Append(self.id_incr_y_per_div, "Incr dB/div") - # menu.Append(self.id_decr_y_per_div, "Decr dB/div") - # menu.AppendSeparator() - # we'd use RadioItems for these, but they're not supported on Mac - #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") - #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") - - self.checkmarks = { - self.id_average : lambda : self.fftsink.average - #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, - #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, - #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, - #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, - #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, - } - - -def next_up(v, seq): - """ - Return the first item in seq that is > v. - """ - for s in seq: - if s > v: - return s - return v - -def next_down(v, seq): - """ - Return the last item in seq that is < v. - """ - rseq = list(seq[:]) - rseq.reverse() - - for s in rseq: - if s < v: - return s - return v - - -# ---------------------------------------------------------------- -# Standalone test app -# ---------------------------------------------------------------- - -class test_top_block (stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) - - fft_size = 512 - - # build our flow graph - input_rate = 20.000e3 - - # Generate a complex sinusoid - self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) - #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) +# - # We add these throttle blocks so that this demo doesn't - # suck down all the CPU available. Normally you wouldn't use these. - self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) +from gnuradio import gr - sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src1, self.thr1, sink1) - vbox.Add (sink1.win, 1, wx.EXPAND) +p = gr.prefs() +style = p.get_string('wxgui', 'style', 'auto') - # generate a real sinusoid - self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) - self.thr2 = gr.throttle(gr.sizeof_float, input_rate) - sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size, - sample_rate=input_rate, baseband_freq=100e3) - self.connect(self.src2, self.thr2, sink2) - vbox.Add (sink2.win, 1, wx.EXPAND) +# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback +if style == 'auto': + style = 'nongl' +if style == 'nongl': + from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c +elif style == 'gl': + try: + import wx + wx.glcanvas.GLCanvas + except AttributeError: + raise RuntimeError("wxPython doesn't support glcanvas") -def main (): - app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App") - app.MainLoop () + try: + from OpenGL.GL import * + except ImportError: + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") -if __name__ == '__main__': - main () + from waterfallsink_gl import waterfall_sink_f, waterfall_sink_c diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py new file mode 100644 index 000000000..0b36e1048 --- /dev/null +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -0,0 +1,175 @@ +# +# 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 +################################################## +import waterfall_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# Waterfall sink block (wrapper for old wxgui) +################################################## +class _waterfall_sink_base(gr.hier_block2, common.prop_setter): + """! + An fft block with real/complex inputs and a gui window. + """ + + def __init__( + self, + parent, + baseband_freq=0, + y_per_div=None, #ignore (old wrapper) + ref_level=50, + sample_rate=1, + fft_size=512, + fft_rate=waterfall_window.DEFAULT_FRAME_RATE, + average=False, + avg_alpha=None, + title='', + size=waterfall_window.DEFAULT_WIN_SIZE, + ref_scale=2.0, + dynamic_range=80, + num_lines=256, + ): + #ensure avg alpha + if avg_alpha is None: avg_alpha = 2.0/fft_rate + #init + gr.hier_block2.__init__( + self, + "waterfall_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=fft_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) + #controller + self.controller = pubsub() + self.controller.subscribe(AVERAGE_KEY, fft.set_average) + self.controller.publish(AVERAGE_KEY, fft.average) + self.controller.subscribe(AVG_ALPHA_KEY, fft.set_avg_alpha) + self.controller.publish(AVG_ALPHA_KEY, fft.avg_alpha) + self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) + self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) + self.controller.subscribe(DECIMATION_KEY, fft.set_decimation) + self.controller.publish(DECIMATION_KEY, fft.decimation) + self.controller.subscribe(FRAME_RATE_KEY, fft.set_vec_rate) + self.controller.publish(FRAME_RATE_KEY, fft.frame_rate) + #start input watcher + def setter(p, k, x): # lambdas can't have assignments :( + p[k] = x + common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + #create window + self.win = waterfall_window.waterfall_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + real=self._real, + fft_size=fft_size, + num_lines=num_lines, + baseband_freq=baseband_freq, + decimation_key=DECIMATION_KEY, + sample_rate_key=SAMPLE_RATE_KEY, + frame_rate_key=FRAME_RATE_KEY, + dynamic_range=dynamic_range, + ref_level=ref_level, + average_key=AVERAGE_KEY, + avg_alpha_key=AVG_ALPHA_KEY, + msg_key=MSG_KEY, + ) + #register callbacks from window for external use + for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): + setattr(self, attr, getattr(self.win, attr)) + self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + self._register_set_prop(self.controller, AVERAGE_KEY) + self._register_set_prop(self.controller, AVG_ALPHA_KEY) + +class waterfall_sink_f(_waterfall_sink_base): + _fft_chain = blks2.logpwrfft_f + _item_size = gr.sizeof_float + _real = True + +class waterfall_sink_c(_waterfall_sink_base): + _fft_chain = blks2.logpwrfft_c + _item_size = gr.sizeof_gr_complex + _real = False + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import stdgui2 + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 512 + + # build our flow graph + input_rate = 20.000e3 + + # Generate a complex sinusoid + self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.connect(self.src1, self.thr1, sink1) + vbox.Add (sink1.win, 1, wx.EXPAND) + + # generate a real sinusoid + self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + self.thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.connect(self.src2, self.thr2, sink2) + vbox.Add (sink2.win, 1, wx.EXPAND) + + +def main (): + app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () + diff --git a/gr-wxgui/src/python/waterfallsink_nongl.py b/gr-wxgui/src/python/waterfallsink_nongl.py new file mode 100644 index 000000000..9d97c4e3c --- /dev/null +++ b/gr-wxgui/src/python/waterfallsink_nongl.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python +# +# Copyright 2003,2004,2005,2007,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 gnuradio import gr, gru, window +from gnuradio.wxgui import stdgui2 +import wx +import gnuradio.wxgui.plot as plot +import numpy +import os +import threading +import math + +default_fftsink_size = (640,240) +default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) + +class waterfall_sink_base(object): + def __init__(self, input_is_real=False, baseband_freq=0, + sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, + average=False, avg_alpha=None, title=''): + + # initialize common attributes + self.baseband_freq = baseband_freq + self.sample_rate = sample_rate + self.fft_size = fft_size + self.fft_rate = fft_rate + self.average = average + if avg_alpha is None: + self.avg_alpha = 2.0 / fft_rate + else: + self.avg_alpha = avg_alpha + self.title = title + self.input_is_real = input_is_real + self.msgq = gr.msg_queue(2) # queue up to 2 messages + + def set_average(self, average): + self.average = average + if average: + self.avg.set_taps(self.avg_alpha) + else: + self.avg.set_taps(1.0) + + def set_avg_alpha(self, avg_alpha): + self.avg_alpha = avg_alpha + + def set_baseband_freq(self, baseband_freq): + self.baseband_freq = baseband_freq + + def set_sample_rate(self, sample_rate): + self.sample_rate = sample_rate + self._set_n() + + def _set_n(self): + self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + +class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + gr.hier_block2.__init__(self, "waterfall_sink_f", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0,0,0)) + + waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + self.fft = gr.fft_vfc(self.fft_size, True, mywindow) + self.c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): + def __init__(self, parent, baseband_freq=0, + y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, + fft_rate=default_fft_rate, average=False, avg_alpha=None, + title='', size=default_fftsink_size): + + gr.hier_block2.__init__(self, "waterfall_sink_f", + gr.io_signature(1, 1, gr.sizeof_gr_complex), + gr.io_signature(0,0,0)) + + waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq, + sample_rate=sample_rate, fft_size=fft_size, + fft_rate=fft_rate, + average=average, avg_alpha=avg_alpha, title=title) + + self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) + self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, + max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + + mywindow = window.blackmanharris(self.fft_size) + self.fft = gr.fft_vcc(self.fft_size, True, mywindow) + self.c2mag = gr.complex_to_mag(self.fft_size) + self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) + self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size)) + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) + self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) + + self.win = waterfall_window(self, parent, size=size) + self.set_average(self.average) + + +# ------------------------------------------------------------------------ + +myDATA_EVENT = wx.NewEventType() +EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0) + + +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self) + self.SetEventType (myDATA_EVENT) + self.data = data + + def Clone (self): + self.__class__ (self.GetId()) + + +class input_watcher (threading.Thread): + def __init__ (self, msgq, fft_size, event_receiver, **kwds): + threading.Thread.__init__ (self, **kwds) + self.setDaemon (1) + self.msgq = msgq + self.fft_size = fft_size + self.event_receiver = event_receiver + self.keep_running = True + self.start () + + def run (self): + while (self.keep_running): + msg = self.msgq.delete_head() # blocking read of message queue + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = numpy.fromstring (s, numpy.float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de + + +class waterfall_window (wx.Panel): + def __init__ (self, fftsink, parent, id = -1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = wx.DEFAULT_FRAME_STYLE, name = ""): + wx.Panel.__init__(self, parent, id, pos, size, style, name) + self.set_baseband_freq = fftsink.set_baseband_freq + self.fftsink = fftsink + self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) + + self.scale_factor = 5.0 # FIXME should autoscale, or set this + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Clear() + + self.pens = self.make_pens() + + wx.EVT_PAINT( self, self.OnPaint ) + wx.EVT_CLOSE (self, self.on_close_window) + EVT_DATA_EVENT (self, self.set_data) + + self.build_popup_menu() + + wx.EVT_CLOSE (self, self.on_close_window) + self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) + + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) + + + def on_close_window (self, event): + print "waterfall_window: on_close_window" + self.keep_running = False + + def const_list(self,const,len): + return [const] * len + + def make_colormap(self): + r = [] + r.extend(self.const_list(0,96)) + r.extend(range(0,255,4)) + r.extend(self.const_list(255,64)) + r.extend(range(255,128,-4)) + + g = [] + g.extend(self.const_list(0,32)) + g.extend(range(0,255,4)) + g.extend(self.const_list(255,64)) + g.extend(range(255,0,-4)) + g.extend(self.const_list(0,32)) + + b = range(128,255,4) + b.extend(self.const_list(255,64)) + b.extend(range(255,0,-4)) + b.extend(self.const_list(0,96)) + return (r,g,b) + + def make_pens(self): + (r,g,b) = self.make_colormap() + pens = [] + for i in range(0,256): + colour = wx.Colour(r[i], g[i], b[i]) + pens.append( wx.Pen(colour, 2, wx.SOLID)) + return pens + + def OnPaint(self, event): + dc = wx.PaintDC(self) + self.DoDrawing(dc) + + def DoDrawing(self, dc=None): + if dc is None: + dc = wx.ClientDC(self) + dc.DrawBitmap(self.bm, 0, 0, False ) + + + def const_list(self,const,len): + a = [const] + for i in range(1,len): + a.append(const) + return a + + + def set_data (self, evt): + dB = evt.data + L = len (dB) + + dc1 = wx.MemoryDC() + dc1.SelectObject(self.bm) + dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1) + + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + if x >= 1e9: + sf = 1e-9 + units = "GHz" + elif x >= 1e6: + sf = 1e-6 + units = "MHz" + else: + sf = 1e-3 + units = "kHz" + + + if self.fftsink.input_is_real: # only plot 1/2 the points + d_max = L/2 + p_width = 2 + else: + d_max = L/2 + p_width = 1 + + scale_factor = self.scale_factor + if self.fftsink.input_is_real: # real fft + for x_pos in range(0, d_max): + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) + else: # complex fft + for x_pos in range(0, d_max): # positive freqs + value = int(dB[x_pos] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) + for x_pos in range(0 , d_max): # negative freqs + value = int(dB[x_pos+d_max] * scale_factor) + value = min(255, max(0, value)) + dc1.SetPen(self.pens[value]) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) + + del dc1 + self.DoDrawing (None) + + def on_average(self, evt): + # print "on_average" + self.fftsink.set_average(evt.IsChecked()) + + def on_right_click(self, event): + menu = self.popup_menu + for id, pred in self.checkmarks.items(): + item = menu.FindItemById(id) + item.Check(pred()) + self.PopupMenu(menu, event.GetPosition()) + + + def build_popup_menu(self): + self.id_incr_ref_level = wx.NewId() + self.id_decr_ref_level = wx.NewId() + self.id_incr_y_per_div = wx.NewId() + self.id_decr_y_per_div = wx.NewId() + self.id_y_per_div_1 = wx.NewId() + self.id_y_per_div_2 = wx.NewId() + self.id_y_per_div_5 = wx.NewId() + self.id_y_per_div_10 = wx.NewId() + self.id_y_per_div_20 = wx.NewId() + self.id_average = wx.NewId() + + self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) + #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) + #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) + + + # make a menu + menu = wx.Menu() + self.popup_menu = menu + menu.AppendCheckItem(self.id_average, "Average") + # menu.Append(self.id_incr_ref_level, "Incr Ref Level") + # menu.Append(self.id_decr_ref_level, "Decr Ref Level") + # menu.Append(self.id_incr_y_per_div, "Incr dB/div") + # menu.Append(self.id_decr_y_per_div, "Decr dB/div") + # menu.AppendSeparator() + # we'd use RadioItems for these, but they're not supported on Mac + #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div") + #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div") + + self.checkmarks = { + self.id_average : lambda : self.fftsink.average + #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, + #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, + #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5, + #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10, + #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20, + } + + +def next_up(v, seq): + """ + Return the first item in seq that is > v. + """ + for s in seq: + if s > v: + return s + return v + +def next_down(v, seq): + """ + Return the last item in seq that is < v. + """ + rseq = list(seq[:]) + rseq.reverse() + + for s in rseq: + if s < v: + return s + return v + + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +class test_top_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + fft_size = 512 + + # build our flow graph + input_rate = 20.000e3 + + # Generate a complex sinusoid + self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000) + + # We add these throttle blocks so that this demo doesn't + # suck down all the CPU available. Normally you wouldn't use these. + self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate) + + sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.connect(self.src1, self.thr1, sink1) + vbox.Add (sink1.win, 1, wx.EXPAND) + + # generate a real sinusoid + self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000) + self.thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size, + sample_rate=input_rate, baseband_freq=100e3) + self.connect(self.src2, self.thr2, sink2) + vbox.Add (sink2.win, 1, wx.EXPAND) + + +def main (): + app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () -- cgit From c3c6eb14f1d8b250322f63fb6b6adc3fb60c67eb Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 14 Aug 2008 19:20:58 +0000 Subject: changed glcanvas test (fedora fix) git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9292 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 5 ++--- gr-wxgui/src/python/scopesink2.py | 5 ++--- gr-wxgui/src/python/waterfallsink2.py | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index ad2a09c96..2d8364aaa 100644 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -32,9 +32,8 @@ if style == 'nongl': from fftsink_nongl import fft_sink_f, fft_sink_c elif style == 'gl': try: - import wx - wx.glcanvas.GLCanvas - except AttributeError: + import wx.glcanvas + except ImportError: raise RuntimeError("wxPython doesn't support glcanvas") try: diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 5eee3efd5..4d9f33afc 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -32,9 +32,8 @@ if style == 'nongl': from scopesink_nongl import scope_sink_f, scope_sink_c elif style == 'gl': try: - import wx - wx.glcanvas.GLCanvas - except AttributeError: + import wx.glcanvas + except ImportError: raise RuntimeError("wxPython doesn't support glcanvas") try: diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index 215193044..c8a4436c7 100644 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -32,9 +32,8 @@ if style == 'nongl': from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c elif style == 'gl': try: - import wx - wx.glcanvas.GLCanvas - except AttributeError: + import wx.glcanvas + except ImportError: raise RuntimeError("wxPython doesn't support glcanvas") try: -- cgit From 2956c43f415c283ce1b1337e78debd896dc4c48a Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 14 Aug 2008 19:53:37 +0000 Subject: more backwards compadibility, marker for scope/xy plotter git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9293 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 4 ++-- gr-wxgui/src/python/const_window.py | 2 +- gr-wxgui/src/python/numbersink2.py | 2 ++ gr-wxgui/src/python/scope_window.py | 19 ++++++++++++++++++- gr-wxgui/src/python/scopesink_gl.py | 4 ++++ 5 files changed, 27 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 84b4c9c4c..21f741c98 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -25,9 +25,9 @@ import math import wx class prop_setter(object): - def _register_set_prop(self, controller, control_key, init=None): + def _register_set_prop(self, controller, control_key, *args): def set_method(value): controller[control_key] = value - if init is not None: set_method(init) + if args: set_method(args[0]) setattr(self, 'set_%s'%control_key, set_method) ################################################## diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 9510416be..366242cb0 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -46,7 +46,7 @@ MARKER_TYPES = ( ('Dot Large', 3.0), ('Line Link', None), ) -DEFAULT_MARKER_TYPE = MARKER_TYPES[1][1] +DEFAULT_MARKER_TYPE = 2.0 ################################################## # Constellation window control panel diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 43eb9e493..4b232d81d 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -118,6 +118,8 @@ class _number_sink_base(gr.hier_block2, common.prop_setter): for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): setattr(self, attr, getattr(self.win, attr)) self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + #backwards compadibility + self.set_show_gauge = self.win.show_gauges def get_average(self): return self._average def set_average(self, average): diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 0c7326b1d..f587f3447 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -58,6 +58,13 @@ CHANNEL_COLOR_SPECS = ( (1, 0, 1), ) AUTORANGE_UPDATE_RATE = 0.5 #sec +MARKER_TYPES = ( + ('Dot Small', 1.0), + ('Dot Medium', 2.0), + ('Dot Large', 3.0), + ('Line Link', None), +) +DEFAULT_MARKER_TYPE = None ################################################## # Scope window control panel @@ -175,7 +182,11 @@ class control_panel(wx.Panel): #autorange check box self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) - #run/stop + #marker + control_box.AddStretchSpacer() + self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) + control_box.Add(self.marker_chooser, 0, wx.EXPAND) + #xy mode control_box.AddStretchSpacer() self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode') parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode) @@ -258,6 +269,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): #check num inputs assert num_inputs <= len(CHANNEL_COLOR_SPECS) #setup + self.sampleses = None self.ext_controller = controller self.num_inputs = num_inputs self.sample_rate_key = sample_rate_key @@ -303,6 +315,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0) self._register_set_prop(self, TRIGGER_MODE_KEY, 1) self._register_set_prop(self, TRIGGER_LEVEL_KEY, None) + self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) #register events self.ext_controller.subscribe(msg_key, self.handle_msg) for key in ( @@ -314,6 +327,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): SCOPE_Y_CHANNEL_KEY, AUTORANGE_KEY, AC_COUPLE_KEY, + MARKER_KEY, ): self.subscribe(key, self.update_grid) #initial update, dont do this here, wait for handle_msg #HACK #self.update_grid() @@ -344,6 +358,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): Handle the cached samples from the scope input. Perform ac coupling, triggering, and auto ranging. """ + if not self.sampleses: return sampleses = self.sampleses #trigger level (must do before ac coupling) self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY] @@ -382,6 +397,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): channel='XY', samples=(x_samples, y_samples), color_spec=CHANNEL_COLOR_SPECS[0], + marker=self[MARKER_KEY], ) #turn off each waveform for i, samples in enumerate(sampleses): @@ -421,6 +437,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): channel='Ch%d'%(i+1), samples=samples[:num_samps], color_spec=CHANNEL_COLOR_SPECS[i], + marker=self[MARKER_KEY], ) #turn XY channel off self.plotter.set_waveform( diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 727e1dc0a..851e43d32 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -114,6 +114,10 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): setattr(self, attr, getattr(self.win, attr)) self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + #backwards compadibility + self.win.set_format_line = lambda: setter(self.win, MARKER_KEY, None) + self.win.set_format_dot = lambda: setter(self.win, MARKER_KEY, 2.0) + self.win.set_format_plus = lambda: setter(self.win, MARKER_KEY, 3.0) class scope_sink_f(_scope_sink_base): _item_size = gr.sizeof_float -- cgit From c5241ccab38d16a529c9ccc9ca10e2fc8e6e5a59 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 14 Aug 2008 22:07:44 +0000 Subject: constsink in scope wrapper (backwards compadible) git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9294 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/scopesink2.py | 4 ++-- gr-wxgui/src/python/scopesink_gl.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 4d9f33afc..8888b6f3a 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -29,7 +29,7 @@ if style == 'auto': style = 'nongl' if style == 'nongl': - from scopesink_nongl import scope_sink_f, scope_sink_c + from scopesink_nongl import scope_sink_f, scope_sink_c, constellation_sink elif style == 'gl': try: import wx.glcanvas @@ -41,4 +41,4 @@ elif style == 'gl': except ImportError: raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - from scopesink_gl import scope_sink_f, scope_sink_c + from scopesink_gl import scope_sink_f, scope_sink_c, constellation_sink diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 851e43d32..8236708b3 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -129,9 +129,9 @@ class scope_sink_c(_scope_sink_base): #backwards compadible wrapper (maybe only grc uses this) class constellation_sink(scope_sink_c): - def __init__(self, **kwargs): - kwargs['xy_mode'] = True - scope_sink_c.__init__(self, **kwargs) + def __init__(self, *args, **kwargs): + scope_sink_c.__init__(self, *args, **kwargs) + self.set_scope_xy_mode(True) # ---------------------------------------------------------------- # Stand-alone test application -- cgit From a04daa744de1bb915c01c7c0c14a3b14a52e64d4 Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 15 Aug 2008 16:08:10 +0000 Subject: dont import plotter git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9298 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/__init__.py | 1 - 1 file changed, 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/__init__.py b/gr-wxgui/src/python/__init__.py index 7be3c38aa..8b1378917 100644 --- a/gr-wxgui/src/python/__init__.py +++ b/gr-wxgui/src/python/__init__.py @@ -1,2 +1 @@ -import plotter -- cgit From e92e82dd65a5c91a6b3a9e098e8f58948c779fa5 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Mon, 18 Aug 2008 20:44:41 +0000 Subject: Make gl selection more robust for testing git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9315 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 10 +++++----- gr-wxgui/src/python/scopesink2.py | 10 +++++----- gr-wxgui/src/python/waterfallsink2.py | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index 2d8364aaa..ecc60834b 100644 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -25,12 +25,9 @@ p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') # In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -if style == 'auto': - style = 'nongl' +# Currently, anything other than 'gl' means 'nongl' -if style == 'nongl': - from fftsink_nongl import fft_sink_f, fft_sink_c -elif style == 'gl': +if style == 'gl': try: import wx.glcanvas except ImportError: @@ -42,3 +39,6 @@ elif style == 'gl': raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") from fftsink_gl import fft_sink_f, fft_sink_c + +else: + from fftsink_nongl import fft_sink_f, fft_sink_c diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 8888b6f3a..40f9f3886 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -25,12 +25,9 @@ p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') # In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -if style == 'auto': - style = 'nongl' +# Currently, anything other than 'gl' means 'nongl' -if style == 'nongl': - from scopesink_nongl import scope_sink_f, scope_sink_c, constellation_sink -elif style == 'gl': +if style == 'gl': try: import wx.glcanvas except ImportError: @@ -42,3 +39,6 @@ elif style == 'gl': raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") from scopesink_gl import scope_sink_f, scope_sink_c, constellation_sink + +else: + from scopesink_nongl import scope_sink_f, scope_sink_c, constellation_sink diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index c8a4436c7..08e96105d 100644 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -25,12 +25,9 @@ p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') # In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -if style == 'auto': - style = 'nongl' +# Currently, anything other than 'gl' means 'nongl' -if style == 'nongl': - from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c -elif style == 'gl': +if style == 'gl': try: import wx.glcanvas except ImportError: @@ -42,3 +39,6 @@ elif style == 'gl': raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") from waterfallsink_gl import waterfall_sink_f, waterfall_sink_c + +else: + from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c -- cgit From 6039ba34aee72974b0eacc9408627a0fa038dc81 Mon Sep 17 00:00:00 2001 From: jblum Date: Tue, 19 Aug 2008 20:53:18 +0000 Subject: plotter: require double buffering git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9333 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/plotter/plotter_base.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 96a1869da..a695fce0a 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -49,11 +49,14 @@ class _plotter_base(wx.glcanvas.GLCanvas): def __init__(self, parent): """! Create a new plotter base. - Initialize GL and register events. + Initialize the GLCanvas with double buffering. + Initialize various plotter flags. + Bind the paint and size events. @param parent the parent widgit """ self._semaphore = threading.Semaphore(1) - wx.glcanvas.GLCanvas.__init__(self, parent, -1) + attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) + wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) self.changed(False) self._gl_init_flag = False self._resized_flag = True @@ -86,7 +89,6 @@ class _plotter_base(wx.glcanvas.GLCanvas): if self._resized_flag: self.lock() 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) -- cgit From ec6d18c95d58707ad89f14e094bf592047048227 Mon Sep 17 00:00:00 2001 From: jblum Date: Tue, 2 Sep 2008 07:24:19 +0000 Subject: replaced semaphore with primitive lock, adjusted drawing bounds git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9482 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/plotter/plotter_base.py | 19 ++++++++++--------- gr-wxgui/src/python/plotter/waterfall_plotter.py | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index a695fce0a..bc42eed1e 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -54,7 +54,7 @@ class _plotter_base(wx.glcanvas.GLCanvas): Bind the paint and size events. @param parent the parent widgit """ - self._semaphore = threading.Semaphore(1) + self._global_lock = threading.Lock() attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) self.changed(False) @@ -64,8 +64,8 @@ class _plotter_base(wx.glcanvas.GLCanvas): self.Bind(wx.EVT_PAINT, self._on_paint) self.Bind(wx.EVT_SIZE, self._on_size) - def lock(self): self._semaphore.acquire(True) - def unlock(self): self._semaphore.release() + def lock(self): self._global_lock.acquire() + def unlock(self): self._global_lock.release() def _on_size(self, event): """! @@ -244,12 +244,13 @@ class grid_plotter_base(_plotter_base): # 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() + 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 Grid X ################################################## diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 88e2b4dc1..42c17da86 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -152,10 +152,10 @@ class waterfall_plotter(grid_plotter_base): glEnable(GL_TEXTURE_2D) glPushMatrix() #matrix scaling - glTranslatef(self.padding_left+1, self.padding_top, 0) + glTranslatef(self.padding_left, self.padding_top, 0) glScalef( - float(self.width-self.padding_left-self.padding_right-1), - float(self.height-self.padding_top-self.padding_bottom-1), + float(self.width-self.padding_left-self.padding_right), + float(self.height-self.padding_top-self.padding_bottom), 1.0, ) #draw texture with wrapping -- cgit From 580a21ac5aa342b79b8d616117478196c5072e71 Mon Sep 17 00:00:00 2001 From: jblum Date: Tue, 2 Sep 2008 18:13:40 +0000 Subject: patched channel plotter -> Stefan Brüns git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9484 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/plotter/channel_plotter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index 22126bd0b..e7e83e5fa 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -107,6 +107,7 @@ class channel_plotter(grid_plotter_base): 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 glPushMatrix() glTranslatef(self.padding_left, self.padding_top, 0) @@ -128,7 +129,7 @@ class channel_plotter(grid_plotter_base): glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) marker = self._channels[channel][MARKERY_KEY] if marker: glPointSize(marker) - glVertexPointer(2, GL_FLOAT, 0, points) + glVertexPointerf(points) glDrawArrays(marker is None and GL_LINE_STRIP or GL_POINTS, 0, len(points)) glPopMatrix() -- cgit From 924831afcdb187b3de670be6fd1f6147d62be5cf Mon Sep 17 00:00:00 2001 From: jblum Date: Wed, 10 Sep 2008 01:23:21 +0000 Subject: Replaced """! with """. Exclamation mark showed in doxygen docs. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9549 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 28 ++++++++--------- gr-wxgui/src/python/const_window.py | 6 ++-- gr-wxgui/src/python/constsink_gl.py | 2 +- gr-wxgui/src/python/fft_window.py | 10 +++--- gr-wxgui/src/python/fftsink_gl.py | 2 +- gr-wxgui/src/python/fftsink_nongl.py | 2 +- gr-wxgui/src/python/number_window.py | 8 ++--- gr-wxgui/src/python/numbersink2.py | 2 +- gr-wxgui/src/python/plotter/channel_plotter.py | 16 +++++----- gr-wxgui/src/python/plotter/plotter_base.py | 40 ++++++++++++------------ gr-wxgui/src/python/plotter/waterfall_plotter.py | 22 ++++++------- gr-wxgui/src/python/pubsub.py | 2 +- gr-wxgui/src/python/scope_window.py | 10 +++--- gr-wxgui/src/python/scopesink_gl.py | 2 +- gr-wxgui/src/python/waterfall_window.py | 10 +++--- gr-wxgui/src/python/waterfallsink_gl.py | 2 +- 16 files changed, 82 insertions(+), 82 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 21f741c98..429764882 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -34,7 +34,7 @@ class prop_setter(object): # 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. @@ -54,7 +54,7 @@ class input_watcher(threading.Thread): # 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. """ @@ -65,11 +65,11 @@ class LabelText(wx.StaticText): 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 @@ -114,7 +114,7 @@ class CheckBoxController(wx.CheckBox): self._controller[self._control_key] = bool(e.IsChecked()) class LogSliderController(wx.BoxSizer): - """! + """ Log slider controller with display label and slider. Gives logarithmic scaling to slider operation. """ @@ -146,12 +146,12 @@ class LogSliderController(wx.BoxSizer): self._label.Disable() class DropDownController(wx.BoxSizer): - """! + """ Drop down controller with label and chooser. Srop down selection from a set of choices. """ def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)): - """! + """ @param parent the parent window @param label the label for the drop down @param choices a list of tuples -> (label, value) @@ -185,7 +185,7 @@ class DropDownController(wx.BoxSizer): # 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 @@ -194,7 +194,7 @@ def get_exp(num): 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 @@ -207,7 +207,7 @@ def get_clean_num(num): return sign*nums[numpy.argmin(numpy.abs(nums - abs(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 @@ -225,7 +225,7 @@ def get_clean_incr(num): }[coeff]*(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 @@ -243,7 +243,7 @@ def get_clean_decr(num): }[coeff]*(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 @@ -256,7 +256,7 @@ def get_min_max(samples): return min, max 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. @@ -281,7 +281,7 @@ def get_si_components(num): return coeff, exp, prefix def label_format(num): - """! + """ Format a floating point number into a presentable string. If the number has an small enough exponent, use regular decimal. Otherwise, format the number with floating point notation. diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 366242cb0..0aea5b596 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -52,11 +52,11 @@ DEFAULT_MARKER_TYPE = 2.0 # 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 """ @@ -152,7 +152,7 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.update_grid() def handle_msg(self, msg): - """! + """ Plot the samples onto the complex grid. @param msg the array of complex samples """ diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py index 2e1f3c5a0..64666e462 100644 --- a/gr-wxgui/src/python/constsink_gl.py +++ b/gr-wxgui/src/python/constsink_gl.py @@ -32,7 +32,7 @@ from constants import * # Constellation sink block (wrapper for old wxgui) ################################################## class const_sink_c(gr.hier_block2, common.prop_setter): - """! + """ A constellation block with a gui window. """ diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 5f48e8324..a8c66dc89 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -46,12 +46,12 @@ NO_PEAK_VALS = list() # 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 """ @@ -192,7 +192,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.update_grid() def autoscale(self, *args): - """! + """ Autoscale the fft plot to the last frame. Set the dynamic range and reference level. """ @@ -211,7 +211,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS 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. @@ -248,7 +248,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.plotter.update() def update_grid(self, *args): - """! + """ Update the plotter grid. This update method is dependent on the variables below. Determine the x and y axis grid parameters. diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index db402618e..7dd200d93 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -32,7 +32,7 @@ from constants import * # FFT sink block (wrapper for old wxgui) ################################################## class _fft_sink_base(gr.hier_block2, common.prop_setter): - """! + """ An fft block with real/complex inputs and a gui window. """ diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index f4e9143f1..d455ddc27 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -278,7 +278,7 @@ class control_panel(wx.Panel): self.update() def update(self): - """! + """ Read the state of the fft plot settings and update the control panel. """ #update checkboxes diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index 8572b7c4a..83f19f6eb 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -42,12 +42,12 @@ DEFAULT_GAUGE_RANGE = 1000 # Number window control panel ################################################## class control_panel(wx.Panel): - """! + """ A control panel with wx widgits to control the averaging. """ def __init__(self, parent): - """! + """ Create a new control panel. @param parent the wx parent window """ @@ -136,7 +136,7 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.ext_controller.subscribe(msg_key, self.handle_msg) def show_gauges(self, show_gauge): - """! + """ Show or hide the gauges. If this is real, never show the imaginary gauge. @param show_gauge true to show @@ -147,7 +147,7 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): else: self.gauge_imag.Hide() def handle_msg(self, msg): - """! + """ Handle a message from the message queue. Convert the string based message into a float or complex. If more than one number was read, only take the last number. diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 4b232d81d..9e52745d8 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -32,7 +32,7 @@ from constants import * # Number sink block (wrapper for old wxgui) ################################################## class _number_sink_base(gr.hier_block2, common.prop_setter): - """! + """ An decimator block with a number window display """ diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index e7e83e5fa..8fa28410b 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -41,7 +41,7 @@ MARKERY_KEY = 'marker' class channel_plotter(grid_plotter_base): def __init__(self, parent): - """! + """ Create a new channel plotter. """ #init @@ -50,14 +50,14 @@ class channel_plotter(grid_plotter_base): self.enable_legend(False) def _gl_init(self): - """! + """ Run gl initialization tasks. """ glEnableClientState(GL_VERTEX_ARRAY) self._grid_compiled_list_id = glGenLists(1) def enable_legend(self, enable=None): - """! + """ Enable/disable the legend. @param enable true to enable @return the enable state when None @@ -69,7 +69,7 @@ class channel_plotter(grid_plotter_base): self.unlock() def draw(self): - """! + """ Draw the grid and waveforms. """ self.lock() @@ -100,7 +100,7 @@ class channel_plotter(grid_plotter_base): self.unlock() def _draw_waveforms(self): - """! + """ Draw the waveforms for each channel. Scale the waveform data to the grid using gl matrix operations. """ @@ -134,7 +134,7 @@ class channel_plotter(grid_plotter_base): glPopMatrix() 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. @@ -164,7 +164,7 @@ class channel_plotter(grid_plotter_base): return label_str 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. @@ -189,7 +189,7 @@ class channel_plotter(grid_plotter_base): x_off -= w + 4*LEGEND_BOX_PADDING def set_waveform(self, channel, samples, color_spec, marker=None): - """! + """ Set the waveform for a given channel. @param channel the channel key @param samples the waveform samples diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index bc42eed1e..6d5349a5f 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -42,12 +42,12 @@ POINT_LABEL_PADDING = 3 # OpenGL WX Plotter Canvas ################################################## class _plotter_base(wx.glcanvas.GLCanvas): - """! + """ 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. @@ -68,14 +68,14 @@ class _plotter_base(wx.glcanvas.GLCanvas): def unlock(self): self._global_lock.release() def _on_size(self, event): - """! + """ Flag the resize event. The paint event will handle the actual resizing. """ self._resized_flag = True def _on_paint(self, event): - """! + """ Respond to paint events, call update. Initialize GL if this is the first paint event. """ @@ -101,7 +101,7 @@ class _plotter_base(wx.glcanvas.GLCanvas): self.draw() def update(self): - """! + """ Force a paint event. Record the timestamp. """ @@ -111,7 +111,7 @@ class _plotter_base(wx.glcanvas.GLCanvas): def clear(self): glClear(GL_COLOR_BUFFER_BIT) def changed(self, state=None): - """! + """ Set the changed flag if state is not None. Otherwise return the changed flag. """ @@ -140,7 +140,7 @@ class grid_plotter_base(_plotter_base): self.Bind(wx.EVT_LEAVE_WINDOW, self._on_leave_window) def _on_motion(self, event): - """! + """ Mouse motion, record the position X, Y. """ self.lock() @@ -150,7 +150,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def _on_leave_window(self, event): - """! + """ Mouse leave window, set the position to None. """ self.lock() @@ -159,7 +159,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def enable_point_label(self, enable=None): - """! + """ Enable/disable the point label. @param enable true to enable @return the enable state when None @@ -171,7 +171,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def set_title(self, title): - """! + """ Set the title. @param title the title string """ @@ -181,7 +181,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def set_x_label(self, x_label, x_units=''): - """! + """ Set the x label and units. @param x_label the x label string @param x_units the x units string @@ -193,7 +193,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def set_y_label(self, y_label, y_units=''): - """! + """ Set the y label and units. @param y_label the y label string @param y_units the y units string @@ -205,7 +205,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def set_x_grid(self, x_min, x_max, x_step, x_scalar=1.0): - """! + """ Set the x grid parameters. @param x_min the left-most value @param x_max the right-most value @@ -221,7 +221,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def set_y_grid(self, y_min, y_max, y_step, y_scalar=1.0): - """! + """ Set the y grid parameters. @param y_min the bottom-most value @param y_max the top-most value @@ -237,7 +237,7 @@ class grid_plotter_base(_plotter_base): self.unlock() def _draw_grid(self): - """! + """ Draw the border, grid, title, and units. """ ################################################## @@ -306,7 +306,7 @@ class grid_plotter_base(_plotter_base): ) def _get_tick_label(self, tick): - """! + """ Format the tick value and create a gl text. @param tick the floating point tick value @return the tick label text @@ -315,7 +315,7 @@ class grid_plotter_base(_plotter_base): 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. @param min the lower bound @param max the upper bound @@ -337,7 +337,7 @@ class grid_plotter_base(_plotter_base): return [i*step*scalar 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 @@ -348,7 +348,7 @@ class grid_plotter_base(_plotter_base): glEnd() 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. @param x the left position of the rectangle @@ -365,7 +365,7 @@ class grid_plotter_base(_plotter_base): 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. diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 42c17da86..4dc19f672 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -38,7 +38,7 @@ PADDING = 35, 60, 40, 60 #top, right, bottom, left ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2))) def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]): - """! + """ Get an array of 256 rgba values where each index maps to a color. The scaling for red, green, blue, alpha are specified in piece-wise functions. The piece-wise functions consist of a set of x, y coordinates. @@ -87,7 +87,7 @@ COLORS = { ################################################## class waterfall_plotter(grid_plotter_base): def __init__(self, parent): - """! + """ Create a new channel plotter. """ #init @@ -103,14 +103,14 @@ class waterfall_plotter(grid_plotter_base): self.set_color_mode(COLORS.keys()[0]) def _gl_init(self): - """! + """ Run gl initialization tasks. """ self._grid_compiled_list_id = glGenLists(1) self._waterfall_texture = glGenTextures(1) def draw(self): - """! + """ Draw the grid and waveforms. """ self.lock() @@ -133,7 +133,7 @@ class waterfall_plotter(grid_plotter_base): self.unlock() def _draw_waterfall(self): - """! + """ Draw the waterfall from the texture. The texture is circularly filled and will wrap around. Use matrix modeling to shift and scale the texture onto the coordinate plane. @@ -176,7 +176,7 @@ class waterfall_plotter(grid_plotter_base): glDisable(GL_TEXTURE_2D) def _populate_point_label(self, x_val, y_val): - """! + """ Get the text the will populate the point label. Give the X value for the current point. @param x_val the current x value @@ -186,7 +186,7 @@ class waterfall_plotter(grid_plotter_base): return '%s: %s %s'%(self.x_label, common.label_format(x_val), self.x_units) def _draw_legend(self): - """! + """ Draw the color scale legend. """ if not self._color_mode: return @@ -213,7 +213,7 @@ class waterfall_plotter(grid_plotter_base): txt.draw_text(wx.Point(x, y)) def _resize_texture(self, flag=None): - """! + """ Create the texture to fit the fft_size X num_lines. @param flag the set/unset or update flag """ @@ -230,7 +230,7 @@ class waterfall_plotter(grid_plotter_base): 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. @@ -244,7 +244,7 @@ class waterfall_plotter(grid_plotter_base): self.unlock() def set_num_lines(self, num_lines): - """! + """ Set number of lines. Powers of two only. @param num_lines the new number of lines @@ -256,7 +256,7 @@ class waterfall_plotter(grid_plotter_base): self.unlock() def set_samples(self, samples, minimum, maximum): - """! + """ Set the samples to the waterfall. Convert the samples to color data. @param samples the array of floats diff --git a/gr-wxgui/src/python/pubsub.py b/gr-wxgui/src/python/pubsub.py index 18aa60603..cc8ea5ccc 100644 --- a/gr-wxgui/src/python/pubsub.py +++ b/gr-wxgui/src/python/pubsub.py @@ -20,7 +20,7 @@ # Boston, MA 02110-1301, USA. # -"""! +""" Abstract GNU Radio publisher/subscriber interface This is a proof of concept implementation, will likely change significantly. diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index f587f3447..2b220c9a7 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -70,11 +70,11 @@ DEFAULT_MARKER_TYPE = None # 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 """ @@ -333,7 +333,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): #self.update_grid() def handle_msg(self, msg): - """! + """ Handle the message from the scope sink message queue. Plot the list of arrays of samples onto the grid. Each samples array gets its own channel. @@ -354,7 +354,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.frame_rate_ts = time.time() def handle_samples(self): - """! + """ Handle the cached samples from the scope input. Perform ac coupling, triggering, and auto ranging. """ @@ -449,7 +449,7 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.plotter.update() def update_grid(self, *args): - """! + """ Update the grid to reflect the current settings: xy divisions, xy offset, xy mode setting """ diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 8236708b3..36d8d8b8a 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -32,7 +32,7 @@ from constants import * # Scope sink block (wrapper for old wxgui) ################################################## class _scope_sink_base(gr.hier_block2, common.prop_setter): - """! + """ A scope block with a gui window. """ diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 3831fca90..51bd01881 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -50,12 +50,12 @@ COLOR_MODES = ( # Waterfall 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 """ @@ -214,7 +214,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.update_grid() def autoscale(self, *args): - """! + """ Autoscale the waterfall plot to the last frame. Set the dynamic range and reference level. Does not affect the current data in the waterfall. @@ -231,7 +231,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.set_dynamic_range(peak_level - noise_floor) 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. @@ -255,7 +255,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.plotter.update() def update_grid(self, *args): - """! + """ Update the plotter grid. This update method is dependent on the variables below. Determine the x and y axis grid parameters. diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index 0b36e1048..dd8e45753 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -32,7 +32,7 @@ from constants import * # Waterfall sink block (wrapper for old wxgui) ################################################## class _waterfall_sink_base(gr.hier_block2, common.prop_setter): - """! + """ An fft block with real/complex inputs and a gui window. """ -- cgit From 407bbe4bb7c8f73ac7d79de2f209f0a0e375cd65 Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 22 Sep 2008 02:37:46 +0000 Subject: config properties and rates for gl sinks git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9637 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/const_window.py | 5 +++-- gr-wxgui/src/python/fft_window.py | 3 ++- gr-wxgui/src/python/number_window.py | 3 ++- gr-wxgui/src/python/scope_window.py | 3 ++- gr-wxgui/src/python/waterfall_window.py | 3 ++- 5 files changed, 11 insertions(+), 6 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 0aea5b596..cb7236b49 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -29,6 +29,7 @@ import numpy import math import pubsub from constants import * +from gnuradio import gr #for gr.prefs ################################################## # Constants @@ -36,9 +37,9 @@ from constants import * SLIDER_STEPS = 200 ALPHA_MIN_EXP, ALPHA_MAX_EXP = -6, -0.301 GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP = -6, -0.301 -DEFAULT_FRAME_RATE = 5 +DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'const_rate', 5) DEFAULT_WIN_SIZE = (500, 400) -DEFAULT_CONST_SIZE = 2048 +DEFAULT_CONST_SIZE = gr.prefs().get_long('wxgui', 'const_size', 2048) CONST_PLOT_COLOR_SPEC = (0, 0, 1) MARKER_TYPES = ( ('Dot Small', 1.0), diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index a8c66dc89..e7affb1b0 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -29,6 +29,7 @@ import numpy import math import pubsub from constants import * +from gnuradio import gr #for gr.prefs ################################################## # Constants @@ -36,7 +37,7 @@ from constants import * SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_WIN_SIZE = (600, 300) -DEFAULT_FRAME_RATE = 30 +DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DIV_LEVELS = (1, 2, 5, 10, 20) FFT_PLOT_COLOR_SPEC = (0, 0, 1) PEAK_VALS_COLOR_SPEC = (0, 1, 0) diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index 83f19f6eb..e3fc4653e 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -27,6 +27,7 @@ import numpy import wx import pubsub from constants import * +from gnuradio import gr #for gr.prefs ################################################## # Constants @@ -34,7 +35,7 @@ from constants import * NEG_INF = float('-inf') SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 -DEFAULT_NUMBER_RATE = 5 +DEFAULT_NUMBER_RATE = gr.prefs().get_long('wxgui', 'number_rate', 5) DEFAULT_WIN_SIZE = (300, 300) DEFAULT_GAUGE_RANGE = 1000 diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 2b220c9a7..57905ca4d 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -29,11 +29,12 @@ import numpy import time import pubsub from constants import * +from gnuradio import gr #for gr.prefs ################################################## # Constants ################################################## -DEFAULT_FRAME_RATE = 30 +DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) DEFAULT_WIN_SIZE = (600, 300) DEFAULT_V_SCALE = 1000 TRIGGER_MODES = ( diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 51bd01881..702f97099 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -29,13 +29,14 @@ import numpy import math import pubsub from constants import * +from gnuradio import gr #for gr.prefs ################################################## # Constants ################################################## SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 -DEFAULT_FRAME_RATE = 30 +DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30) DEFAULT_WIN_SIZE = (600, 300) DIV_LEVELS = (1, 2, 5, 10, 20) MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200 -- cgit From c77463ef0bdb7ef7857c0e78b2821ff03796ccc0 Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 22 Sep 2008 14:05:09 +0000 Subject: removed redundant pref git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9638 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink_gl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 7dd200d93..0c6f38dc0 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -46,7 +46,7 @@ class _fft_sink_base(gr.hier_block2, common.prop_setter): ref_level=50, sample_rate=1, fft_size=512, - fft_rate=gr.prefs().get_long('wxgui', 'fft_rate', fft_window.DEFAULT_FRAME_RATE), + fft_rate=fft_window.DEFAULT_FRAME_RATE, average=False, avg_alpha=None, title='', -- cgit From 55ef8d97a4de54f41df8fe9fa53397ab813a8af8 Mon Sep 17 00:00:00 2001 From: jblum Date: Wed, 8 Oct 2008 14:56:34 +0000 Subject: custom wx event to post data, avoid threading issues git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9749 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 6 ++++++ gr-wxgui/src/python/number_window.py | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 429764882..e54f4dbf1 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -24,6 +24,12 @@ import numpy import math import wx +EVT_DATA = wx.NewEventType() +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA) + self.data = data + class prop_setter(object): def _register_set_prop(self, controller, control_key, *args): def set_method(value): controller[control_key] = value diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index e3fc4653e..dbdb68120 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -135,6 +135,7 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): self._register_set_prop(self, RUNNING_KEY, True) #register events self.ext_controller.subscribe(msg_key, self.handle_msg) + self.Connect(wx.ID_ANY, wx.ID_ANY, common.EVT_DATA, self.update) def show_gauges(self, show_gauge): """ @@ -148,12 +149,20 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): else: self.gauge_imag.Hide() def handle_msg(self, msg): + """ + Post this message into a data event. + Allow wx to handle the event to avoid threading issues. + @param msg the incoming numbersink data + """ + wx.PostEvent(self, common.DataEvent(msg)) + + def update(self, event): """ Handle a message from the message queue. Convert the string based message into a float or complex. If more than one number was read, only take the last number. Perform peak hold operations, set the gauges and display. - @param msg the number sample as a character array + @param event event.data is the number sample as a character array """ if not self[RUNNING_KEY]: return #set gauge @@ -164,12 +173,12 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): gauge.SetValue(gauge_val) format_string = "%%.%df"%self.decimal_places if self.real: - sample = numpy.fromstring(msg, numpy.float32)[-1] + sample = numpy.fromstring(event.data, numpy.float32)[-1] if self[PEAK_HOLD_KEY]: sample = self.peak_val_real = max(self.peak_val_real, sample) label_text = "%s %s"%(format_string%sample, self.units) set_gauge_value(self.gauge_real, sample) else: - sample = numpy.fromstring(msg, numpy.complex64)[-1] + sample = numpy.fromstring(event.data, numpy.complex64)[-1] if self[PEAK_HOLD_KEY]: self.peak_val_real = max(self.peak_val_real, sample.real) self.peak_val_imag = max(self.peak_val_imag, sample.imag) -- cgit From 73f3c91a5ad2acbb9f32bd440f2d4ea9ab54ac08 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 9 Oct 2008 01:42:25 +0000 Subject: proper custom wxPython event, in wxPython style git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9760 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 15 +++++++++------ gr-wxgui/src/python/number_window.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index e54f4dbf1..f31e1a708 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -24,18 +24,21 @@ import numpy import math import wx -EVT_DATA = wx.NewEventType() -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA) - self.data = data - class prop_setter(object): def _register_set_prop(self, controller, control_key, *args): def set_method(value): controller[control_key] = value if args: set_method(args[0]) setattr(self, 'set_%s'%control_key, set_method) +################################################## +# Custom Data Event +################################################## +EVT_DATA = wx.PyEventBinder(wx.NewEventType()) +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) + self.data = data + ################################################## # Input Watcher Thread ################################################## diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index dbdb68120..e766c68c1 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -135,7 +135,7 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): self._register_set_prop(self, RUNNING_KEY, True) #register events self.ext_controller.subscribe(msg_key, self.handle_msg) - self.Connect(wx.ID_ANY, wx.ID_ANY, common.EVT_DATA, self.update) + self.Bind(common.EVT_DATA, self.update) def show_gauges(self, show_gauge): """ -- cgit From 3fc664ececbafa04ab5a117b418cb7b4fc0652c6 Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 10 Oct 2008 21:13:33 +0000 Subject: check for samples before autoscale, avoids potential error condition git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9767 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fft_window.py | 2 ++ gr-wxgui/src/python/waterfall_window.py | 2 ++ 2 files changed, 4 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index e7affb1b0..6e54aec87 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -149,6 +149,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): #ensure y_per_div if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0] #setup + self.samples = list() self.ext_controller = controller self.real = real self.fft_size = fft_size @@ -197,6 +198,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): Autoscale the fft plot to the last frame. Set the dynamic range and reference level. """ + if not len(self.samples): return #get the peak level (max of the samples) peak_level = numpy.max(self.samples) #get the noise floor (averge the smallest samples) diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 702f97099..f24b142a7 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -168,6 +168,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): ): pubsub.pubsub.__init__(self) #setup + self.samples = list() self.ext_controller = controller self.real = real self.fft_size = fft_size @@ -220,6 +221,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): Set the dynamic range and reference level. Does not affect the current data in the waterfall. """ + if not len(self.samples): return #get the peak level (max of the samples) peak_level = numpy.max(self.samples) #get the noise floor (averge the smallest samples) -- cgit From f6d78800dc5b9db2025ffc2855f09f5cc79b16a6 Mon Sep 17 00:00:00 2001 From: jblum Date: Wed, 28 Jan 2009 19:52:45 +0000 Subject: fixed 2 scaling bugs in scopesink git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10329 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 2 +- gr-wxgui/src/python/scope_window.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index f31e1a708..e7d08891d 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -259,7 +259,7 @@ def get_min_max(samples): """ scale_factor = 3 mean = numpy.average(samples) - rms = scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5) + rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1]) min = mean - rms max = mean + rms return min, max diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 57905ca4d..7d4f97113 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -411,8 +411,8 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): #autorange if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: bounds = [common.get_min_max(samples) for samples in sampleses] - y_min = numpy.min(*[bound[0] for bound in bounds]) - y_max = numpy.max(*[bound[1] for bound in bounds]) + y_min = numpy.min([bound[0] for bound in bounds]) + y_max = numpy.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_KEY]) if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) -- cgit From f92986d6ff10330ee4608510f7baaa895e6772e0 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sun, 22 Feb 2009 20:20:34 +0000 Subject: Adds new simple gui class to gr-wxgui. Apps using it to come. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10474 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 1 + gr-wxgui/src/python/form.py | 2 +- gr-wxgui/src/python/gui.py | 126 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 gr-wxgui/src/python/gui.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index a85bd0920..58a9f5754 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -41,6 +41,7 @@ ourpython_PYTHON = \ fftsink_nongl.py \ fftsink_gl.py \ fft_window.py \ + gui.py \ numbersink2.py \ number_window.py \ plot.py \ diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index 3e303554d..e635c5461 100644 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -273,7 +273,7 @@ class radiobox_field(field): else: style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL - w = wx.RadioBox(parent, new_id, label, style=style, majorDimension=major_dimension, + w = wx.RadioBox(parent, new_id, label="", style=style, majorDimension=major_dimension, choices=choices) self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) if callback: diff --git a/gr-wxgui/src/python/gui.py b/gr-wxgui/src/python/gui.py new file mode 100644 index 000000000..9cb66c4a1 --- /dev/null +++ b/gr-wxgui/src/python/gui.py @@ -0,0 +1,126 @@ +# +# 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 gnuradio import gr + +# +# Top-level display panel with vertical box sizer. User does not create or +# subclass this class; rather, the user supplies his own class constructor +# that gets invoked with needed parameters. +# +class top_panel(wx.Panel): + def __init__(self, frame, top_block, gui, options, args): + wx.Panel.__init__(self, frame, -1) + vbox = wx.BoxSizer(wx.VERTICAL) + + # Create the user's GUI class + if gui is not None: + self.gui = gui(frame, # Top-level window frame + self, # Parent class for user created windows + vbox, # Sizer for user to add windows to + top_block, # GUI-unaware flowgraph to manipulate + options, # Command-line options + args) # Command-line arguments + + else: + # User hasn't made their own GUI, create our default + # We don't have a default GUI yet either :) + p = wx.Panel(self) + p.SetSize((640,480)) + vbox.Add(p, 1, wx.EXPAND) + + self.SetSizer(vbox) + self.SetAutoLayout(True) + vbox.Fit(self) + + +# +# Top-level window frame with menu and status bars. +# +class top_frame(wx.Frame): + def __init__ (self, top_block, gui, options, args, + title, nstatus, start, realtime): + + wx.Frame.__init__(self, None, -1, title) + self.top_block = top_block + + self.CreateStatusBar(nstatus) + mainmenu = wx.MenuBar() + self.SetMenuBar(mainmenu) + + menu = wx.Menu() + + item = menu.Append(200, 'E&xit', 'Exit Application') # FIXME magic ID + self.Bind(wx.EVT_MENU, self.OnCloseWindow, item) + mainmenu.Append(menu, "&File") + self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) + + # Create main panel, creates user GUI class with supplied parameters + self.panel = top_panel(self, top_block, gui, options, args) + + vbox = wx.BoxSizer(wx.VERTICAL) + vbox.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(vbox) + self.SetAutoLayout(True) + vbox.Fit(self) + + if realtime: + if gr.enable_realtime_scheduling() != gr.RT_OK: + self.SetStatusText("Failed to enable realtime scheduling") + + if start and self.top_block is not None: + self.top_block.start() + + def OnCloseWindow(self, event): + self.SetStatusText("Ensuring flowgraph has completed before exiting...") + if self.top_block is not None: + self.top_block.stop() + self.top_block.wait() + + self.Destroy() + + +# +# Top-level wxPython application object. User creates or subclasses this +# in their GUI script. +# +class app(wx.App): + def __init__ (self, top_block=None, gui=None, options=None, args=None, + title="GNU Radio", nstatus=1, start=False, realtime=False): + self.top_block = top_block + self.gui = gui + self.options = options + self.args = args + self.title = title + self.nstatus = nstatus + self.start = start + self.realtime = realtime + + wx.App.__init__ (self, redirect=False) + + def OnInit(self): + # Pass user parameters to top window frame + frame = top_frame(self.top_block, self.gui, self.options, self.args, + self.title, self.nstatus, self.start, self.realtime) + frame.Show(True) + self.SetTopWindow(frame) + return True -- cgit From 1df84e329951c51ce7fedd5eb3a2bd1e4bb8ad3d Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sun, 8 Mar 2009 02:38:18 +0000 Subject: Fix radiobox label git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10574 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/form.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index e635c5461..b55b04d73 100644 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -263,7 +263,7 @@ class checkbox_field(field): class radiobox_field(field): - def __init__(self, parent=None, sizer=None, label="", value=None, + def __init__(self, parent=None, sizer=None, label=None, value=None, converter=identity_converter(), callback=None, weight=1, choices=None, major_dimension=1, specify_rows=False): new_id = wx.NewId() @@ -273,9 +273,9 @@ class radiobox_field(field): else: style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL - w = wx.RadioBox(parent, new_id, label="", style=style, majorDimension=major_dimension, + w = wx.RadioBox(parent, new_id, label=label, style=style, majorDimension=major_dimension, choices=choices) - self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=label, weight=weight) + self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) if callback: wx.EVT_RADIOBOX(w, new_id, lambda evt: callback(evt.GetString())) field.__init__(self, converter, value) -- cgit From 48ecbf1162f9031ab7134bbb571efc345bc8d7bd Mon Sep 17 00:00:00 2001 From: jcorgan Date: Wed, 11 Mar 2009 20:52:08 +0000 Subject: Add shutdown hook git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10582 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/gui.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/gui.py b/gr-wxgui/src/python/gui.py index 9cb66c4a1..2f59af593 100644 --- a/gr-wxgui/src/python/gui.py +++ b/gr-wxgui/src/python/gui.py @@ -52,6 +52,11 @@ class top_panel(wx.Panel): self.SetAutoLayout(True) vbox.Fit(self) + def shutdown(self): + try: + self.gui.shutdown() + except AttributeError: + pass # # Top-level window frame with menu and status bars. @@ -91,6 +96,10 @@ class top_frame(wx.Frame): self.top_block.start() def OnCloseWindow(self, event): + # Give user API a chance to do something + self.panel.shutdown() + + # Stop flowgraph as a convenience self.SetStatusText("Ensuring flowgraph has completed before exiting...") if self.top_block is not None: self.top_block.stop() -- cgit From d9744799b7df6c09180778540bf55eb9dc84281b Mon Sep 17 00:00:00 2001 From: jcorgan Date: Fri, 20 Mar 2009 02:16:20 +0000 Subject: Merged r10463:10658 from jblum/gui_guts into trunk. Trunk passes distcheck. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10660 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 2 + gr-wxgui/src/python/common.py | 226 +++++---- gr-wxgui/src/python/const_window.py | 80 +-- gr-wxgui/src/python/constants.py | 18 +- gr-wxgui/src/python/constsink_gl.py | 19 +- gr-wxgui/src/python/fft_window.py | 123 ++--- gr-wxgui/src/python/fftsink_gl.py | 15 +- gr-wxgui/src/python/fftsink_nongl.py | 2 +- gr-wxgui/src/python/histo_window.py | 154 ++++++ gr-wxgui/src/python/histosink_gl.py | 110 +++++ gr-wxgui/src/python/number_window.py | 35 +- gr-wxgui/src/python/numbersink2.py | 44 +- gr-wxgui/src/python/plotter/Makefile.am | 5 +- gr-wxgui/src/python/plotter/__init__.py | 1 + gr-wxgui/src/python/plotter/bar_plotter.py | 144 ++++++ gr-wxgui/src/python/plotter/channel_plotter.py | 127 ++--- gr-wxgui/src/python/plotter/common.py | 131 +++++ gr-wxgui/src/python/plotter/grid_plotter_base.py | 370 ++++++++++++++ gr-wxgui/src/python/plotter/plotter_base.py | 418 ++++------------ gr-wxgui/src/python/plotter/waterfall_plotter.py | 107 ++-- gr-wxgui/src/python/pubsub.py | 106 ++-- gr-wxgui/src/python/scope_window.py | 589 ++++++++++++----------- gr-wxgui/src/python/scopesink2.py | 4 +- gr-wxgui/src/python/scopesink_gl.py | 115 +++-- gr-wxgui/src/python/scopesink_nongl.py | 22 +- gr-wxgui/src/python/waterfall_window.py | 150 +++--- gr-wxgui/src/python/waterfallsink_gl.py | 14 +- 27 files changed, 1918 insertions(+), 1213 deletions(-) create mode 100644 gr-wxgui/src/python/histo_window.py create mode 100644 gr-wxgui/src/python/histosink_gl.py create mode 100644 gr-wxgui/src/python/plotter/bar_plotter.py create mode 100644 gr-wxgui/src/python/plotter/common.py create mode 100644 gr-wxgui/src/python/plotter/grid_plotter_base.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 58a9f5754..45d75b605 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -42,6 +42,8 @@ ourpython_PYTHON = \ fftsink_gl.py \ fft_window.py \ gui.py \ + histosink_gl.py \ + histo_window.py \ numbersink2.py \ number_window.py \ plot.py \ diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index e7d08891d..c84827eb8 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -19,60 +19,86 @@ # Boston, MA 02110-1301, USA. # -import threading -import numpy -import math -import wx +#A macro to apply an index to a key +index_key = lambda key, i: "%s_%d"%(key, i+1) -class prop_setter(object): - def _register_set_prop(self, controller, control_key, *args): - def set_method(value): controller[control_key] = value - if args: set_method(args[0]) - setattr(self, 'set_%s'%control_key, set_method) +def _register_access_method(destination, controller, key): + """ + Helper function for register access methods. + This helper creates distinct set and get methods for each key + and adds them to the destination object. + """ + def set(value): controller[key] = value + setattr(destination, 'set_'+key, set) + def get(): return controller[key] + setattr(destination, 'get_'+key, get) -################################################## -# Custom Data Event -################################################## -EVT_DATA = wx.PyEventBinder(wx.NewEventType()) -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) - self.data = data +def register_access_methods(destination, controller): + """ + Register setter and getter functions in the destination object for all keys in the controller. + @param destination the object to get new setter and getter methods + @param controller the pubsub controller + """ + for key in controller.keys(): _register_access_method(destination, controller, key) ################################################## # Input Watcher Thread ################################################## +import threading + 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): + def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''): threading.Thread.__init__(self) self.setDaemon(1) self.msgq = msgq - self._handle_msg = handle_msg + self._controller = controller + self._msg_key = msg_key + self._arg1_key = arg1_key + self._arg2_key = arg2_key self.keep_running = True self.start() def run(self): - while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string()) + while self.keep_running: + msg = self.msgq.delete_head() + if self._arg1_key: self._controller[self._arg1_key] = msg.arg1() + if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() + self._controller[self._msg_key] = msg.to_string() ################################################## # WX Shared Classes ################################################## +import math +import wx + +EVT_DATA = wx.PyEventBinder(wx.NewEventType()) +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) + self.data = data + 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) + wx.StaticText.__init__(self, parent, label=label) font = self.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) self.SetFont(font) +class LabelBox(wx.BoxSizer): + def __init__(self, parent, label, widget): + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + self.Add(wx.StaticText(parent, label=' %s '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + class IncrDecrButtons(wx.BoxSizer): """ A horizontal box sizer with a increment and a decrement button. @@ -84,14 +110,14 @@ class IncrDecrButtons(wx.BoxSizer): @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 = wx.Button(parent, label='+', 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 = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT) self._decr_button.Bind(wx.EVT_BUTTON, on_decr) self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) - def Disable(self, disable=True): self.Enable(not disable) + def Disable(self): self.Enable(False) def Enable(self, enable=True): if enable: self._incr_button.Enable() @@ -104,7 +130,7 @@ class ToggleButtonController(wx.Button): def __init__(self, parent, controller, control_key, true_label, false_label): self._controller = controller self._control_key = control_key - wx.Button.__init__(self, parent, -1, '', style=wx.BU_EXACTFIT) + wx.Button.__init__(self, parent, style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self._evt_button) controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label)) @@ -122,30 +148,55 @@ class CheckBoxController(wx.CheckBox): def _evt_checkbox(self, e): self._controller[self._control_key] = bool(e.IsChecked()) +from gnuradio import eng_notation + +class TextBoxController(wx.TextCtrl): + def __init__(self, parent, controller, control_key, cast=float): + self._controller = controller + self._control_key = control_key + self._cast = cast + wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self._evt_enter) + controller.subscribe(control_key, lambda x: self.SetValue(eng_notation.num_to_str(x))) + + def _evt_enter(self, e): + try: self._controller[self._control_key] = self._cast(eng_notation.str_to_num(self.GetValue())) + except: self._controller[self._control_key] = self._controller[self._control_key] + class LogSliderController(wx.BoxSizer): """ Log slider controller with display label and slider. Gives logarithmic scaling to slider operation. """ - def __init__(self, parent, label, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): + def __init__(self, parent, prefix, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): + self._prefix = prefix + self._min_exp = min_exp + self._max_exp = max_exp + self._controller = controller + self._control_key = control_key + self._formatter = formatter wx.BoxSizer.__init__(self, wx.VERTICAL) - self._label = wx.StaticText(parent, -1, label + formatter(1/3.0)) + self._label = wx.StaticText(parent, label=prefix + formatter(1/3.0)) self.Add(self._label, 0, wx.EXPAND) - self._slider = wx.Slider(parent, -1, 0, 0, slider_steps, style=wx.SL_HORIZONTAL) + self._slider = wx.Slider(parent, minValue=0, maxValue=slider_steps, style=wx.SL_HORIZONTAL) self.Add(self._slider, 0, wx.EXPAND) - def _on_slider_event(event): - controller[control_key] = \ - 10**(float(max_exp-min_exp)*self._slider.GetValue()/slider_steps + min_exp) - self._slider.Bind(wx.EVT_SLIDER, _on_slider_event) - def _on_controller_set(value): - self._label.SetLabel(label + formatter(value)) - slider_value = slider_steps*(math.log10(value)-min_exp)/(max_exp-min_exp) - slider_value = min(max(0, slider_value), slider_steps) - if abs(slider_value - self._slider.GetValue()) > 1: - self._slider.SetValue(slider_value) - controller.subscribe(control_key, _on_controller_set) - - def Disable(self, disable=True): self.Enable(not disable) + self._slider.Bind(wx.EVT_SLIDER, self._on_slider_event) + controller.subscribe(control_key, self._on_controller_set) + + def _get_slope(self): + return float(self._max_exp-self._min_exp)/self._slider.GetMax() + + def _on_slider_event(self, e): + self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp) + + def _on_controller_set(self, value): + self._label.SetLabel(self._prefix + self._formatter(value)) + slider_value = (math.log10(value)-self._min_exp)/self._get_slope() + slider_value = min(max(self._slider.GetMin(), slider_value), self._slider.GetMax()) + if abs(slider_value - self._slider.GetValue()) > 1: + self._slider.SetValue(slider_value) + + def Disable(self): self.Enable(False) def Enable(self, enable=True): if enable: self._slider.Enable() @@ -154,45 +205,39 @@ class LogSliderController(wx.BoxSizer): self._slider.Disable() self._label.Disable() -class DropDownController(wx.BoxSizer): +class DropDownController(wx.Choice): """ Drop down controller with label and chooser. Srop down selection from a set of choices. """ - def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)): + def __init__(self, parent, choices, controller, control_key, size=(-1, -1)): """ @param parent the parent window - @param label the label for the drop down @param choices a list of tuples -> (label, value) @param controller the prop val controller @param control_key the prop key for this control """ - wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self._label = wx.StaticText(parent, -1, ' %s '%label) - self.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL) - self._chooser = wx.Choice(parent, -1, choices=[c[0] for c in choices], size=size) - def _on_chooser_event(event): - controller[control_key] = choices[self._chooser.GetSelection()][1] - self._chooser.Bind(wx.EVT_CHOICE, _on_chooser_event) - self.Add(self._chooser, 0, wx.ALIGN_CENTER_VERTICAL) - def _on_controller_set(value): - #only set the chooser if the value is a possible choice - for i, choice in enumerate(choices): - if value == choice[1]: self._chooser.SetSelection(i) - controller.subscribe(control_key, _on_controller_set) - - def Disable(self, disable=True): self.Enable(not disable) - def Enable(self, enable=True): - if enable: - self._chooser.Enable() - self._label.Enable() - else: - self._chooser.Disable() - self._label.Disable() + self._controller = controller + self._control_key = control_key + self._choices = choices + wx.Choice.__init__(self, parent, choices=[c[0] for c in choices], size=size) + self.Bind(wx.EVT_CHOICE, self._on_chooser_event) + controller.subscribe(control_key, self._on_controller_set) + + def _on_chooser_event(self, e): + self._controller[self._control_key] = self._choices[self.GetSelection()][1] + + def _on_controller_set(self, value): + #only set the chooser if the value is a possible choice + for i, choice in enumerate(self._choices): + if value == choice[1]: self.SetSelection(i) ################################################## # Shared Functions ################################################## +import numpy +import math + def get_exp(num): """ Get the exponent of the number in base 10. @@ -209,8 +254,7 @@ def get_clean_num(num): @return the closest number """ if num == 0: return 0 - if num > 0: sign = 1 - else: sign = -1 + sign = num > 0 and 1 or -1 exp = get_exp(num) nums = numpy.array((1, 2, 5, 10))*(10**exp) return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))] @@ -263,49 +307,3 @@ def get_min_max(samples): min = mean - rms max = mean + rms return min, max - -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. - @param num the floating point number - @return the tuple coeff, exp, prefix - """ - 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 label_format(num): - """ - Format a floating point number into a presentable string. - If the number has an small enough exponent, use regular decimal. - Otherwise, format the number with floating point notation. - Exponents are normalized to multiples of 3. - In the case where the exponent was found to be -3, - it is best to display this as a regular decimal, with a 0 to the left. - @param num the number to format - @return a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%se%d'%('%.3g'%coeff, exp) - -if __name__ == '__main__': - import random - for i in range(-25, 25): - num = random.random()*10**i - print num, ':', get_si_components(num) diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index cb7236b49..8e0e61ac3 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -62,32 +62,31 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) self.marker_index = 2 #begin control box - control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - #marker - control_box.AddStretchSpacer() - self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(self.marker_chooser, 0, wx.EXPAND) #alpha control_box.AddStretchSpacer() - self.alpha_slider = common.LogSliderController( + alpha_slider = common.LogSliderController( self, 'Alpha', ALPHA_MIN_EXP, ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.alpha_key, + parent, ALPHA_KEY, ) - control_box.Add(self.alpha_slider, 0, wx.EXPAND) + control_box.Add(alpha_slider, 0, wx.EXPAND) #gain_mu control_box.AddStretchSpacer() - self.gain_mu_slider = common.LogSliderController( + gain_mu_slider = common.LogSliderController( self, 'Gain Mu', GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.gain_mu_key, + parent, GAIN_MU_KEY, ) - control_box.Add(self.gain_mu_slider, 0, wx.EXPAND) + control_box.Add(gain_mu_slider, 0, wx.EXPAND) + #marker + control_box.AddStretchSpacer() + marker_chooser = common.DropDownController(self, MARKER_TYPES, parent, MARKER_KEY) + control_box.Add(common.LabelBox(self, 'Marker', marker_chooser), 0, wx.EXPAND) #run/stop control_box.AddStretchSpacer() self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') @@ -98,7 +97,7 @@ class control_panel(wx.Panel): ################################################## # Constellation window with plotter and control panel ################################################## -class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class const_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -110,22 +109,27 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): beta_key, gain_mu_key, gain_omega_key, + omega_key, + sample_rate_key, ): pubsub.pubsub.__init__(self) - #setup - self.ext_controller = controller - self.alpha_key = alpha_key - self.beta_key = beta_key - self.gain_mu_key = gain_mu_key - self.gain_omega_key = gain_omega_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(ALPHA_KEY, controller, alpha_key) + self.proxy(BETA_KEY, controller, beta_key) + self.proxy(GAIN_MU_KEY, controller, gain_mu_key) + self.proxy(GAIN_OMEGA_KEY, controller, gain_omega_key) + self.proxy(OMEGA_KEY, controller, omega_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.set_x_label('Inphase') self.plotter.set_y_label('Quadrature') self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -133,22 +137,21 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) #alpha and gain mu 2nd orders - def set_beta(alpha): self.ext_controller[self.beta_key] = .25*alpha**2 - self.ext_controller.subscribe(self.alpha_key, set_beta) - def set_gain_omega(gain_mu): self.ext_controller[self.gain_omega_key] = .25*gain_mu**2 - self.ext_controller.subscribe(self.gain_mu_key, set_gain_omega) - #initial setup - self.ext_controller[self.alpha_key] = self.ext_controller[self.alpha_key] - self.ext_controller[self.gain_mu_key] = self.ext_controller[self.gain_mu_key] - self._register_set_prop(self, RUNNING_KEY, True) - self._register_set_prop(self, X_DIVS_KEY, 8) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) + def set_beta(alpha): self[BETA_KEY] = .25*alpha**2 + self.subscribe(ALPHA_KEY, set_beta) + def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 + self.subscribe(GAIN_MU_KEY, set_gain_omega) + #initialize values + self[ALPHA_KEY] = self[ALPHA_KEY] + self[GAIN_MU_KEY] = self[GAIN_MU_KEY] + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[MARKER_KEY] = DEFAULT_MARKER_TYPE #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - for key in ( - X_DIVS_KEY, Y_DIVS_KEY, - ): self.subscribe(key, self.update_grid) + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(Y_DIVS_KEY, self.update_grid) #initial update self.update_grid() @@ -173,15 +176,12 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.plotter.update() def update_grid(self): - #grid parameters - x_divs = self[X_DIVS_KEY] - y_divs = self[Y_DIVS_KEY] #update the x axis x_max = 2.0 - self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/x_divs)) + self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/self[X_DIVS_KEY])) #update the y axis y_max = 2.0 - self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/y_divs)) + self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/self[Y_DIVS_KEY])) #update plotter self.plotter.update() diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 06c5a44f1..a4ccdca6d 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -36,6 +36,7 @@ FRAME_RATE_KEY = 'frame_rate' GAIN_MU_KEY = 'gain_mu' GAIN_OMEGA_KEY = 'gain_omega' MARKER_KEY = 'marker' +XY_MARKER_KEY = 'xy_marker' MSG_KEY = 'msg' NUM_LINES_KEY = 'num_lines' OMEGA_KEY = 'omega' @@ -43,15 +44,15 @@ PEAK_HOLD_KEY = 'peak_hold' REF_LEVEL_KEY = 'ref_level' RUNNING_KEY = 'running' SAMPLE_RATE_KEY = 'sample_rate' -SCOPE_TRIGGER_CHANNEL_KEY = 'scope_trigger_channel' -SCOPE_TRIGGER_LEVEL_KEY = 'scope_trigger_level' -SCOPE_TRIGGER_MODE_KEY = 'scope_trigger_mode' -SCOPE_X_CHANNEL_KEY = 'scope_x_channel' -SCOPE_Y_CHANNEL_KEY = 'scope_y_channel' -SCOPE_XY_MODE_KEY = 'scope_xy_mode' TRIGGER_CHANNEL_KEY = 'trigger_channel' TRIGGER_LEVEL_KEY = 'trigger_level' TRIGGER_MODE_KEY = 'trigger_mode' +TRIGGER_SLOPE_KEY = 'trigger_slope' +TRIGGER_SHOW_KEY = 'trigger_show' +XY_MODE_KEY = 'xy_mode' +X_CHANNEL_KEY = 'x_channel' +Y_CHANNEL_KEY = 'y_channel' +T_FRAC_OFF_KEY = 't_frac_off' T_DIVS_KEY = 't_divs' T_OFF_KEY = 't_off' T_PER_DIV_KEY = 't_per_div' @@ -61,4 +62,7 @@ X_PER_DIV_KEY = 'x_per_div' Y_DIVS_KEY = 'y_divs' Y_OFF_KEY = 'y_off' Y_PER_DIV_KEY = 'y_per_div' - +MAXIMUM_KEY = 'maximum' +MINIMUM_KEY = 'minimum' +NUM_BINS_KEY = 'num_bins' +FRAME_SIZE_KEY = 'frame_size' diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py index 64666e462..b3a1625b0 100644 --- a/gr-wxgui/src/python/constsink_gl.py +++ b/gr-wxgui/src/python/constsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Constellation sink block (wrapper for old wxgui) ################################################## -class const_sink_c(gr.hier_block2, common.prop_setter): +class const_sink_c(gr.hier_block2): """ A constellation block with a gui window. """ @@ -97,8 +97,7 @@ class const_sink_c(gr.hier_block2, common.prop_setter): #connect self.connect(self, self._costas, self._retime, agc, sd, sink) #controller - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x + def setter(p, k, x): p[k] = x self.controller = pubsub() self.controller.subscribe(ALPHA_KEY, self._costas.set_alpha) self.controller.publish(ALPHA_KEY, self._costas.alpha) @@ -116,7 +115,7 @@ class const_sink_c(gr.hier_block2, common.prop_setter): #initial update self.controller[SAMPLE_RATE_KEY] = sample_rate #start input watcher - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = const_window.const_window( parent=parent, @@ -128,15 +127,9 @@ class const_sink_c(gr.hier_block2, common.prop_setter): beta_key=BETA_KEY, gain_mu_key=GAIN_MU_KEY, gain_omega_key=GAIN_OMEGA_KEY, + omega_key=OMEGA_KEY, + sample_rate_key=SAMPLE_RATE_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, ALPHA_KEY) - self._register_set_prop(self.controller, BETA_KEY) - self._register_set_prop(self.controller, GAIN_MU_KEY) - self._register_set_prop(self.controller, OMEGA_KEY) - self._register_set_prop(self.controller, GAIN_OMEGA_KEY) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + common.register_access_methods(self, self.win) diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 6e54aec87..fdd5562dc 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -39,8 +39,8 @@ AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DIV_LEVELS = (1, 2, 5, 10, 20) -FFT_PLOT_COLOR_SPEC = (0, 0, 1) -PEAK_VALS_COLOR_SPEC = (0, 1, 0) +FFT_PLOT_COLOR_SPEC = (0.3, 0.3, 1.0) +PEAK_VALS_COLOR_SPEC = (0.0, 0.8, 0.0) NO_PEAK_VALS = list() ################################################## @@ -57,31 +57,31 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, 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 = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) - self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) + control_box.Add(peak_hold_check_box, 0, wx.EXPAND) + average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( + avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) + control_box.Add(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 = wx.RadioButton(self, label="%d dB/div"%y_per_div) radio_button.Bind(wx.EVT_RADIOBUTTON, self._on_y_per_div) self.radio_buttons.append(radio_button) radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) @@ -91,18 +91,23 @@ class control_panel(wx.Panel): control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Ref Level'), 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) + _ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) + control_box.Add(_ref_lvl_buttons, 0, wx.ALIGN_CENTER) #autoscale control_box.AddStretchSpacer() - self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(self.autoscale_button, 0, wx.EXPAND) + autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(autoscale_button, 0, wx.EXPAND) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event + def on_mouse_wheel(event): + if event.GetWheelRotation() < 0: self._on_incr_ref_level(event) + else: self._on_decr_ref_level(event) + parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) ################################################## # Event handlers @@ -117,16 +122,14 @@ class control_panel(wx.Panel): index = self.radio_buttons.index(selected_radio_button) self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index] def _on_incr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY]) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY]) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY] ################################################## # FFT window with plotter and control panel ################################################## -class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class fft_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -150,47 +153,48 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0] #setup self.samples = list() - self.ext_controller = controller self.real = real self.fft_size = fft_size - self.sample_rate_key = sample_rate_key - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key self._reset_peak_vals() + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) + self.plotter.enable_legend(True) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #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) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) - self._register_set_prop(self, Y_PER_DIV_KEY, y_per_div) - self._register_set_prop(self, Y_DIVS_KEY, y_divs) - self._register_set_prop(self, X_DIVS_KEY, 8) #approximate - self._register_set_prop(self, REF_LEVEL_KEY, ref_level) - self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] + self[PEAK_HOLD_KEY] = peak_hold + self[Y_PER_DIV_KEY] = y_per_div + self[Y_DIVS_KEY] = y_divs + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[RUNNING_KEY] = True #register events - self.subscribe(PEAK_HOLD_KEY, self.plotter.enable_legend) - self.ext_controller.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) - self.ext_controller.subscribe(msg_key, self.handle_msg) - self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) + self.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(SAMPLE_RATE_KEY, self.update_grid) for key in ( BASEBAND_FREQ_KEY, Y_PER_DIV_KEY, X_DIVS_KEY, Y_DIVS_KEY, REF_LEVEL_KEY, ): self.subscribe(key, self.update_grid) #initial update - self.plotter.enable_legend(self[PEAK_HOLD_KEY]) self.update_grid() def autoscale(self, *args): @@ -207,9 +211,9 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): noise_floor -= abs(noise_floor)*.5 peak_level += abs(peak_level)*.1 #set the reference level to a multiple of y divs - self.set_ref_level(self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY])) + self[REF_LEVEL_KEY] = self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY]) #set the range to a clean number of the dynamic range - self.set_y_per_div(common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY])) + self[Y_PER_DIV_KEY] = common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY]) def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS @@ -234,19 +238,21 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self[PEAK_HOLD_KEY]: if len(self.peak_vals) != len(samples): self.peak_vals = samples self.peak_vals = numpy.maximum(samples, self.peak_vals) - else: self._reset_peak_vals() + #plot the peak hold + self.plotter.set_waveform( + channel='Peak', + samples=self.peak_vals, + color_spec=PEAK_VALS_COLOR_SPEC, + ) + else: + self._reset_peak_vals() + self.plotter.clear_waveform(channel='Peak') #plot the fft self.plotter.set_waveform( channel='FFT', samples=samples, color_spec=FFT_PLOT_COLOR_SPEC, ) - #plot the peak hold - self.plotter.set_waveform( - channel='Peak', - samples=self.peak_vals, - color_spec=PEAK_VALS_COLOR_SPEC, - ) #update the plotter self.plotter.update() @@ -259,7 +265,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): The y axis depends on y per div, y divs, and ref level. """ #grid parameters - sample_rate = self.ext_controller[self.sample_rate_key] + sample_rate = self[SAMPLE_RATE_KEY] baseband_freq = self[BASEBAND_FREQ_KEY] y_per_div = self[Y_PER_DIV_KEY] y_divs = self[Y_DIVS_KEY] @@ -269,24 +275,21 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self.real: x_width = sample_rate/2.0 else: x_width = sample_rate/1.0 x_per_div = common.get_clean_num(x_width/x_divs) - coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) #update the x grid if self.real: self.plotter.set_x_grid( baseband_freq, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) else: self.plotter.set_x_grid( baseband_freq - sample_rate/2.0, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) #update x units - self.plotter.set_x_label('Frequency', prefix+'Hz') + self.plotter.set_x_label('Frequency', 'Hz') #update y grid self.plotter.set_y_grid(ref_level-y_per_div*y_divs, ref_level, y_per_div) #update y units diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 0c6f38dc0..30ebd3fde 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # FFT sink block (wrapper for old wxgui) ################################################## -class _fft_sink_base(gr.hier_block2, common.prop_setter): +class _fft_sink_base(gr.hier_block2): """ An fft block with real/complex inputs and a gui window. """ @@ -85,9 +85,7 @@ class _fft_sink_base(gr.hier_block2, common.prop_setter): self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = fft_window.fft_window( parent=parent, @@ -106,12 +104,9 @@ class _fft_sink_base(gr.hier_block2, common.prop_setter): peak_hold=peak_hold, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - self._register_set_prop(self.controller, AVERAGE_KEY) - self._register_set_prop(self.controller, AVG_ALPHA_KEY) + common.register_access_methods(self, self.win) + setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS + setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS class fft_sink_f(_fft_sink_base): _fft_chain = blks2.logpwrfft_f diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index d455ddc27..a1033e818 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -319,7 +319,7 @@ class fft_window (wx.Panel): # self.SetBackgroundColour ('black') self.build_popup_menu() - self.set_baseband_freq(0.0) + self.set_baseband_freq(self.fftsink.baseband_freq) EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py new file mode 100644 index 000000000..dce52ff9b --- /dev/null +++ b/gr-wxgui/src/python/histo_window.py @@ -0,0 +1,154 @@ +# +# 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. +# + +################################################## +# Imports +################################################## +import plotter +import common +import wx +import numpy +import math +import pubsub +from constants import * +from gnuradio import gr #for gr.prefs + +################################################## +# Constants +################################################## +DEFAULT_WIN_SIZE = (600, 300) + +################################################## +# histo window control panel +################################################## +class control_panel(wx.Panel): + """ + A control panel with wx widgits to control the plotter and histo sink. + """ + + def __init__(self, parent): + """ + Create a new control panel. + @param parent the wx parent window + """ + self.parent = parent + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + control_box = wx.BoxSizer(wx.VERTICAL) + SIZE = (100, -1) + control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + control_box.AddStretchSpacer() + #num bins + def num_bins_cast(num): + num = int(num) + assert num > 1 + return num + num_bins_ctrl = common.TextBoxController(self, parent, NUM_BINS_KEY, cast=num_bins_cast) + control_box.Add(common.LabelBox(self, ' Num Bins ', num_bins_ctrl), 0, wx.EXPAND) + control_box.AddStretchSpacer() + #frame size + frame_size_ctrl = common.TextBoxController(self, parent, FRAME_SIZE_KEY, cast=num_bins_cast) + control_box.Add(common.LabelBox(self, ' Frame Size ', frame_size_ctrl), 0, wx.EXPAND) + control_box.AddStretchSpacer() + #run/stop + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + +################################################## +# histo window with plotter and control panel +################################################## +class histo_window(wx.Panel, pubsub.pubsub): + def __init__( + self, + parent, + controller, + size, + title, + maximum_key, + minimum_key, + num_bins_key, + frame_size_key, + msg_key, + ): + pubsub.pubsub.__init__(self) + #setup + self.samples = list() + #proxy the keys + self.proxy(MAXIMUM_KEY, controller, maximum_key) + self.proxy(MINIMUM_KEY, controller, minimum_key) + self.proxy(NUM_BINS_KEY, controller, num_bins_key) + self.proxy(FRAME_SIZE_KEY, controller, frame_size_key) + self.proxy(MSG_KEY, controller, msg_key) + #init panel and plot + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) + self.plotter = plotter.bar_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(False) + #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) + #initialize values + self[NUM_BINS_KEY] = self[NUM_BINS_KEY] + self[FRAME_SIZE_KEY] = self[FRAME_SIZE_KEY] + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 4 + #register events + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(Y_DIVS_KEY, self.update_grid) + + def handle_msg(self, msg): + """ + Handle the message from the fft sink message queue. + @param msg the frame as a character array + """ + if not self[RUNNING_KEY]: return + #convert to floating point numbers + self.samples = 100*numpy.fromstring(msg, numpy.float32)[:self[NUM_BINS_KEY]] #only take first frame + self.plotter.set_bars( + bars=self.samples, + bar_width=0.6, + color_spec=(0, 0, 1), + ) + self.update_grid() + + def update_grid(self): + if not len(self.samples): return + #calculate the maximum y value + y_off = math.ceil(numpy.max(self.samples)) + y_off = min(max(y_off, 1.0), 100.0) #between 1% and 100% + #update the x grid + self.plotter.set_x_grid( + self[MINIMUM_KEY], self[MAXIMUM_KEY], + common.get_clean_num((self[MAXIMUM_KEY] - self[MINIMUM_KEY])/self[X_DIVS_KEY]), + ) + self.plotter.set_x_label('Counts') + #update the y grid + self.plotter.set_y_grid(0, y_off, y_off/self[Y_DIVS_KEY]) + self.plotter.set_y_label('Frequency', '%') + self.plotter.update() diff --git a/gr-wxgui/src/python/histosink_gl.py b/gr-wxgui/src/python/histosink_gl.py new file mode 100644 index 000000000..db6606e41 --- /dev/null +++ b/gr-wxgui/src/python/histosink_gl.py @@ -0,0 +1,110 @@ +# +# 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. +# + +################################################## +# Imports +################################################## +import histo_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# histo sink block (wrapper for old wxgui) +################################################## +class histo_sink_f(gr.hier_block2): + """ + A histogram block and a gui window. + """ + + def __init__( + self, + parent, + size=histo_window.DEFAULT_WIN_SIZE, + title='', + num_bins=11, + frame_size=1000, + ): + #init + gr.hier_block2.__init__( + self, + "histo_sink", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0, 0, 0), + ) + #blocks + msgq = gr.msg_queue(2) + histo = gr.histo_sink_f(msgq) + histo.set_num_bins(num_bins) + histo.set_frame_size(frame_size) + #connect + self.connect(self, histo) + #controller + self.controller = pubsub() + self.controller.subscribe(NUM_BINS_KEY, histo.set_num_bins) + self.controller.publish(NUM_BINS_KEY, histo.get_num_bins) + self.controller.subscribe(FRAME_SIZE_KEY, histo.set_frame_size) + self.controller.publish(FRAME_SIZE_KEY, histo.get_frame_size) + #start input watcher + common.input_watcher(msgq, self.controller, MSG_KEY, arg1_key=MINIMUM_KEY, arg2_key=MAXIMUM_KEY) + #create window + self.win = histo_window.histo_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + maximum_key=MAXIMUM_KEY, + minimum_key=MINIMUM_KEY, + num_bins_key=NUM_BINS_KEY, + frame_size_key=FRAME_SIZE_KEY, + msg_key=MSG_KEY, + ) + common.register_access_methods(self, self.win) + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import stdgui2 + +class test_app_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + # build our flow graph + input_rate = 20.48e3 + + src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) + #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + thr2 = gr.throttle(gr.sizeof_float, input_rate) + sink2 = histo_sink_f (panel, title="Data", num_bins=31, frame_size=1000) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.connect(src2, thr2, sink2) + +def main (): + app = stdgui2.stdapp (test_app_block, "Histo Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index e766c68c1..f12a18248 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -53,23 +53,23 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, 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 = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + self.average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(self.average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) self.avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) + parent.subscribe(AVERAGE_KEY, self.avg_alpha_slider.Enable) control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) #run/stop control_box.AddStretchSpacer() @@ -81,7 +81,7 @@ class control_panel(wx.Panel): ################################################## # Numbersink window with label and gauges ################################################## -class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class number_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -98,20 +98,23 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): avg_alpha_key, peak_hold, msg_key, + sample_rate_key, ): pubsub.pubsub.__init__(self) - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) #setup self.peak_val_real = NEG_INF self.peak_val_imag = NEG_INF - self.ext_controller = controller self.real = real self.units = units self.minval = minval self.maxval = maxval self.decimal_places = decimal_places - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #setup the box with display and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -128,13 +131,13 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): sizer.Add(self.gauge_real, 1, wx.EXPAND) sizer.Add(self.gauge_imag, 1, wx.EXPAND) self.SetSizerAndFit(main_box) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[RUNNING_KEY] = True + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) + self.subscribe(MSG_KEY, self.handle_msg) self.Bind(common.EVT_DATA, self.update) def show_gauges(self, show_gauge): diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 9e52745d8..5fa9e3aef 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Number sink block (wrapper for old wxgui) ################################################## -class _number_sink_base(gr.hier_block2, common.prop_setter): +class _number_sink_base(gr.hier_block2): """ An decimator block with a number window display """ @@ -74,29 +74,28 @@ class _number_sink_base(gr.hier_block2, common.prop_setter): if self._real: mult = gr.multiply_const_ff(factor) add = gr.add_const_ff(ref_level) - self._avg = gr.single_pole_iir_filter_ff(1.0) + avg = gr.single_pole_iir_filter_ff(1.0) else: mult = gr.multiply_const_cc(factor) add = gr.add_const_cc(ref_level) - self._avg = gr.single_pole_iir_filter_cc(1.0) + avg = gr.single_pole_iir_filter_cc(1.0) msgq = gr.msg_queue(2) sink = gr.message_sink(self._item_size, msgq, True) #connect - self.connect(self, sd, mult, add, self._avg, sink) - #setup averaging - self._avg_alpha = avg_alpha - self.set_average(average) - self.set_avg_alpha(avg_alpha) + self.connect(self, sd, mult, add, avg, sink) #controller self.controller = pubsub() self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) - self.controller.subscribe(AVERAGE_KEY, self.set_average) - self.controller.publish(AVERAGE_KEY, self.get_average) - self.controller.subscribe(AVG_ALPHA_KEY, self.set_avg_alpha) - self.controller.publish(AVG_ALPHA_KEY, self.get_avg_alpha) + self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) + def update_avg(*args): + if self.controller[AVERAGE_KEY]: avg.set_taps(self.controller[AVG_ALPHA_KEY]) + else: avg.set_taps(1.0) + self.controller.subscribe(AVERAGE_KEY, update_avg) + self.controller.subscribe(AVG_ALPHA_KEY, update_avg) + self.controller[AVERAGE_KEY] = average + self.controller[AVG_ALPHA_KEY] = avg_alpha #start input watcher - def set_msg(msg): self.controller[MSG_KEY] = msg - common.input_watcher(msgq, set_msg) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = number_window.number_window( parent=parent, @@ -113,25 +112,12 @@ class _number_sink_base(gr.hier_block2, common.prop_setter): avg_alpha_key=AVG_ALPHA_KEY, peak_hold=peak_hold, msg_key=MSG_KEY, + sample_rate_key=SAMPLE_RATE_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + common.register_access_methods(self, self.controller) #backwards compadibility self.set_show_gauge = self.win.show_gauges - def get_average(self): return self._average - def set_average(self, average): - self._average = average - if self.get_average(): self._avg.set_taps(self.get_avg_alpha()) - else: self._avg.set_taps(1.0) - - def get_avg_alpha(self): return self._avg_alpha - def set_avg_alpha(self, avg_alpha): - self._avg_alpha = avg_alpha - self.set_average(self.get_average()) - class number_sink_f(_number_sink_base): _item_size = gr.sizeof_float _real = True diff --git a/gr-wxgui/src/python/plotter/Makefile.am b/gr-wxgui/src/python/plotter/Makefile.am index ada506794..d00f0a425 100644 --- a/gr-wxgui/src/python/plotter/Makefile.am +++ b/gr-wxgui/src/python/plotter/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004,2005,2008 Free Software Foundation, Inc. +# Copyright 2004,2005,2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -30,8 +30,11 @@ ourlibdir = $(grpyexecdir)/wxgui/plotter ourpython_PYTHON = \ __init__.py \ + bar_plotter.py \ channel_plotter.py \ + common.py \ gltext.py \ + grid_plotter_base.py \ plotter_base.py \ waterfall_plotter.py diff --git a/gr-wxgui/src/python/plotter/__init__.py b/gr-wxgui/src/python/plotter/__init__.py index 12f8b3450..616492a3e 100644 --- a/gr-wxgui/src/python/plotter/__init__.py +++ b/gr-wxgui/src/python/plotter/__init__.py @@ -21,3 +21,4 @@ from channel_plotter import channel_plotter from waterfall_plotter import waterfall_plotter +from bar_plotter import bar_plotter diff --git a/gr-wxgui/src/python/plotter/bar_plotter.py b/gr-wxgui/src/python/plotter/bar_plotter.py new file mode 100644 index 000000000..3f9259e9d --- /dev/null +++ b/gr-wxgui/src/python/plotter/bar_plotter.py @@ -0,0 +1,144 @@ +# +# 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. + @param x_val the current x value + @param y_val the current y value + @return 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. + @param bars a list of bars + @param bar_width the fractional width of the bar, between 0 and 1 + @param 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/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index 8fa28410b..ff0a3a160 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -20,20 +20,21 @@ # import wx -from plotter_base import grid_plotter_base -from OpenGL.GL import * -from gnuradio.wxgui import common +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 -PADDING = 35, 15, 40, 60 #top, right, bottom, left +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 @@ -45,16 +46,21 @@ class channel_plotter(grid_plotter_base): Create a new channel plotter. """ #init - grid_plotter_base.__init__(self, parent, PADDING) - self._channels = dict() + grid_plotter_base.__init__(self, parent, MIN_PADDING) + #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) - def _gl_init(self): + def _init_channel_plotter(self): """ Run gl initialization tasks. """ - glEnableClientState(GL_VERTEX_ARRAY) - self._grid_compiled_list_id = glGenLists(1) + GL.glEnableClientState(GL.GL_VERTEX_ARRAY) def enable_legend(self, enable=None): """ @@ -65,73 +71,55 @@ class channel_plotter(grid_plotter_base): if enable is None: return self._enable_legend self.lock() self._enable_legend = enable - self.changed(True) + self._legend_cache.changed(True) self.unlock() - def draw(self): + def _draw_waveforms(self): """ - Draw the grid and waveforms. + Draw the waveforms for each channel. + Scale the waveform data to the grid using gl matrix operations. """ - self.lock() - self.clear() - #store the grid drawing operations - if self.changed(): - glNewList(self._grid_compiled_list_id, GL_COMPILE) - self._draw_grid() - self._draw_legend() - glEndList() - self.changed(False) - #draw the grid - glCallList(self._grid_compiled_list_id) #use scissor to prevent drawing outside grid - glEnable(GL_SCISSOR_TEST) - glScissor( + 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, ) - #draw the waveforms - self._draw_waveforms() - glDisable(GL_SCISSOR_TEST) - self._draw_point_label() - #swap buffer into display - self.SwapBuffers() - self.unlock() - - def _draw_waveforms(self): - """ - Draw the waveforms for each channel. - Scale the waveform data to the grid using gl matrix operations. - """ 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 - glPushMatrix() - glTranslatef(self.padding_left, self.padding_top, 0) - glScalef( + 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, ) - glTranslatef(0, 1, 0) + 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), 0 + x_scale, x_trans = 1.0/(num_samps-1), -self._channels[channel][TRIG_OFF_KEY] 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) + 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 - glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) + GL.glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) marker = self._channels[channel][MARKERY_KEY] - if marker: glPointSize(marker) - glVertexPointerf(points) - glDrawArrays(marker is None and GL_LINE_STRIP or GL_POINTS, 0, len(points)) - glPopMatrix() + 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): """ @@ -143,12 +131,9 @@ class channel_plotter(grid_plotter_base): @return a string with newlines """ #create text - label_str = '%s: %s %s\n%s: %s %s'%( - self.x_label, - common.label_format(x_val), - self.x_units, self.y_label, - common.label_format(y_val), - self.y_units, + 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] @@ -156,11 +141,12 @@ class channel_plotter(grid_plotter_base): if not num_samps: continue if isinstance(samples, tuple): continue #linear interpolation - x_index = (num_samps-1)*(x_val/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + 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)) - y_value = (samples[x_index_high] - samples[x_index_low])*(x_index - x_index_low) + samples[x_index_low] - label_str += '\n%s: %s %s'%(channel, common.label_format(y_value), self.y_units) + 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] + label_str += '\n%s: %s'%(channel, common.eng_format(y_value, self.y_units)) return label_str def _draw_legend(self): @@ -178,7 +164,7 @@ class channel_plotter(grid_plotter_base): txt = gltext.Text(channel, font_size=LEGEND_TEXT_FONT_SIZE) w, h = txt.get_size() #draw rect + text - glColor3f(*color_spec) + GL.glColor3f(*color_spec) self._draw_rect( x_off - w - LEGEND_BOX_PADDING, self.padding_top/2 - h/2 - LEGEND_BOX_PADDING, @@ -188,21 +174,36 @@ class channel_plotter(grid_plotter_base): txt.draw_text(wx.Point(x_off - w, self.padding_top/2 - h/2)) x_off -= w + 4*LEGEND_BOX_PADDING - def set_waveform(self, channel, samples, color_spec, marker=None): + def clear_waveform(self, channel): + """ + Remove a waveform from the list of waveforms. + @param 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. @param channel the channel key @param samples the waveform samples @param color_spec the 3-tuple for line color @param marker None for line + @param trig_off fraction of sample for trigger offset """ self.lock() - if channel not in self._channels.keys(): self.changed(True) + 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__': diff --git a/gr-wxgui/src/python/plotter/common.py b/gr-wxgui/src/python/plotter/common.py new file mode 100644 index 000000000..7699986aa --- /dev/null +++ b/gr-wxgui/src/python/plotter/common.py @@ -0,0 +1,131 @@ +# +# 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. + @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_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. + @param num the floating point number + @return 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. + @param num the number to format + @return 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. + @param num the number to format + @param units the units to append + @return 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)) + #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/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py new file mode 100644 index 000000000..fd318ffa0 --- /dev/null +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -0,0 +1,370 @@ +# +# 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, .5) +POINT_LABEL_PADDING = 3 +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.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. + @param 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 enable_point_label(self, enable=None): + """ + Enable/disable the point label. + @param enable true to enable + @return 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. + @param 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. + @param x_label the x label string + @param 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. + @param y_label the y label string + @param 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. + @param minimum the left-most value + @param maximum the right-most value + @param step the grid spacing + @param 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. + @param minimum the bottom-most value + @param maximum the top-most value + @param step the grid spacing + @param 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) + ################################################## + # 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. + @param tick the floating point tick value + @param unit the axis unit + @return 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. + @param min the lower bound + @param max the upper bound + @param step the grid spacing + @param scalar the grid scaling + @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 + 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. + @param enable true to enable + @return 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. + @param corr1 a tuple of x, y + @param 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. + @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 + @param 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() + #draw rect + text + GL.glColor3f(*POINT_LABEL_COLOR_SPEC) + if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + 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/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 6d5349a5f..662365a37 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,27 +21,59 @@ import wx import wx.glcanvas -from OpenGL.GL import * -from gnuradio.wxgui import common -import threading -import gltext -import math -import time +from OpenGL import GL +import common 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 -TICK_LABEL_PADDING = 5 -POINT_LABEL_FONT_SIZE = 8 -POINT_LABEL_COLOR_SPEC = (1, 1, .5) -POINT_LABEL_PADDING = 3 + +################################################## +# 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. + @param 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): +class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ Plotter base class for all plot types. """ @@ -54,340 +86,86 @@ class _plotter_base(wx.glcanvas.GLCanvas): Bind the paint and size events. @param parent the parent widgit """ - self._global_lock = threading.Lock() attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) - self.changed(False) self._gl_init_flag = False self._resized_flag = True - self._update_ts = 0 + 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 lock(self): self._global_lock.acquire() - def unlock(self): self._global_lock.release() + def new_gl_cache(self, draw_fcn, draw_pri=50): + """ + Create a new gl cache. + Register its draw and init function. + @return 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.unlock() def _on_paint(self, event): """ - Respond to paint events, call update. + 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. """ + self.lock() self.SetCurrent() #check if gl was initialized if not self._gl_init_flag: - glClearColor(*BACKGROUND_COLOR_SPEC) - self._gl_init() + 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.lock() self.width, self.height = self.GetSize() - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glOrtho(0, self.width, self.height, 0, 1, 0) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - glViewport(0, 0, self.width, self.height) + 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 - self.changed(True) - self.unlock() - self.draw() + #clear, draw functions, swap + GL.glClear(GL.GL_COLOR_BUFFER_BIT) + for fcn in self._draw_fcns: fcn[1]() + self.SwapBuffers() + self.unlock() def update(self): """ Force a paint event. - Record the timestamp. """ wx.PostEvent(self, wx.PaintEvent()) - self._update_ts = time.time() - - def clear(self): glClear(GL_COLOR_BUFFER_BIT) - - def changed(self, state=None): - """ - Set the changed flag if state is not None. - Otherwise return the changed flag. - """ - if state is not None: self._changed = state - else: return self._changed - -################################################## -# Grid Plotter Base Class -################################################## -class grid_plotter_base(_plotter_base): - - def __init__(self, parent, padding): - _plotter_base.__init__(self, parent) - self.padding_top, self.padding_right, self.padding_bottom, self.padding_left = 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 - self.enable_point_label(False) - self._mouse_coordinate = None - self.Bind(wx.EVT_MOTION, self._on_motion) - self.Bind(wx.EVT_LEAVE_WINDOW, self._on_leave_window) - - def _on_motion(self, event): - """ - Mouse motion, record the position X, Y. - """ - self.lock() - self._mouse_coordinate = event.GetPosition() - #update based on last known update time - if time.time() - self._update_ts > 0.03: self.update() - self.unlock() - - def _on_leave_window(self, event): - """ - Mouse leave window, set the position to None. - """ - self.lock() - self._mouse_coordinate = None - self.update() - self.unlock() - - def enable_point_label(self, enable=None): - """ - Enable/disable the point label. - @param enable true to enable - @return the enable state when None - """ - if enable is None: return self._enable_point_label - self.lock() - self._enable_point_label = enable - self.changed(True) - self.unlock() - - def set_title(self, title): - """ - Set the title. - @param title the title string - """ - self.lock() - self.title = title - self.changed(True) - self.unlock() - - def set_x_label(self, x_label, x_units=''): - """ - Set the x label and units. - @param x_label the x label string - @param x_units the x units string - """ - self.lock() - self.x_label = x_label - self.x_units = x_units - self.changed(True) - self.unlock() - - def set_y_label(self, y_label, y_units=''): - """ - Set the y label and units. - @param y_label the y label string - @param y_units the y units string - """ - self.lock() - self.y_label = y_label - self.y_units = y_units - self.changed(True) - self.unlock() - - def set_x_grid(self, x_min, x_max, x_step, x_scalar=1.0): - """ - 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 - @param x_scalar the scalar factor - """ - self.lock() - self.x_min = float(x_min) - self.x_max = float(x_max) - self.x_step = float(x_step) - self.x_scalar = float(x_scalar) - self.changed(True) - self.unlock() - - def set_y_grid(self, y_min, y_max, y_step, y_scalar=1.0): - """ - 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 - @param y_scalar the scalar factor - """ - self.lock() - self.y_min = float(y_min) - self.y_max = float(y_max) - self.y_step = float(y_step) - self.y_scalar = float(y_scalar) - self.changed(True) - self.unlock() - - def _draw_grid(self): - """ - Draw the border, grid, title, and units. - """ - ################################################## - # Draw Border - ################################################## - glColor3f(*GRID_LINE_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 Grid X - ################################################## - for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar): - 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 - glColor3f(*GRID_LINE_COLOR_SPEC) - self._draw_line( - (scaled_tick, self.padding_top, 0), - (scaled_tick, self.height-self.padding_bottom, 0), - ) - txt = self._get_tick_label(tick) - w, h = txt.get_size() - txt.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) - ################################################## - # Draw Grid Y - ################################################## - for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar): - 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 - glColor3f(*GRID_LINE_COLOR_SPEC) - self._draw_line( - (self.padding_left, scaled_tick, 0), - (self.width-self.padding_right, scaled_tick, 0), - ) - txt = self._get_tick_label(tick) - w, h = txt.get_size() - txt.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) - ################################################## - # Draw Title - ################################################## - #draw x units - txt = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) - txt.draw_text(wx.Point(self.width/2.0, .5*self.padding_top)) - ################################################## - # Draw Labels - ################################################## - #draw x labels - x_label_str = self.x_units and "%s (%s)"%(self.x_label, self.x_units) or self.x_label - txt = gltext.Text(x_label_str, bold=True, 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 labels - y_label_str = self.y_units and "%s (%s)"%(self.y_label, self.y_units) or self.y_label - txt = gltext.Text(y_label_str, bold=True, 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, - ) - - def _get_tick_label(self, tick): - """ - Format the tick value and create a gl text. - @param tick the floating point tick value - @return the tick label text - """ - tick_str = common.label_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. - @param min the lower bound - @param max the upper bound - @param step the grid spacing - @param scalar the grid scaling - @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*scalar 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, fill=True): - """ - 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 - @param fill true to color inside of rectangle - """ - glBegin(fill and GL_QUADS or GL_LINE_LOOP) - glVertex2f(x, y) - glVertex2f(x+width, y) - glVertex2f(x+width, y+height) - glVertex2f(x, y+height) - 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._mouse_coordinate: return - x, y = self._mouse_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 = self.x_scalar*(x_win_scalar*(self.x_max-self.x_min) + self.x_min) - y_val = self.y_scalar*(y_win_scalar*(self.y_max-self.y_min) + self.y_min) - #create text - label_str = self._populate_point_label(x_val, y_val) - txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) - w, h = txt.get_size() - #draw rect + text - glColor3f(*POINT_LABEL_COLOR_SPEC) - if x > self.width/2: x -= w+2*POINT_LABEL_PADDING - 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/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 4dc19f672..2e0669961 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -20,9 +20,9 @@ # import wx -from plotter_base import grid_plotter_base -from OpenGL.GL import * -from gnuradio.wxgui import common +from grid_plotter_base import grid_plotter_base +from OpenGL import GL +import common import numpy import gltext import math @@ -33,7 +33,7 @@ LEGEND_NUM_LABELS = 9 LEGEND_WIDTH = 8 LEGEND_FONT_SIZE = 8 LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black -PADDING = 35, 60, 40, 60 #top, right, bottom, left +MIN_PADDING = 0, 60, 0, 0 #top, right, bottom, left ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2))) @@ -91,7 +91,13 @@ class waterfall_plotter(grid_plotter_base): Create a new channel plotter. """ #init - grid_plotter_base.__init__(self, parent, PADDING) + 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 @@ -102,35 +108,11 @@ class waterfall_plotter(grid_plotter_base): self.set_num_lines(0) self.set_color_mode(COLORS.keys()[0]) - def _gl_init(self): + def _init_waterfall(self): """ Run gl initialization tasks. """ - self._grid_compiled_list_id = glGenLists(1) - self._waterfall_texture = glGenTextures(1) - - def draw(self): - """ - Draw the grid and waveforms. - """ - self.lock() - #resize texture - self._resize_texture() - #store the grid drawing operations - if self.changed(): - glNewList(self._grid_compiled_list_id, GL_COMPILE) - self._draw_grid() - self._draw_legend() - glEndList() - self.changed(False) - self.clear() - #draw the grid - glCallList(self._grid_compiled_list_id) - self._draw_waterfall() - self._draw_point_label() - #swap buffer into display - self.SwapBuffers() - self.unlock() + self._waterfall_texture = GL.glGenTextures(1) def _draw_waterfall(self): """ @@ -138,42 +120,44 @@ class waterfall_plotter(grid_plotter_base): 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 - glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) + 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: - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL_RGBA, GL_UNSIGNED_BYTE, self._buffer.pop(0)) + 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 - glEnable(GL_TEXTURE_2D) - glPushMatrix() + GL.glEnable(GL.GL_TEXTURE_2D) + GL.glPushMatrix() #matrix scaling - glTranslatef(self.padding_left, self.padding_top, 0) - glScalef( + 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 - glBegin(GL_QUADS) + 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) - glTexCoord2f(0, prop_y+1-off) - glVertex2f(0, 1) - glTexCoord2f(prop_x, prop_y+1-off) - glVertex2f(1, 1) - glTexCoord2f(prop_x, prop_y) - glVertex2f(1, 0) - glTexCoord2f(0, prop_y) - glVertex2f(0, 0) - glEnd() - glPopMatrix() - glDisable(GL_TEXTURE_2D) + 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): """ @@ -183,7 +167,7 @@ class waterfall_plotter(grid_plotter_base): @param y_val the current y value @return a value string with units """ - return '%s: %s %s'%(self.x_label, common.label_format(x_val), self.x_units) + return '%s: %s'%(self.x_label, common.eng_format(x_val, self.x_units)) def _draw_legend(self): """ @@ -196,11 +180,11 @@ class waterfall_plotter(grid_plotter_base): x = self.width - self.padding_right + LEGEND_LEFT_PAD for i in range(LEGEND_NUM_BLOCKS): color = COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))] - glColor4f(*map(lambda c: ord(c)/255.0, color)) + GL.glColor4f(*map(lambda c: ord(c)/255.0, color)) y = self.height - (i+1)*block_height - self.padding_bottom self._draw_rect(x, y, LEGEND_WIDTH, block_height) #draw rectangle around color scale border - glColor3f(*LEGEND_BORDER_COLOR_SPEC) + 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) @@ -224,9 +208,9 @@ class waterfall_plotter(grid_plotter_base): self._buffer = list() self._pointer = 0 if self._num_lines and self._fft_size: - glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) + GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture) data = numpy.zeros(self._num_lines*self._fft_size*4, numpy.uint8).tostring() - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) + 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): @@ -239,7 +223,7 @@ class waterfall_plotter(grid_plotter_base): self.lock() if color_mode in COLORS.keys(): self._color_mode = color_mode - self.changed(True) + self._legend_cache.changed(True) self.update() self.unlock() @@ -268,7 +252,7 @@ class waterfall_plotter(grid_plotter_base): if self._minimum != minimum or self._maximum != maximum: self._minimum = minimum self._maximum = maximum - self.changed(True) + self._legend_cache.changed(True) if self._fft_size != len(samples): self._fft_size = len(samples) self._resize_texture(True) @@ -279,4 +263,5 @@ class waterfall_plotter(grid_plotter_base): #convert the samples to RGBA data data = numpy.choose(samples, COLORS[self._color_mode]).tostring() self._buffer.append(data) + self._waterfall_cache.changed(True) self.unlock() diff --git a/gr-wxgui/src/python/pubsub.py b/gr-wxgui/src/python/pubsub.py index cc8ea5ccc..e55d69197 100644 --- a/gr-wxgui/src/python/pubsub.py +++ b/gr-wxgui/src/python/pubsub.py @@ -28,73 +28,73 @@ This is a proof of concept implementation, will likely change significantly. class pubsub(dict): def __init__(self): - self._publishers = { } - self._subscribers = { } - self._proxies = { } - + self._publishers = { } + self._subscribers = { } + self._proxies = { } + def __missing__(self, key, value=None): - dict.__setitem__(self, key, value) - self._publishers[key] = None - self._subscribers[key] = [] - self._proxies[key] = None - + dict.__setitem__(self, key, value) + self._publishers[key] = None + self._subscribers[key] = [] + self._proxies[key] = None + def __setitem__(self, key, val): - if not self.has_key(key): - self.__missing__(key, val) - elif self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p[newkey] = val - else: - dict.__setitem__(self, key, val) - for sub in self._subscribers[key]: - # Note this means subscribers will get called in the thread - # context of the 'set' caller. - sub(val) + if not self.has_key(key): + self.__missing__(key, val) + elif self._proxies[key] is not None: + (p, pkey) = self._proxies[key] + p[pkey] = val + else: + dict.__setitem__(self, key, val) + for sub in self._subscribers[key]: + # Note this means subscribers will get called in the thread + # context of the 'set' caller. + sub(val) def __getitem__(self, key): - if not self.has_key(key): self.__missing__(key) - if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - return p[newkey] - elif self._publishers[key] is not None: - return self._publishers[key]() - else: - return dict.__getitem__(self, key) + if not self.has_key(key): self.__missing__(key) + if self._proxies[key] is not None: + (p, pkey) = self._proxies[key] + return p[pkey] + elif self._publishers[key] is not None: + return self._publishers[key]() + else: + return dict.__getitem__(self, key) def publish(self, key, publisher): - if not self.has_key(key): self.__missing__(key) + if not self.has_key(key): self.__missing__(key) if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.publish(newkey, publisher) + (p, pkey) = self._proxies[key] + p.publish(pkey, publisher) else: self._publishers[key] = publisher - + def subscribe(self, key, subscriber): - if not self.has_key(key): self.__missing__(key) + if not self.has_key(key): self.__missing__(key) if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.subscribe(newkey, subscriber) + (p, pkey) = self._proxies[key] + p.subscribe(pkey, subscriber) else: self._subscribers[key].append(subscriber) - + def unpublish(self, key): if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.unpublish(newkey) + (p, pkey) = self._proxies[key] + p.unpublish(pkey) else: self._publishers[key] = None - + def unsubscribe(self, key, subscriber): if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.unsubscribe(newkey, subscriber) + (p, pkey) = self._proxies[key] + p.unsubscribe(pkey, subscriber) else: self._subscribers[key].remove(subscriber) - def proxy(self, key, p, newkey=None): - if not self.has_key(key): self.__missing__(key) - if newkey is None: newkey = key - self._proxies[key] = (p, newkey) + def proxy(self, key, p, pkey=None): + if not self.has_key(key): self.__missing__(key) + if pkey is None: pkey = key + self._proxies[key] = (p, pkey) def unproxy(self, key): self._proxies[key] = None @@ -110,22 +110,22 @@ if __name__ == "__main__": # Add some subscribers # First is a bare function def print_len(x): - print "len=%i" % (len(x), ) + print "len=%i" % (len(x), ) o.subscribe('foo', print_len) # The second is a class member function class subber(object): - def __init__(self, param): - self._param = param - def printer(self, x): - print self._param, `x` + def __init__(self, param): + self._param = param + def printer(self, x): + print self._param, `x` s = subber('param') o.subscribe('foo', s.printer) # The third is a lambda function o.subscribe('foo', lambda x: sys.stdout.write('val='+`x`+'\n')) - # Update key 'foo', will notify subscribers + # Update key 'foo', will notify subscribers print "Updating 'foo' with three subscribers:" o['foo'] = 'bar'; @@ -135,7 +135,7 @@ if __name__ == "__main__": # Update now will only trigger second and third subscriber print "Updating 'foo' after removing a subscriber:" o['foo'] = 'bar2'; - + # Publish a key as a function, in this case, a lambda function o.publish('baz', lambda : 42) print "Published value of 'baz':", o['baz'] @@ -145,7 +145,7 @@ if __name__ == "__main__": # This will return None, as there is no publisher print "Value of 'baz' with no publisher:", o['baz'] - + # Set 'baz' key, it gets cached o['baz'] = 'bazzz' diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 7d4f97113..bbc66426a 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -29,41 +29,40 @@ import numpy import time import pubsub from constants import * -from gnuradio import gr #for gr.prefs +from gnuradio import gr #for gr.prefs, trigger modes ################################################## # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) DEFAULT_WIN_SIZE = (600, 300) -DEFAULT_V_SCALE = 1000 +COUPLING_MODES = ( + ('DC', False), + ('AC', True), +) TRIGGER_MODES = ( - ('Off', 0), - ('Neg', -1), - ('Pos', +1), + ('Freerun', gr.gr_TRIG_MODE_FREE), + ('Automatic', gr.gr_TRIG_MODE_AUTO), + ('Normal', gr.gr_TRIG_MODE_NORM), ) -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), +TRIGGER_SLOPES = ( + ('Positive +', gr.gr_TRIG_SLOPE_POS), + ('Negative -', gr.gr_TRIG_SLOPE_NEG), ) CHANNEL_COLOR_SPECS = ( - (0, 0, 1), - (0, 1, 0), - (1, 0, 0), - (1, 0, 1), + (0.3, 0.3, 1.0), + (0.0, 0.8, 0.0), + (1.0, 0.0, 0.0), + (0.8, 0.0, 0.8), ) +TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0) AUTORANGE_UPDATE_RATE = 0.5 #sec MARKER_TYPES = ( - ('Dot Small', 1.0), - ('Dot Medium', 2.0), - ('Dot Large', 3.0), ('Line Link', None), + ('Dot Large', 3.0), + ('Dot Med', 2.0), + ('Dot Small', 1.0), + ('None', 0.0), ) DEFAULT_MARKER_TYPE = None @@ -79,175 +78,213 @@ class control_panel(wx.Panel): Create a new control panel. @param parent the wx parent window """ + SIZE = (100, -1) self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - self.control_box = 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 - self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY) - control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND) - #trigger level - self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0)) - control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND) - #trigger channel - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0)) - control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND) - #axes options - SPACING = 15 + control_box = wx.BoxSizer(wx.VERTICAL) + ################################################## + # Axes Options + ################################################## control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) ################################################## # Scope Mode Box ################################################## - self.scope_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(self.scope_mode_box, 0, wx.EXPAND) + scope_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(scope_mode_box, 0, wx.EXPAND) #x axis divs - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL) - x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) - hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + x_buttons_scope = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) + scope_mode_box.Add(common.LabelBox(self, 'Secs/Div', x_buttons_scope), 0, wx.EXPAND) #y axis divs - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL) - y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) - hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + y_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) + parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons_scope.Enable(not x)) + scope_mode_box.Add(common.LabelBox(self, 'Counts/Div', y_buttons_scope), 0, wx.EXPAND) #y axis ref lvl - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL) - y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) - hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + y_off_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) + parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons_scope.Enable(not x)) + scope_mode_box.Add(common.LabelBox(self, 'Y Offset', y_off_buttons_scope), 0, wx.EXPAND) + #t axis ref lvl + scope_mode_box.AddSpacer(5) + t_off_slider = wx.Slider(self, size=SIZE, style=wx.SL_HORIZONTAL) + t_off_slider.SetRange(0, 1000) + def t_off_slider_changed(evt): parent[T_FRAC_OFF_KEY] = float(t_off_slider.GetValue())/t_off_slider.GetMax() + t_off_slider.Bind(wx.EVT_SLIDER, t_off_slider_changed) + parent.subscribe(T_FRAC_OFF_KEY, lambda x: t_off_slider.SetValue(int(round(x*t_off_slider.GetMax())))) + scope_mode_box.Add(common.LabelBox(self, 'T Offset', t_off_slider), 0, wx.EXPAND) + scope_mode_box.AddSpacer(5) ################################################## # XY Mode Box ################################################## - self.xy_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(self.xy_mode_box, 0, wx.EXPAND) - #x and y channel - CHOOSER_WIDTH = 60 - CENTER_SPACING = 10 - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) - hbox.Add(self.channel_x_chooser, 0, wx.EXPAND) - hbox.AddSpacer(CENTER_SPACING) - self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) - hbox.Add(self.channel_y_chooser, 0, wx.EXPAND) - #div controls - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + xy_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(xy_mode_box, 0, wx.EXPAND) + #x div controls x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs) - parent.subscribe(AUTORANGE_KEY, x_buttons.Disable) - hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(CENTER_SPACING) - hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: x_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'X/Div', x_buttons), 0, wx.EXPAND) + #y div controls y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) - hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - #offset controls - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'Y/Div', y_buttons), 0, wx.EXPAND) + #x offset controls x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off) - parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable) - hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(CENTER_SPACING) - hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: x_off_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'X Off', x_off_buttons), 0, wx.EXPAND) + #y offset controls y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) - hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - ################################################## - # End Special Boxes - ################################################## - #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 = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY) - control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT) + parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'Y Off', y_off_buttons), 0, wx.EXPAND) + xy_mode_box.ShowItems(False) #autorange check box self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) - #marker control_box.AddStretchSpacer() - self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(self.marker_chooser, 0, wx.EXPAND) - #xy mode - control_box.AddStretchSpacer() - self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode') - parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode) - control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND) + ################################################## + # Channel Options + ################################################## + TRIGGER_PAGE_INDEX = parent.num_inputs + XY_PAGE_INDEX = parent.num_inputs+1 + control_box.Add(common.LabelText(self, 'Channel Options'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + options_notebook = wx.Notebook(self) + control_box.Add(options_notebook, 0, wx.EXPAND) + def options_notebook_changed(evt): + try: + parent[TRIGGER_SHOW_KEY] = options_notebook.GetSelection() == TRIGGER_PAGE_INDEX + parent[XY_MODE_KEY] = options_notebook.GetSelection() == XY_PAGE_INDEX + except wx.PyDeadObjectError: pass + options_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, options_notebook_changed) + def xy_mode_changed(mode): + #ensure xy tab is selected + if mode and options_notebook.GetSelection() != XY_PAGE_INDEX: + options_notebook.SetSelection(XY_PAGE_INDEX) + #ensure xy tab is not selected + elif not mode and options_notebook.GetSelection() == XY_PAGE_INDEX: + options_notebook.SetSelection(0) + #show/hide control buttons + scope_mode_box.ShowItems(not mode) + xy_mode_box.ShowItems(mode) + control_box.Layout() + parent.subscribe(XY_MODE_KEY, xy_mode_changed) + ################################################## + # Channel Menu Boxes + ################################################## + for i in range(parent.num_inputs): + channel_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(channel_menu_panel, 'Ch%d'%(i+1)) + channel_menu_box = wx.BoxSizer(wx.VERTICAL) + channel_menu_panel.SetSizer(channel_menu_box) + #ac couple check box + channel_menu_box.AddStretchSpacer() + coupling_chooser = common.DropDownController(channel_menu_panel, COUPLING_MODES, parent, common.index_key(AC_COUPLE_KEY, i), SIZE) + channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Coupling', coupling_chooser), 0, wx.EXPAND) + #marker + channel_menu_box.AddStretchSpacer() + marker_chooser = common.DropDownController(channel_menu_panel, MARKER_TYPES, parent, common.index_key(MARKER_KEY, i), SIZE) + channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + channel_menu_box.AddStretchSpacer() + ################################################## + # Trigger Menu Box + ################################################## + trigger_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(trigger_menu_panel, 'Trig') + trigger_menu_box = wx.BoxSizer(wx.VERTICAL) + trigger_menu_panel.SetSizer(trigger_menu_box) + #trigger mode + trigger_mode_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_MODES, parent, TRIGGER_MODE_KEY, SIZE) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Mode', trigger_mode_chooser), 0, wx.EXPAND) + #trigger slope + trigger_slope_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_SLOPES, parent, TRIGGER_SLOPE_KEY, SIZE) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_slope_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Slope', trigger_slope_chooser), 0, wx.EXPAND) + #trigger channel + choices = [('Channel %d'%(i+1), i) for i in range(parent.num_inputs)] + trigger_channel_chooser = common.DropDownController(trigger_menu_panel, choices, parent, TRIGGER_CHANNEL_KEY, SIZE) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_channel_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Channel', trigger_channel_chooser), 0, wx.EXPAND) + #trigger level + hbox = wx.BoxSizer(wx.HORIZONTAL) + trigger_menu_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(trigger_menu_panel, label=' Level '), 1, wx.ALIGN_CENTER_VERTICAL) + trigger_level_button = wx.Button(trigger_menu_panel, label='50%', style=wx.BU_EXACTFIT) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_button.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_level_button.Bind(wx.EVT_BUTTON, self.parent.set_auto_trigger_level) + hbox.Add(trigger_level_button, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(10) + trigger_level_buttons = common.IncrDecrButtons(trigger_menu_panel, self._on_incr_trigger_level, self._on_decr_trigger_level) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_buttons.Enable(x!=gr.gr_TRIG_MODE_FREE)) + hbox.Add(trigger_level_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + ################################################## + # XY Menu Box + ################################################## + if parent.num_inputs > 1: + xy_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(xy_menu_panel, 'XY') + xy_menu_box = wx.BoxSizer(wx.VERTICAL) + xy_menu_panel.SetSizer(xy_menu_box) + #x and y channel choosers + xy_menu_box.AddStretchSpacer() + choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] + x_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, X_CHANNEL_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch X', x_channel_chooser), 0, wx.EXPAND) + xy_menu_box.AddStretchSpacer() + y_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, Y_CHANNEL_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch Y', y_channel_chooser), 0, wx.EXPAND) + #marker + xy_menu_box.AddStretchSpacer() + marker_chooser = common.DropDownController(xy_menu_panel, MARKER_TYPES, parent, XY_MARKER_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + xy_menu_box.AddStretchSpacer() + ################################################## + # Run/Stop Button + ################################################## #run/stop self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') control_box.Add(self.run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event + def on_mouse_wheel(event): + if not parent[XY_MODE_KEY]: + if event.GetWheelRotation() < 0: self._on_incr_t_divs(event) + else: self._on_decr_t_divs(event) + parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) ################################################## # Event handlers ################################################## - def _on_scope_xy_mode(self, mode): - self.scope_mode_box.ShowItems(not mode) - self.xy_mode_box.ShowItems(mode) - self.control_box.Layout() + #trigger level + def _on_incr_trigger_level(self, event): + self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3. + def _on_decr_trigger_level(self, event): + self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3. #incr/decr divs def _on_incr_t_divs(self, event): - self.parent.set_t_per_div( - common.get_clean_incr(self.parent[T_PER_DIV_KEY])) + self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY]) def _on_decr_t_divs(self, event): - self.parent.set_t_per_div( - common.get_clean_decr(self.parent[T_PER_DIV_KEY])) + self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY]) def _on_incr_x_divs(self, event): - self.parent.set_x_per_div( - common.get_clean_incr(self.parent[X_PER_DIV_KEY])) + self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY]) def _on_decr_x_divs(self, event): - self.parent.set_x_per_div( - common.get_clean_decr(self.parent[X_PER_DIV_KEY])) + self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY]) def _on_incr_y_divs(self, event): - self.parent.set_y_per_div( - common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) + self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY]) def _on_decr_y_divs(self, event): - self.parent.set_y_per_div( - common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) + self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY]) #incr/decr offset - def _on_incr_t_off(self, event): - self.parent.set_t_off( - self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY]) - def _on_decr_t_off(self, event): - self.parent.set_t_off( - self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY]) def _on_incr_x_off(self, event): - self.parent.set_x_off( - self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]) + self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY] def _on_decr_x_off(self, event): - self.parent.set_x_off( - self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]) + self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY] def _on_incr_y_off(self, event): - self.parent.set_y_off( - self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]) + self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_y_off(self, event): - self.parent.set_y_off( - self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]) + self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY] ################################################## # Scope window with plotter and control panel ################################################## -class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class scope_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -259,11 +296,13 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): sample_rate_key, t_scale, v_scale, - ac_couple, xy_mode, - scope_trigger_level_key, - scope_trigger_mode_key, - scope_trigger_channel_key, + ac_couple_key, + trigger_level_key, + trigger_mode_key, + trigger_slope_key, + trigger_channel_key, + decimation_key, msg_key, ): pubsub.pubsub.__init__(self) @@ -271,67 +310,73 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): assert num_inputs <= len(CHANNEL_COLOR_SPECS) #setup self.sampleses = None - self.ext_controller = controller self.num_inputs = num_inputs - self.sample_rate_key = sample_rate_key - autorange = v_scale is None + autorange = not v_scale self.autorange_ts = 0 - if v_scale is None: v_scale = 1 + v_scale = v_scale or 1 self.frame_rate_ts = 0 - self._init = False #HACK - #scope keys - self.scope_trigger_level_key = scope_trigger_level_key - self.scope_trigger_mode_key = scope_trigger_mode_key - self.scope_trigger_channel_key = scope_trigger_channel_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key) + self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key) + self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key) + self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key) + self.proxy(DECIMATION_KEY, controller, decimation_key) + for i in range(num_inputs): + self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.enable_legend(True) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #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) - #initial setup - self._register_set_prop(self, RUNNING_KEY, True) - self._register_set_prop(self, AC_COUPLE_KEY, ac_couple) - self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode) - self._register_set_prop(self, AUTORANGE_KEY, autorange) - self._register_set_prop(self, T_PER_DIV_KEY, t_scale) - self._register_set_prop(self, X_PER_DIV_KEY, v_scale) - self._register_set_prop(self, Y_PER_DIV_KEY, v_scale) - self._register_set_prop(self, T_OFF_KEY, 0) - self._register_set_prop(self, X_OFF_KEY, 0) - self._register_set_prop(self, Y_OFF_KEY, 0) - self._register_set_prop(self, T_DIVS_KEY, 8) - self._register_set_prop(self, X_DIVS_KEY, 8) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0) - self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1) - self._register_set_prop(self, FRAME_RATE_KEY, frame_rate) - self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0) - self._register_set_prop(self, TRIGGER_MODE_KEY, 1) - self._register_set_prop(self, TRIGGER_LEVEL_KEY, None) - self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) - #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - for key in ( + #initialize values + self[RUNNING_KEY] = True + for i in range(self.num_inputs): + self[common.index_key(AC_COUPLE_KEY, i)] = self[common.index_key(AC_COUPLE_KEY, i)] + self[common.index_key(MARKER_KEY, i)] = DEFAULT_MARKER_TYPE + self[XY_MARKER_KEY] = 2.0 + self[XY_MODE_KEY] = xy_mode + self[X_CHANNEL_KEY] = 0 + self[Y_CHANNEL_KEY] = self.num_inputs-1 + self[AUTORANGE_KEY] = autorange + self[T_PER_DIV_KEY] = t_scale + self[X_PER_DIV_KEY] = v_scale + self[Y_PER_DIV_KEY] = v_scale + self[T_OFF_KEY] = 0 + self[X_OFF_KEY] = 0 + self[Y_OFF_KEY] = 0 + self[T_DIVS_KEY] = 8 + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[FRAME_RATE_KEY] = frame_rate + self[TRIGGER_LEVEL_KEY] = 0 + self[TRIGGER_CHANNEL_KEY] = 0 + self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO + self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS + self[T_FRAC_OFF_KEY] = 0.5 + #register events for message + self.subscribe(MSG_KEY, self.handle_msg) + #register events for grid + for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [ + TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY, T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY, T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY, T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY, - SCOPE_XY_MODE_KEY, - SCOPE_X_CHANNEL_KEY, - SCOPE_Y_CHANNEL_KEY, - AUTORANGE_KEY, - AC_COUPLE_KEY, - MARKER_KEY, - ): self.subscribe(key, self.update_grid) - #initial update, dont do this here, wait for handle_msg #HACK - #self.update_grid() + XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY, + TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, + ]: self.subscribe(key, self.update_grid) + #initial update + self.update_grid() def handle_msg(self, msg): """ @@ -345,15 +390,23 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return #convert to floating point numbers samples = numpy.fromstring(msg, numpy.float32) + #extract the trigger offset + self.trigger_offset = samples[-1] + samples = samples[:-1] samps_per_ch = len(samples)/self.num_inputs self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)] - if not self._init: #HACK - self._init = True - self.update_grid() #handle samples self.handle_samples() self.frame_rate_ts = time.time() + def set_auto_trigger_level(self, *args): + """ + Use the current trigger channel and samples to calculate the 50% level. + """ + if not self.sampleses: return + samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]] + self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2 + def handle_samples(self): """ Handle the cached samples from the scope input. @@ -361,52 +414,37 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): """ if not self.sampleses: return sampleses = self.sampleses - #trigger level (must do before ac coupling) - self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY] - self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY] - trigger_level = self[TRIGGER_LEVEL_KEY] - if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = '' - else: - samples = sampleses[self[TRIGGER_CHANNEL_KEY]] - self.ext_controller[self.scope_trigger_level_key] = \ - trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples) - #ac coupling - if self[AC_COUPLE_KEY]: - sampleses = [samples - numpy.average(samples) for samples in sampleses] - if self[SCOPE_XY_MODE_KEY]: - x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]] - y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]] + if self[XY_MODE_KEY]: + self[DECIMATION_KEY] = 1 + x_samples = sampleses[self[X_CHANNEL_KEY]] + y_samples = sampleses[self[Y_CHANNEL_KEY]] #autorange if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: x_min, x_max = common.get_min_max(x_samples) y_min, y_max = common.get_min_max(y_samples) #adjust the x per div x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY]) - if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div) + if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return #adjust the x offset x_off = x_per_div*round((x_max+x_min)/2/x_per_div) - if x_off != self[X_OFF_KEY]: self.set_x_off(x_off) + if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return #adjust the y per div y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return #adjust the y offset y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off) + if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return self.autorange_ts = time.time() #plot xy channel self.plotter.set_waveform( channel='XY', samples=(x_samples, y_samples), color_spec=CHANNEL_COLOR_SPECS[0], - marker=self[MARKER_KEY], + marker=self[XY_MARKER_KEY], ) #turn off each waveform for i, samples in enumerate(sampleses): - self.plotter.set_waveform( - channel='Ch%d'%(i+1), - samples=[], - color_spec=CHANNEL_COLOR_SPECS[i], - ) + self.plotter.clear_waveform(channel='Ch%d'%(i+1)) else: #autorange if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: @@ -415,86 +453,89 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): y_max = numpy.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_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return #adjust the y offset y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off) + if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return 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[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key]) - #handle num samps out of bounds - if num_samps > len(samples): - self.set_t_per_div( - common.get_clean_decr(self[T_PER_DIV_KEY])) - elif num_samps < 2: - self.set_t_per_div( - common.get_clean_incr(self[T_PER_DIV_KEY])) - num_samps = 0 - else: + #number of samples to scale to the screen + actual_rate = self.get_actual_rate() + time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + num_samps = int(round(time_span*actual_rate)) + #handle the time offset + t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span) + if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return + samps_off = int(round(actual_rate*self[T_OFF_KEY])) + #adjust the decim so that we use about half the samps + self[DECIMATION_KEY] = int(round( + time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0])) + ) + ) + #num samps too small, auto increment the time + if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY]) + #num samps in bounds, plot each waveform + elif num_samps <= len(sampleses[0]): + for i, samples in enumerate(sampleses): #plot samples self.plotter.set_waveform( channel='Ch%d'%(i+1), - samples=samples[:num_samps], + samples=samples[samps_off:num_samps+samps_off], color_spec=CHANNEL_COLOR_SPECS[i], - marker=self[MARKER_KEY], + marker=self[common.index_key(MARKER_KEY, i)], + trig_off=self.trigger_offset, ) #turn XY channel off + self.plotter.clear_waveform(channel='XY') + #keep trigger level within range + if self[TRIGGER_LEVEL_KEY] > self.get_y_max(): + self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return + if self[TRIGGER_LEVEL_KEY] < self.get_y_min(): + self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return + #disable the trigger channel + if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_FREE: + self.plotter.clear_waveform(channel='Trig') + else: #show trigger channel + trigger_level = self[TRIGGER_LEVEL_KEY] + trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0 self.plotter.set_waveform( - channel='XY', - samples=[], - color_spec=CHANNEL_COLOR_SPECS[0], + channel='Trig', + samples=( + [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()], + [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level] + ), + color_spec=TRIGGER_COLOR_SPEC, ) #update the plotter self.plotter.update() + def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY] + def get_t_min(self): return self[T_OFF_KEY] + def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY] + def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] + def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] + def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] + def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] + def update_grid(self, *args): """ Update the grid to reflect the current settings: xy divisions, xy offset, xy mode setting """ - #grid parameters - t_per_div = self[T_PER_DIV_KEY] - x_per_div = self[X_PER_DIV_KEY] - y_per_div = self[Y_PER_DIV_KEY] - t_off = self[T_OFF_KEY] - x_off = self[X_OFF_KEY] - y_off = self[Y_OFF_KEY] - t_divs = self[T_DIVS_KEY] - x_divs = self[X_DIVS_KEY] - y_divs = self[Y_DIVS_KEY] - if self[SCOPE_XY_MODE_KEY]: + if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return + if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return + if self[XY_MODE_KEY]: #update the x axis - self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1)) - self.plotter.set_x_grid( - -1*x_per_div*x_divs/2.0 + x_off, - x_per_div*x_divs/2.0 + x_off, - x_per_div, - ) + self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1)) + self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY]) #update the y axis - self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1)) - self.plotter.set_y_grid( - -1*y_per_div*y_divs/2.0 + y_off, - y_per_div*y_divs/2.0 + y_off, - y_per_div, - ) + self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1)) + self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) else: #update the t axis - coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off) - self.plotter.set_x_label('Time', prefix+'s') - self.plotter.set_x_grid( - t_off, - t_per_div*t_divs + t_off, - t_per_div, - 10**(-exp), - ) + self.plotter.set_x_label('Time', 's') + self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True) #update the y axis self.plotter.set_y_label('Counts') - self.plotter.set_y_grid( - -1*y_per_div*y_divs/2.0 + y_off, - y_per_div*y_divs/2.0 + y_off, - y_per_div, - ) + self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) #redraw current sample self.handle_samples() diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 40f9f3886..87aa4337f 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -38,7 +38,7 @@ if style == 'gl': except ImportError: raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - from scopesink_gl import scope_sink_f, scope_sink_c, constellation_sink + from scopesink_gl import scope_sink_f, scope_sink_c else: - from scopesink_nongl import scope_sink_f, scope_sink_c, constellation_sink + from scopesink_nongl import scope_sink_f, scope_sink_c diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 36d8d8b8a..73125c359 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -28,10 +28,38 @@ from gnuradio import gr from pubsub import pubsub from constants import * +class ac_couple_block(gr.hier_block2): + """ + AC couple the incoming stream by subtracting out the low pass signal. + Mute the low pass filter to disable ac coupling. + """ + + def __init__(self, controller, ac_couple_key, ac_couple, sample_rate_key): + gr.hier_block2.__init__( + self, + "ac_couple", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(1, 1, gr.sizeof_float), + ) + #blocks + copy = gr.kludge_copy(gr.sizeof_float) + lpf = gr.single_pole_iir_filter_ff(0.0) + sub = gr.sub_ff() + mute = gr.mute_ff() + #connect + self.connect(self, copy, sub, self) + self.connect(copy, lpf, mute, (sub, 1)) + #subscribe + controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) + controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(2.0/x)) + #initialize + controller[ac_couple_key] = ac_couple + controller[sample_rate_key] = controller[sample_rate_key] + ################################################## # Scope sink block (wrapper for old wxgui) ################################################## -class _scope_sink_base(gr.hier_block2, common.prop_setter): +class _scope_sink_base(gr.hier_block2): """ A scope block with a gui window. """ @@ -42,15 +70,14 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): title='', sample_rate=1, size=scope_window.DEFAULT_WIN_SIZE, - frame_decim=None, #ignore (old wrapper) - v_scale=scope_window.DEFAULT_V_SCALE, - t_scale=None, - num_inputs=1, - ac_couple=False, + v_scale=0, + t_scale=0, xy_mode=False, + ac_couple=False, + num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, ): - if t_scale is None: t_scale = 0.001 + if not t_scale: t_scale = 10.0/sample_rate #init gr.hier_block2.__init__( self, @@ -61,37 +88,41 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): #scope msgq = gr.msg_queue(2) scope = gr.oscope_sink_f(sample_rate, msgq) + #controller + self.controller = pubsub() + self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) + self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) + self.controller.subscribe(DECIMATION_KEY, scope.set_decimation_count) + self.controller.publish(DECIMATION_KEY, scope.get_decimation_count) + self.controller.subscribe(TRIGGER_LEVEL_KEY, scope.set_trigger_level) + self.controller.publish(TRIGGER_LEVEL_KEY, scope.get_trigger_level) + self.controller.subscribe(TRIGGER_MODE_KEY, scope.set_trigger_mode) + self.controller.publish(TRIGGER_MODE_KEY, scope.get_trigger_mode) + self.controller.subscribe(TRIGGER_SLOPE_KEY, scope.set_trigger_slope) + self.controller.publish(TRIGGER_SLOPE_KEY, scope.get_trigger_slope) + self.controller.subscribe(TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) + self.controller.publish(TRIGGER_CHANNEL_KEY, scope.get_trigger_channel) #connect if self._real: for i in range(num_inputs): - self.connect((self, i), (scope, i)) + self.connect( + (self, i), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, i), ac_couple, SAMPLE_RATE_KEY), + (scope, i), + ) else: for i in range(num_inputs): c2f = gr.complex_to_float() self.connect((self, i), c2f) - self.connect((c2f, 0), (scope, 2*i+0)) - self.connect((c2f, 1), (scope, 2*i+1)) + for j in range(2): + self.connect( + (c2f, j), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), ac_couple, SAMPLE_RATE_KEY), + (scope, 2*i+j), + ) num_inputs *= 2 - #controller - self.controller = pubsub() - self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) - def set_trigger_level(level): - if level == '': scope.set_trigger_level_auto() - else: scope.set_trigger_level(level) - self.controller.subscribe(SCOPE_TRIGGER_LEVEL_KEY, set_trigger_level) - def set_trigger_mode(mode): - if mode == 0: mode = gr.gr_TRIG_AUTO - elif mode < 0: mode = gr.gr_TRIG_NEG_SLOPE - elif mode > 0: mode = gr.gr_TRIG_POS_SLOPE - else: return - scope.set_trigger_mode(mode) - self.controller.subscribe(SCOPE_TRIGGER_MODE_KEY, set_trigger_mode) - self.controller.subscribe(SCOPE_TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = scope_window.scope_window( parent=parent, @@ -103,21 +134,16 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): sample_rate_key=SAMPLE_RATE_KEY, t_scale=t_scale, v_scale=v_scale, - ac_couple=ac_couple, xy_mode=xy_mode, - scope_trigger_level_key=SCOPE_TRIGGER_LEVEL_KEY, - scope_trigger_mode_key=SCOPE_TRIGGER_MODE_KEY, - scope_trigger_channel_key=SCOPE_TRIGGER_CHANNEL_KEY, + ac_couple_key=AC_COUPLE_KEY, + trigger_level_key=TRIGGER_LEVEL_KEY, + trigger_mode_key=TRIGGER_MODE_KEY, + trigger_slope_key=TRIGGER_SLOPE_KEY, + trigger_channel_key=TRIGGER_CHANNEL_KEY, + decimation_key=DECIMATION_KEY, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - #backwards compadibility - self.win.set_format_line = lambda: setter(self.win, MARKER_KEY, None) - self.win.set_format_dot = lambda: setter(self.win, MARKER_KEY, 2.0) - self.win.set_format_plus = lambda: setter(self.win, MARKER_KEY, 3.0) + common.register_access_methods(self, self.win) class scope_sink_f(_scope_sink_base): _item_size = gr.sizeof_float @@ -127,12 +153,6 @@ class scope_sink_c(_scope_sink_base): _item_size = gr.sizeof_gr_complex _real = False -#backwards compadible wrapper (maybe only grc uses this) -class constellation_sink(scope_sink_c): - def __init__(self, *args, **kwargs): - scope_sink_c.__init__(self, *args, **kwargs) - self.set_scope_xy_mode(True) - # ---------------------------------------------------------------- # Stand-alone test application # ---------------------------------------------------------------- @@ -171,7 +191,6 @@ class test_top_block (stdgui2.std_top_block): self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, - frame_decim=frame_decim, v_scale=v_scale, t_scale=t_scale) vbox.Add (scope.win, 1, wx.EXPAND) diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index 71fd7e128..34967dd13 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -35,7 +35,7 @@ default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) class scope_sink_f(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): + v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): gr.hier_block2.__init__(self, "scope_sink_f", gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), @@ -56,7 +56,7 @@ class scope_sink_f(gr.hier_block2): class scope_sink_c(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): + v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): gr.hier_block2.__init__(self, "scope_sink_c", gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), @@ -167,10 +167,7 @@ class win_info (object): self.marker = 'line' self.xy = xy - if v_scale == None: # 0 and None are both False, but 0 != None - self.autorange = True - else: - self.autorange = False # 0 is a valid v_scale + self.autorange = not v_scale self.running = True def get_time_per_div (self): @@ -320,7 +317,8 @@ class scope_window (wx.Panel): ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) self.trig_mode_choice = wx.Choice (self, 1005, - choices = ['Auto', 'Pos', 'Neg']) + choices = ['Free', 'Auto', 'Norm']) + self.trig_mode_choice.SetSelection(1) self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) @@ -432,12 +430,12 @@ class scope_window (wx.Panel): def trig_mode_choice_event (self, evt): sink = self.info.scopesink s = evt.GetString () - if s == 'Pos': - sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) - elif s == 'Neg': - sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) + if s == 'Norm': + sink.set_trigger_mode (gr.gr_TRIG_MODE_NORM) elif s == 'Auto': - sink.set_trigger_mode (gr.gr_TRIG_AUTO) + sink.set_trigger_mode (gr.gr_TRIG_MODE_AUTO) + elif s == 'Free': + sink.set_trigger_mode (gr.gr_TRIG_MODE_FREE) else: assert 0, "Bad trig_mode_choice string" diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index f24b142a7..8dcb4b619 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -16,7 +16,7 @@ # 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. +# Boston, MA 02110-1`301, USA. # ################################################## @@ -61,57 +61,57 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) #color mode control_box.AddStretchSpacer() - self.color_mode_chooser = common.DropDownController(self, 'Color', COLOR_MODES, parent, COLOR_MODE_KEY) - control_box.Add(self.color_mode_chooser, 0, wx.EXPAND) + color_mode_chooser = common.DropDownController(self, COLOR_MODES, parent, COLOR_MODE_KEY) + control_box.Add(common.LabelBox(self, 'Color', color_mode_chooser), 0, wx.EXPAND) #average control_box.AddStretchSpacer() - self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) + average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( + avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) + control_box.Add(avg_alpha_slider, 0, wx.EXPAND) #dyanmic range buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) - control_box.Add(self._dynamic_range_buttons, 0, wx.ALIGN_CENTER) + dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) + control_box.Add(dynamic_range_buttons, 0, wx.ALIGN_CENTER) #ref lvl buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Ref Level'), 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) + ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) + control_box.Add(ref_lvl_buttons, 0, wx.ALIGN_CENTER) #num lines buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) - control_box.Add(self._time_scale_buttons, 0, wx.ALIGN_CENTER) + time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) + control_box.Add(time_scale_buttons, 0, wx.ALIGN_CENTER) #autoscale control_box.AddStretchSpacer() - self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(self.autoscale_button, 0, wx.EXPAND) + autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(autoscale_button, 0, wx.EXPAND) #clear - self.clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) - self.clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) - control_box.Add(self.clear_button, 0, wx.EXPAND) + clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) + clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) + control_box.Add(clear_button, 0, wx.EXPAND) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) @@ -119,34 +119,30 @@ class control_panel(wx.Panel): # Event handlers ################################################## def _on_clear_button(self, event): - self.parent.set_num_lines(self.parent[NUM_LINES_KEY]) + self.parent[NUM_LINES_KEY] = self.parent[NUM_LINES_KEY] def _on_incr_dynamic_range(self, event): - self.parent.set_dynamic_range( - min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE)) + self.parent[DYNAMIC_RANGE_KEY] = min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE) def _on_decr_dynamic_range(self, event): - self.parent.set_dynamic_range( - max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE)) + self.parent[DYNAMIC_RANGE_KEY] = max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE) def _on_incr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1 def _on_decr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1 def _on_incr_time_scale(self, event): - old_rate = self.parent.ext_controller[self.parent.frame_rate_key] - self.parent.ext_controller[self.parent.frame_rate_key] *= 0.75 - if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: - self.parent.ext_controller[self.parent.decimation_key] += 1 + old_rate = self.parent[FRAME_RATE_KEY] + self.parent[FRAME_RATE_KEY] *= 0.75 + if self.parent[FRAME_RATE_KEY] == old_rate: + self.parent[DECIMATION_KEY] += 1 def _on_decr_time_scale(self, event): - old_rate = self.parent.ext_controller[self.parent.frame_rate_key] - self.parent.ext_controller[self.parent.frame_rate_key] *= 1.25 - if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: - self.parent.ext_controller[self.parent.decimation_key] -= 1 + old_rate = self.parent[FRAME_RATE_KEY] + self.parent[FRAME_RATE_KEY] *= 1.25 + if self.parent[FRAME_RATE_KEY] == old_rate: + self.parent[DECIMATION_KEY] -= 1 ################################################## # Waterfall window with plotter and control panel ################################################## -class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class waterfall_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -169,20 +165,22 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): pubsub.pubsub.__init__(self) #setup self.samples = list() - self.ext_controller = controller self.real = real self.fft_size = fft_size - self.decimation_key = decimation_key - self.sample_rate_key = sample_rate_key - self.frame_rate_key = frame_rate_key - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(DECIMATION_KEY, controller, decimation_key) + self.proxy(FRAME_RATE_KEY, controller, frame_rate_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.waterfall_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(False) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -192,26 +190,23 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): #plotter listeners self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode) self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range) - self._register_set_prop(self, NUM_LINES_KEY, num_lines) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, X_DIVS_KEY, 8) #approximate - self._register_set_prop(self, REF_LEVEL_KEY, ref_level) - self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) - self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1]) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] + self[DYNAMIC_RANGE_KEY] = dynamic_range + self[NUM_LINES_KEY] = num_lines + self[Y_DIVS_KEY] = 8 + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[COLOR_MODE_KEY] = COLOR_MODES[0][1] + self[RUNNING_KEY] = True #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - self.ext_controller.subscribe(self.decimation_key, self.update_grid) - self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) - self.ext_controller.subscribe(self.frame_rate_key, self.update_grid) - self.subscribe(BASEBAND_FREQ_KEY, self.update_grid) - self.subscribe(NUM_LINES_KEY, self.update_grid) - self.subscribe(Y_DIVS_KEY, self.update_grid) - self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(MSG_KEY, self.handle_msg) + for key in ( + DECIMATION_KEY, SAMPLE_RATE_KEY, FRAME_RATE_KEY, + BASEBAND_FREQ_KEY, X_DIVS_KEY, Y_DIVS_KEY, NUM_LINES_KEY, + ): self.subscribe(key, self.update_grid) #initial update self.update_grid() @@ -230,8 +225,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): noise_floor -= abs(noise_floor)*.5 peak_level += abs(peak_level)*.1 #set the range and level - self.set_ref_level(peak_level) - self.set_dynamic_range(peak_level - noise_floor) + self[REF_LEVEL_KEY] = peak_level + self[DYNAMIC_RANGE_KEY] = peak_level - noise_floor def handle_msg(self, msg): """ @@ -266,8 +261,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): The y axis depends on y per div, y divs, and ref level. """ #grid parameters - sample_rate = self.ext_controller[self.sample_rate_key] - frame_rate = self.ext_controller[self.frame_rate_key] + sample_rate = self[SAMPLE_RATE_KEY] + frame_rate = self[FRAME_RATE_KEY] baseband_freq = self[BASEBAND_FREQ_KEY] num_lines = self[NUM_LINES_KEY] y_divs = self[Y_DIVS_KEY] @@ -276,28 +271,25 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self.real: x_width = sample_rate/2.0 else: x_width = sample_rate/1.0 x_per_div = common.get_clean_num(x_width/x_divs) - coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) #update the x grid if self.real: self.plotter.set_x_grid( baseband_freq, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) else: self.plotter.set_x_grid( baseband_freq - sample_rate/2.0, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) #update x units - self.plotter.set_x_label('Frequency', prefix+'Hz') + self.plotter.set_x_label('Frequency', 'Hz') #update y grid duration = float(num_lines)/frame_rate y_per_div = common.get_clean_num(duration/y_divs) - self.plotter.set_y_grid(0, duration, y_per_div) + self.plotter.set_y_grid(0, duration, y_per_div, True) #update y units self.plotter.set_y_label('Time', 's') #update plotter diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index dd8e45753..344640af0 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Waterfall sink block (wrapper for old wxgui) ################################################## -class _waterfall_sink_base(gr.hier_block2, common.prop_setter): +class _waterfall_sink_base(gr.hier_block2): """ An fft block with real/complex inputs and a gui window. """ @@ -89,9 +89,7 @@ class _waterfall_sink_base(gr.hier_block2, common.prop_setter): self.controller.subscribe(FRAME_RATE_KEY, fft.set_vec_rate) self.controller.publish(FRAME_RATE_KEY, fft.frame_rate) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = waterfall_window.waterfall_window( parent=parent, @@ -111,12 +109,8 @@ class _waterfall_sink_base(gr.hier_block2, common.prop_setter): avg_alpha_key=AVG_ALPHA_KEY, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - self._register_set_prop(self.controller, AVERAGE_KEY) - self._register_set_prop(self.controller, AVG_ALPHA_KEY) + common.register_access_methods(self, self.win) + setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS class waterfall_sink_f(_waterfall_sink_base): _fft_chain = blks2.logpwrfft_f -- cgit From 22b2e334a5fe7a909d936f1c948328418eca0e8c Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 30 Mar 2009 20:57:00 +0000 Subject: option to enforce padding aspect ratio in grid plotters git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10714 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/plotter/grid_plotter_base.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py index fd318ffa0..39bed1811 100644 --- a/gr-wxgui/src/python/plotter/grid_plotter_base.py +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -62,6 +62,7 @@ class grid_plotter_base(plotter_base): #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 @@ -84,6 +85,20 @@ class grid_plotter_base(plotter_base): self.update() self.unlock() + 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 + @param enable true to enable + @return 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. @@ -204,6 +219,23 @@ class grid_plotter_base(plotter_base): 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 ################################################## -- cgit From cb9b98a7b9bd5e2678a35bba6c6c8e6565c47d79 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 2 Apr 2009 20:02:15 +0000 Subject: added catchall **kwargs to scope sink gl git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10745 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/scopesink_gl.py | 1 + 1 file changed, 1 insertion(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 73125c359..6e9ff832a 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -76,6 +76,7 @@ class _scope_sink_base(gr.hier_block2): ac_couple=False, num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, + **kwargs #do not end with a comma ): if not t_scale: t_scale = 10.0/sample_rate #init -- cgit From 77bfe4faccd79741b49e0dee3bb0a21bd21da53f Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sun, 19 Apr 2009 20:45:40 +0000 Subject: Merged r10875:10880 from jcorgan/t161 into trunk. Implements ticket:161, allowing multiple internal blocks to be connected to a hier_block2 external input. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10881 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink_gl.py | 3 +-- gr-wxgui/src/python/scopesink_gl.py | 5 ++--- gr-wxgui/src/python/waterfallsink_gl.py | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 30ebd3fde..3f0a93fc8 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -63,7 +63,6 @@ class _fft_sink_base(gr.hier_block2): 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, @@ -75,7 +74,7 @@ class _fft_sink_base(gr.hier_block2): msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) #connect - self.connect(self, copy, fft, sink) + self.connect(self, fft, sink) #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 6e9ff832a..b4ae0f339 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -42,13 +42,12 @@ class ac_couple_block(gr.hier_block2): gr.io_signature(1, 1, gr.sizeof_float), ) #blocks - copy = gr.kludge_copy(gr.sizeof_float) lpf = gr.single_pole_iir_filter_ff(0.0) sub = gr.sub_ff() mute = gr.mute_ff() #connect - self.connect(self, copy, sub, self) - self.connect(copy, lpf, mute, (sub, 1)) + self.connect(self, sub, self) + self.connect(self, lpf, mute, (sub, 1)) #subscribe controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(2.0/x)) diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index 344640af0..91c1c7eb5 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -63,7 +63,6 @@ class _waterfall_sink_base(gr.hier_block2): 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, @@ -75,7 +74,7 @@ class _waterfall_sink_base(gr.hier_block2): msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) #connect - self.connect(self, copy, fft, sink) + self.connect(self, fft, sink) #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) -- cgit From 88164b0cde4b740654f72b088e44eb9158f8ff48 Mon Sep 17 00:00:00 2001 From: jblum Date: Wed, 6 May 2009 20:58:28 +0000 Subject: numbersink fix for when average=True on init git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10979 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/numbersink2.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 5fa9e3aef..4a182bd04 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -87,13 +87,14 @@ class _number_sink_base(gr.hier_block2): self.controller = pubsub() self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) + self.controller[AVERAGE_KEY] = average + self.controller[AVG_ALPHA_KEY] = avg_alpha def update_avg(*args): if self.controller[AVERAGE_KEY]: avg.set_taps(self.controller[AVG_ALPHA_KEY]) else: avg.set_taps(1.0) + update_avg() self.controller.subscribe(AVERAGE_KEY, update_avg) self.controller.subscribe(AVG_ALPHA_KEY, update_avg) - self.controller[AVERAGE_KEY] = average - self.controller[AVG_ALPHA_KEY] = avg_alpha #start input watcher common.input_watcher(msgq, self.controller, MSG_KEY) #create window -- cgit From 45981ced71e44f55d12005abc7fe65cbbc9bb3ae Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 15 May 2009 17:48:12 +0000 Subject: Added arg to start non gl scope in xy mode. Harmless and backwards compadible. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11047 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/scopesink_nongl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index 34967dd13..bd3187992 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -56,7 +56,7 @@ class scope_sink_f(gr.hier_block2): class scope_sink_c(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): + v_scale=default_v_scale, t_scale=None, num_inputs=1, xy_mode=False, **kwargs): gr.hier_block2.__init__(self, "scope_sink_c", gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), @@ -72,6 +72,7 @@ class scope_sink_c(gr.hier_block2): self.win = scope_window(win_info(msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) + self.win.info.xy = xy_mode def set_sample_rate(self, sample_rate): self.guts.set_sample_rate(sample_rate) -- cgit From 2cd2d8f3ab6971ececbb44ddb2e2a4c7fc3bf1a5 Mon Sep 17 00:00:00 2001 From: jcorgan Date: Sat, 23 May 2009 16:43:05 +0000 Subject: Change wxgui style default now so 'auto' tries for OpenGL sinks and falls back to non-GL sinks. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11091 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fftsink2.py | 25 +++++++++++-------------- gr-wxgui/src/python/scopesink2.py | 25 +++++++++++-------------- gr-wxgui/src/python/waterfallsink2.py | 25 +++++++++++-------------- 3 files changed, 33 insertions(+), 42 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink2.py b/gr-wxgui/src/python/fftsink2.py index ecc60834b..3277cd3ff 100644 --- a/gr-wxgui/src/python/fftsink2.py +++ b/gr-wxgui/src/python/fftsink2.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,21 +24,18 @@ from gnuradio import gr p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') -# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -# Currently, anything other than 'gl' means 'nongl' - -if style == 'gl': +if style == 'auto' or style == 'gl': try: import wx.glcanvas - except ImportError: - raise RuntimeError("wxPython doesn't support glcanvas") - - try: from OpenGL.GL import * + from fftsink_gl import fft_sink_f, fft_sink_c except ImportError: - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - - from fftsink_gl import fft_sink_f, fft_sink_c - -else: + if style == 'gl': + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") + else: + # Fall backto non-gl sinks + from fftsink_nongl import fft_sink_f, fft_sink_c +elif style == 'nongl': from fftsink_nongl import fft_sink_f, fft_sink_c +else: + raise RuntimeError("Unknown wxgui style") diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 87aa4337f..99e268895 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,21 +24,18 @@ from gnuradio import gr p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') -# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -# Currently, anything other than 'gl' means 'nongl' - -if style == 'gl': +if style == 'auto' or style == 'gl': try: import wx.glcanvas - except ImportError: - raise RuntimeError("wxPython doesn't support glcanvas") - - try: from OpenGL.GL import * + from scopesink_gl import scope_sink_f, scope_sink_c except ImportError: - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - - from scopesink_gl import scope_sink_f, scope_sink_c - -else: + if style == 'gl': + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") + else: + # Fall backto non-gl sinks + from scopesink_nongl import scope_sink_f, scope_sink_c +elif style == 'nongl': from scopesink_nongl import scope_sink_f, scope_sink_c +else: + raise RuntimeError("Unknown wxgui style") diff --git a/gr-wxgui/src/python/waterfallsink2.py b/gr-wxgui/src/python/waterfallsink2.py index 08e96105d..0b876fc3e 100644 --- a/gr-wxgui/src/python/waterfallsink2.py +++ b/gr-wxgui/src/python/waterfallsink2.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -24,21 +24,18 @@ from gnuradio import gr p = gr.prefs() style = p.get_string('wxgui', 'style', 'auto') -# In 3.2 we'll change 'auto' to mean 'gl' if possible, then fallback -# Currently, anything other than 'gl' means 'nongl' - -if style == 'gl': +if style == 'auto' or style == 'gl': try: import wx.glcanvas - except ImportError: - raise RuntimeError("wxPython doesn't support glcanvas") - - try: from OpenGL.GL import * + from waterfallsink_gl import waterfall_sink_f, waterfall_sink_c except ImportError: - raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - - from waterfallsink_gl import waterfall_sink_f, waterfall_sink_c - -else: + if style == 'gl': + raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") + else: + # Fall backto non-gl sinks + from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c +elif style == 'nongl': from waterfallsink_nongl import waterfall_sink_f, waterfall_sink_c +else: + raise RuntimeError("Unknown wxgui style") -- cgit From efc3d118fe6e2386da9cf3d8a4a09df0efa749b1 Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 29 May 2009 00:25:16 +0000 Subject: removed defunct base_value parameter from numbersink git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11162 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/numbersink2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 4a182bd04..7f853e6a4 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -40,7 +40,6 @@ class _number_sink_base(gr.hier_block2): self, parent, unit='units', - base_value=None, #ignore (old wrapper) minval=0, maxval=1, factor=1, @@ -54,6 +53,7 @@ class _number_sink_base(gr.hier_block2): size=number_window.DEFAULT_WIN_SIZE, peak_hold=False, show_gauge=True, + **kwargs #catchall for backwards compatibility ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/number_rate -- cgit From 51af4269d3eebd3d611be918f8c799c96c650496 Mon Sep 17 00:00:00 2001 From: jblum Date: Sat, 13 Jun 2009 21:26:25 +0000 Subject: Merged wxgui/forms branch r11124:11183 The forms module is set of wxgui forms wrapped in pubsub aware convenience classes. The forms module will be used by the wxgui window classes (fft, scope, waterfall...) The forms module will be used in grc generated flowgraphs. The forms module will be used by future gui apps (usrp siggen...). Tasks: Moved forms module into wxgui. Modified *_window classes to use the forms module. Added features to forms as required. Removed pubsub aware forms in common.py. Switched grc to use the forms module in wxgui. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11184 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/Makefile.am | 7 + gr-wxgui/src/python/common.py | 162 -------- gr-wxgui/src/python/const_window.py | 68 ++-- gr-wxgui/src/python/constants.py | 1 + gr-wxgui/src/python/fft_window.py | 116 +++--- gr-wxgui/src/python/forms/__init__.py | 103 +++++ gr-wxgui/src/python/forms/converters.py | 145 ++++++++ gr-wxgui/src/python/forms/forms.py | 641 ++++++++++++++++++++++++++++++++ gr-wxgui/src/python/histo_window.py | 43 ++- gr-wxgui/src/python/number_window.py | 128 ++++--- gr-wxgui/src/python/scope_window.py | 298 +++++++++------ gr-wxgui/src/python/waterfall_window.py | 116 +++--- 12 files changed, 1353 insertions(+), 475 deletions(-) create mode 100644 gr-wxgui/src/python/forms/__init__.py create mode 100644 gr-wxgui/src/python/forms/converters.py create mode 100644 gr-wxgui/src/python/forms/forms.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 45d75b605..e06298a2d 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -59,3 +59,10 @@ ourpython_PYTHON = \ waterfall_window.py \ slider.py \ stdgui2.py + +formspythondir = $(grpythondir)/wxgui/forms + +formspython_PYTHON = \ + forms/__init__.py \ + forms/forms.py \ + forms/converters.py diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index c84827eb8..c6b9509b2 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -70,168 +70,6 @@ class input_watcher(threading.Thread): if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() self._controller[self._msg_key] = msg.to_string() -################################################## -# WX Shared Classes -################################################## -import math -import wx - -EVT_DATA = wx.PyEventBinder(wx.NewEventType()) -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) - self.data = data - -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, label=label) - font = self.GetFont() - font.SetWeight(wx.FONTWEIGHT_BOLD) - self.SetFont(font) - -class LabelBox(wx.BoxSizer): - def __init__(self, parent, label, widget): - wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self.Add(wx.StaticText(parent, label=' %s '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) - self.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) - -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, label='+', 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, label=' - ', style=wx.BU_EXACTFIT) - self._decr_button.Bind(wx.EVT_BUTTON, on_decr) - self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) - - def Disable(self): self.Enable(False) - def Enable(self, enable=True): - if enable: - self._incr_button.Enable() - self._decr_button.Enable() - else: - self._incr_button.Disable() - self._decr_button.Disable() - -class ToggleButtonController(wx.Button): - def __init__(self, parent, controller, control_key, true_label, false_label): - self._controller = controller - self._control_key = control_key - wx.Button.__init__(self, parent, style=wx.BU_EXACTFIT) - self.Bind(wx.EVT_BUTTON, self._evt_button) - controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label)) - - def _evt_button(self, e): - self._controller[self._control_key] = not self._controller[self._control_key] - -class CheckBoxController(wx.CheckBox): - def __init__(self, parent, label, controller, control_key): - self._controller = controller - self._control_key = control_key - wx.CheckBox.__init__(self, parent, style=wx.CHK_2STATE, label=label) - self.Bind(wx.EVT_CHECKBOX, self._evt_checkbox) - controller.subscribe(control_key, lambda x: self.SetValue(bool(x))) - - def _evt_checkbox(self, e): - self._controller[self._control_key] = bool(e.IsChecked()) - -from gnuradio import eng_notation - -class TextBoxController(wx.TextCtrl): - def __init__(self, parent, controller, control_key, cast=float): - self._controller = controller - self._control_key = control_key - self._cast = cast - wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER) - self.Bind(wx.EVT_TEXT_ENTER, self._evt_enter) - controller.subscribe(control_key, lambda x: self.SetValue(eng_notation.num_to_str(x))) - - def _evt_enter(self, e): - try: self._controller[self._control_key] = self._cast(eng_notation.str_to_num(self.GetValue())) - except: self._controller[self._control_key] = self._controller[self._control_key] - -class LogSliderController(wx.BoxSizer): - """ - Log slider controller with display label and slider. - Gives logarithmic scaling to slider operation. - """ - def __init__(self, parent, prefix, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): - self._prefix = prefix - self._min_exp = min_exp - self._max_exp = max_exp - self._controller = controller - self._control_key = control_key - self._formatter = formatter - wx.BoxSizer.__init__(self, wx.VERTICAL) - self._label = wx.StaticText(parent, label=prefix + formatter(1/3.0)) - self.Add(self._label, 0, wx.EXPAND) - self._slider = wx.Slider(parent, minValue=0, maxValue=slider_steps, style=wx.SL_HORIZONTAL) - self.Add(self._slider, 0, wx.EXPAND) - self._slider.Bind(wx.EVT_SLIDER, self._on_slider_event) - controller.subscribe(control_key, self._on_controller_set) - - def _get_slope(self): - return float(self._max_exp-self._min_exp)/self._slider.GetMax() - - def _on_slider_event(self, e): - self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp) - - def _on_controller_set(self, value): - self._label.SetLabel(self._prefix + self._formatter(value)) - slider_value = (math.log10(value)-self._min_exp)/self._get_slope() - slider_value = min(max(self._slider.GetMin(), slider_value), self._slider.GetMax()) - if abs(slider_value - self._slider.GetValue()) > 1: - self._slider.SetValue(slider_value) - - def Disable(self): self.Enable(False) - def Enable(self, enable=True): - if enable: - self._slider.Enable() - self._label.Enable() - else: - self._slider.Disable() - self._label.Disable() - -class DropDownController(wx.Choice): - """ - Drop down controller with label and chooser. - Srop down selection from a set of choices. - """ - def __init__(self, parent, choices, controller, control_key, size=(-1, -1)): - """ - @param parent the parent window - @param choices a list of tuples -> (label, value) - @param controller the prop val controller - @param control_key the prop key for this control - """ - self._controller = controller - self._control_key = control_key - self._choices = choices - wx.Choice.__init__(self, parent, choices=[c[0] for c in choices], size=size) - self.Bind(wx.EVT_CHOICE, self._on_chooser_event) - controller.subscribe(control_key, self._on_controller_set) - - def _on_chooser_event(self, e): - self._controller[self._control_key] = self._choices[self.GetSelection()][1] - - def _on_controller_set(self, value): - #only set the chooser if the value is a possible choice - for i, choice in enumerate(self._choices): - if value == choice[1]: self.SetSelection(i) - ################################################## # Shared Functions ################################################## diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 8e0e61ac3..b128a4a98 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -63,34 +64,53 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - control_box = wx.BoxSizer(wx.VERTICAL) - self.marker_index = 2 - #begin control box - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + control_box = forms.static_box_sizer( + parent=self, label='Options', + bold=True, orient=wx.VERTICAL, + ) #alpha control_box.AddStretchSpacer() - alpha_slider = common.LogSliderController( - self, 'Alpha', - ALPHA_MIN_EXP, ALPHA_MAX_EXP, SLIDER_STEPS, - parent, ALPHA_KEY, + forms.text_box( + sizer=control_box, parent=self, label='Alpha', + converter=forms.float_converter(), + ps=parent, key=ALPHA_KEY, + ) + forms.log_slider( + sizer=control_box, parent=self, + min_exp=ALPHA_MIN_EXP, + max_exp=ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ALPHA_KEY, ) - control_box.Add(alpha_slider, 0, wx.EXPAND) #gain_mu control_box.AddStretchSpacer() - gain_mu_slider = common.LogSliderController( - self, 'Gain Mu', - GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP, SLIDER_STEPS, - parent, GAIN_MU_KEY, + forms.text_box( + sizer=control_box, parent=self, label='Gain Mu', + converter=forms.float_converter(), + ps=parent, key=GAIN_MU_KEY, + ) + forms.log_slider( + sizer=control_box, parent=self, + min_exp=GAIN_MU_MIN_EXP, + max_exp=GAIN_MU_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=GAIN_MU_KEY, ) - control_box.Add(gain_mu_slider, 0, wx.EXPAND) #marker control_box.AddStretchSpacer() - marker_chooser = common.DropDownController(self, MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(common.LabelBox(self, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + sizer=control_box, parent=self, + ps=parent, key=MARKER_KEY, label='Marker', + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + ) #run/stop control_box.AddStretchSpacer() - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -121,6 +141,11 @@ class const_window(wx.Panel, pubsub.pubsub): self.proxy(GAIN_OMEGA_KEY, controller, gain_omega_key) self.proxy(OMEGA_KEY, controller, omega_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[MARKER_KEY] = DEFAULT_MARKER_TYPE #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) @@ -141,13 +166,6 @@ class const_window(wx.Panel, pubsub.pubsub): self.subscribe(ALPHA_KEY, set_beta) def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 self.subscribe(GAIN_MU_KEY, set_gain_omega) - #initialize values - self[ALPHA_KEY] = self[ALPHA_KEY] - self[GAIN_MU_KEY] = self[GAIN_MU_KEY] - self[RUNNING_KEY] = True - self[X_DIVS_KEY] = 8 - self[Y_DIVS_KEY] = 8 - self[MARKER_KEY] = DEFAULT_MARKER_TYPE #register events self.subscribe(MSG_KEY, self.handle_msg) self.subscribe(X_DIVS_KEY, self.update_grid) diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index a4ccdca6d..5e1395701 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -66,3 +66,4 @@ MAXIMUM_KEY = 'maximum' MINIMUM_KEY = 'minimum' NUM_BINS_KEY = 'num_bins' FRAME_SIZE_KEY = 'frame_size' +CHANNEL_OPTIONS_KEY = 'channel_options' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index fdd5562dc..fded1a8fa 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -59,48 +60,66 @@ class control_panel(wx.Panel): self.parent = parent wx.Panel.__init__(self, parent, 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) - peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(peak_hold_check_box, 0, wx.EXPAND) - average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + #checkboxes for average and peak hold + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) + forms.check_box( + sizer=options_box, parent=self, label='Peak Hold', + ps=parent, key=PEAK_HOLD_KEY, + ) + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, ) - parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) - control_box.Add(avg_alpha_slider, 0, wx.EXPAND) + #static text and slider for averaging + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, + ) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) #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, label="%d dB/div"%y_per_div) - radio_button.Bind(wx.EVT_RADIOBUTTON, self._on_y_per_div) - self.radio_buttons.append(radio_button) - radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) - parent.subscribe(Y_PER_DIV_KEY, self._on_set_y_per_div) - control_box.Add(radio_box, 0, wx.EXPAND) + y_ctrl_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axis Options', + bold=True, orient=wx.VERTICAL, + ) + forms.radio_buttons( + sizer=y_ctrl_box, parent=self, + ps=parent, key=Y_PER_DIV_KEY, + style=wx.RA_VERTICAL|wx.NO_BORDER, choices=DIV_LEVELS, + labels=map(lambda x: '%s dB/div'%x, DIV_LEVELS), + ) #ref lvl buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - _ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(_ref_lvl_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=y_ctrl_box, label='Ref Level', + on_incr=self._on_incr_ref_level, on_decr=self._on_decr_ref_level, + ) + y_ctrl_box.AddSpacer(2) #autoscale - control_box.AddStretchSpacer() - autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(autoscale_button, 0, wx.EXPAND) + forms.single_button( + sizer=y_ctrl_box, parent=self, label='Autoscale', + callback=self.parent.autoscale, + ) #run/stop - run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) #mouse wheel event @@ -112,15 +131,6 @@ class control_panel(wx.Panel): ################################################## # Event handlers ################################################## - def _on_set_y_per_div(self, y_per_div): - try: - index = list(DIV_LEVELS).index(y_per_div) - self.radio_buttons[index].SetValue(True) - except: pass - def _on_y_per_div(self, event): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] - index = self.radio_buttons.index(selected_radio_button) - self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index] def _on_incr_ref_level(self, event): self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_ref_level(self, event): @@ -161,6 +171,14 @@ class fft_window(wx.Panel, pubsub.pubsub): self.proxy(AVERAGE_KEY, controller, average_key) self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[Y_PER_DIV_KEY] = y_per_div + self[Y_DIVS_KEY] = y_divs + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[RUNNING_KEY] = True #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) @@ -175,16 +193,6 @@ class fft_window(wx.Panel, pubsub.pubsub): main_box.Add(self.plotter, 1, wx.EXPAND) main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] - self[PEAK_HOLD_KEY] = peak_hold - self[Y_PER_DIV_KEY] = y_per_div - self[Y_DIVS_KEY] = y_divs - self[X_DIVS_KEY] = 8 #approximate - self[REF_LEVEL_KEY] = ref_level - self[BASEBAND_FREQ_KEY] = baseband_freq - self[RUNNING_KEY] = True #register events self.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) self.subscribe(MSG_KEY, self.handle_msg) diff --git a/gr-wxgui/src/python/forms/__init__.py b/gr-wxgui/src/python/forms/__init__.py new file mode 100644 index 000000000..3f9f4c735 --- /dev/null +++ b/gr-wxgui/src/python/forms/__init__.py @@ -0,0 +1,103 @@ +# +# 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. +# + +""" +The following classes will be available through gnuradio.wxgui.forms: +""" + +######################################################################## +# External Converters +######################################################################## +from converters import \ + eval_converter, str_converter, \ + float_converter, int_converter + +######################################################################## +# External Forms +######################################################################## +from forms import \ + radio_buttons, drop_down, notebook, \ + button, toggle_button, single_button, \ + check_box, text_box, static_text, \ + slider, log_slider, gauge, \ + make_bold, DataEvent, EVT_DATA + +######################################################################## +# Helpful widgets +######################################################################## +import wx + +class static_box_sizer(wx.StaticBoxSizer): + """ + A box sizer with label and border. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param label title label for this widget (optional) + @param bold true to boldify the label + @param orient the sizer orientation wx.VERTICAL or wx.HORIZONTAL (default=wx.VERTICAL) + """ + def __init__(self, parent, label='', bold=False, sizer=None, orient=wx.VERTICAL, proportion=0, flag=wx.EXPAND): + box = wx.StaticBox(parent=parent, label=label) + if bold: make_bold(box) + wx.StaticBoxSizer.__init__(self, box=box, orient=orient) + if sizer: sizer.Add(self, proportion, flag) + +class incr_decr_buttons(wx.BoxSizer): + """ + A horizontal box sizer with a increment and a decrement button. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param label title label for this widget (optional) + @param on_incr the callback for pressing the + button + @param on_decr the callback for pressing the - button + """ + def __init__(self, parent, on_incr, on_decr, label='', sizer=None, proportion=0, flag=wx.EXPAND): + """ + @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) + buttons_box = wx.BoxSizer(wx.HORIZONTAL) + self._incr_button = wx.Button(parent, label='+', style=wx.BU_EXACTFIT) + self._incr_button.Bind(wx.EVT_BUTTON, on_incr) + buttons_box.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL) + self._decr_button = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT) + self._decr_button.Bind(wx.EVT_BUTTON, on_decr) + buttons_box.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) + if label: #add label + self.Add(wx.StaticText(parent, label='%s: '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(buttons_box, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + else: self.Add(buttons_box, 0, wx.ALIGN_CENTER_VERTICAL) + if sizer: sizer.Add(self, proportion, flag) + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + self._incr_button.Enable() + self._decr_button.Enable() + else: + self._incr_button.Disable() + self._decr_button.Disable() diff --git a/gr-wxgui/src/python/forms/converters.py b/gr-wxgui/src/python/forms/converters.py new file mode 100644 index 000000000..123aefeb0 --- /dev/null +++ b/gr-wxgui/src/python/forms/converters.py @@ -0,0 +1,145 @@ +# +# 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. +# + +from gnuradio import eng_notation +import math + +class abstract_converter(object): + def external_to_internal(self, v): + """ + Convert from user specified value to value acceptable to underlying primitive. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def internal_to_external(self, s): + """ + Convert from underlying primitive value to user specified value. + The underlying primitive usually expects strings. + """ + raise NotImplementedError + def help(self): + return "Any string is acceptable" + +class identity_converter(abstract_converter): + def external_to_internal(self,v): + return v + def internal_to_external(self, s): + return s + +######################################################################## +# Commonly used converters +######################################################################## +class chooser_converter(abstract_converter): + """ + Convert between a set of possible choices and an index. + Used in the chooser base and all sub-classes. + """ + def __init__(self, choices): + self._choices = choices + def external_to_internal(self, choice): + return self._choices.index(choice) + def internal_to_external(self, index): + return self._choices[index] + def help(self): + return 'Enter a possible value in choices: "%s"'%str(self._choices) + +class bool_converter(abstract_converter): + """ + The internal representation is boolean. + The external representation is specified. + Used in the check box form. + """ + def __init__(self, true, false): + self._true = true + self._false = false + def external_to_internal(self, v): + return bool(v) + def internal_to_external(self, v): + if v: return self._true + else: return self._false + def help(self): + return "Value must be cast-able to type bool." + +class eval_converter(abstract_converter): + """ + A catchall converter when int and float are not enough. + Evaluate the internal representation with python's eval(). + Possible uses, set a complex number, constellation points. + Used in text box. + """ + def external_to_internal(self, s): + return str(s) + def internal_to_external(self, s): + return eval(s) + def help(self): + return "Value must be evaluatable by python's eval." + +class str_converter(abstract_converter): + def external_to_internal(self, v): + return str(v) + def internal_to_external(self, s): + return str(s) + +class int_converter(abstract_converter): + def external_to_internal(self, v): + return str(int(round(v))) + def internal_to_external(self, s): + return int(s, 0) + def help(self): + return "Enter an integer. Leading 0x indicates hex" + +class float_converter(abstract_converter): + def __init__(self, formatter=eng_notation.num_to_str): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) + def internal_to_external(self, s): + return eng_notation.str_to_num(s) + def help(self): + return "Enter a float with optional scale suffix. E.g., 100.1M" + +class slider_converter(abstract_converter): + """ + Scale values to and from the slider. + """ + def __init__(self, minimum, maximum, num_steps, cast): + assert minimum < maximum + assert num_steps > 0 + self._offset = minimum + self._scaler = float(maximum - minimum)/num_steps + self._cast = cast + def external_to_internal(self, v): + return (v - self._offset)/self._scaler + def internal_to_external(self, v): + return self._cast(v*self._scaler + self._offset) + def help(self): + return "Value should be within slider range" + +class log_slider_converter(slider_converter): + def __init__(self, min_exp, max_exp, num_steps, base): + assert min_exp < max_exp + assert num_steps > 0 + self._base = base + slider_converter.__init__(self, minimum=min_exp, maximum=max_exp, num_steps=num_steps, cast=float) + def external_to_internal(self, v): + return slider_converter.external_to_internal(self, math.log(v, self._base)) + def internal_to_external(self, v): + return self._base**slider_converter.internal_to_external(self, v) diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py new file mode 100644 index 000000000..10f6a4823 --- /dev/null +++ b/gr-wxgui/src/python/forms/forms.py @@ -0,0 +1,641 @@ +# +# 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. +# + +""" +The forms module contains general purpose wx-gui forms for gnuradio apps. + +The forms follow a layered model: + * internal layer + * deals with the wxgui objects directly + * implemented in event handler and update methods + * translation layer + * translates the between the external and internal layers + * handles parsing errors between layers + * external layer + * provided external access to the user + * set_value, get_value, and optional callback + * set and get through optional pubsub and key + +Known problems: + * An empty label in the radio box still consumes space. + * The static text cannot resize the parent at runtime. +""" + +EXT_KEY = 'external' +INT_KEY = 'internal' + +import wx +import sys +from gnuradio.gr.pubsub import pubsub +import converters + +EVT_DATA = wx.PyEventBinder(wx.NewEventType()) +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) + self.data = data + +def make_bold(widget): + font = widget.GetFont() + font.SetWeight(wx.FONTWEIGHT_BOLD) + widget.SetFont(font) + +######################################################################## +# Base Class Form +######################################################################## +class _form_base(pubsub, wx.BoxSizer): + def __init__(self, parent=None, sizer=None, proportion=0, flag=wx.EXPAND, ps=None, key='', value=None, callback=None, converter=converters.identity_converter()): + pubsub.__init__(self) + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + self._parent = parent + self._key = key + self._converter = converter + self._callback = callback + self._widgets = list() + #add to the sizer if provided + if sizer: sizer.Add(self, proportion, flag) + #proxy the pubsub and key into this form + if ps is not None: + assert key + self.proxy(EXT_KEY, ps, key) + #no pubsub passed, must set initial value + else: self.set_value(value) + + def __str__(self): + return "Form: %s -> %s"%(self.__class__, self._key) + + def _add_widget(self, widget, label='', flag=0, label_prop=0, widget_prop=1): + """ + Add the main widget to this object sizer. + If label is passed, add a label as well. + Register the widget and the label in the widgets list (for enable/disable). + Bind the update handler to the widget for data events. + This ensures that the gui thread handles updating widgets. + Setup the pusub triggers for external and internal. + @param widget the main widget + @param label the optional label + @param flag additional flags for widget + @param label_prop the proportion for the label + @param widget_prop the proportion for the widget + """ + #setup data event + widget.Bind(EVT_DATA, lambda x: self._update(x.data)) + update = lambda x: wx.PostEvent(widget, DataEvent(x)) + #register widget + self._widgets.append(widget) + #create optional label + if not label: self.Add(widget, widget_prop, wx.ALIGN_CENTER_VERTICAL | flag) + else: + label_text = wx.StaticText(self._parent, label='%s: '%label) + self._widgets.append(label_text) + self.Add(label_text, label_prop, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(widget, widget_prop, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | flag) + #initialize without triggering pubsubs + self._translate_external_to_internal(self[EXT_KEY]) + update(self[INT_KEY]) + #subscribe all the functions + self.subscribe(INT_KEY, update) + self.subscribe(INT_KEY, self._translate_internal_to_external) + self.subscribe(EXT_KEY, self._translate_external_to_internal) + if self._callback: self.subscribe(EXT_KEY, self._callback) + + def _translate_external_to_internal(self, external): + try: + internal = self._converter.external_to_internal(external) + #prevent infinite loop between internal and external pubsub keys by only setting if changed + if self[INT_KEY] != internal: self[INT_KEY] = internal + except Exception, e: + self._err_msg(external, e) + self[INT_KEY] = self[INT_KEY] #reset to last good setting + + def _translate_internal_to_external(self, internal): + try: + external = self._converter.internal_to_external(internal) + #prevent infinite loop between internal and external pubsub keys by only setting if changed + if self[EXT_KEY] != external: self[EXT_KEY] = external + except Exception, e: + self._err_msg(internal, e) + self[EXT_KEY] = self[EXT_KEY] #reset to last good setting + + def _err_msg(self, value, e): + print >> sys.stderr, self, 'Error translating value: "%s"\n\t%s\n\t%s'%(value, e, self._converter.help()) + + #override in subclasses to handle the wxgui object + def _update(self, value): raise NotImplementedError + def _handle(self, event): raise NotImplementedError + + #provide a set/get interface for this form + def get_value(self): return self[EXT_KEY] + def set_value(self, value): self[EXT_KEY] = value + + def Disable(self, disable=True): self.Enable(not disable) + def Enable(self, enable=True): + if enable: + for widget in self._widgets: widget.Enable() + else: + for widget in self._widgets: widget.Disable() + +######################################################################## +# Base Class Chooser Form +######################################################################## +class _chooser_base(_form_base): + def __init__(self, choices=[], labels=None, **kwargs): + _form_base.__init__(self, converter=converters.chooser_converter(choices), **kwargs) + self._choices = choices + self._labels = map(str, labels or choices) + +######################################################################## +# Base Class Slider Form +######################################################################## +class _slider_base(_form_base): + def __init__(self, label='', length=-1, converter=None, num_steps=100, style=wx.SL_HORIZONTAL, **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + if style & wx.SL_HORIZONTAL: slider_size = wx.Size(length, -1) + elif style & wx.SL_VERTICAL: slider_size = wx.Size(-1, length) + else: raise NotImplementedError + self._slider = wx.Slider(self._parent, minValue=0, maxValue=num_steps, size=slider_size, style=style) + self._slider.Bind(wx.EVT_SCROLL, self._handle) + self._add_widget(self._slider, label, flag=wx.EXPAND) + + def _handle(self, event): self[INT_KEY] = self._slider.GetValue() + def _update(self, value): self._slider.SetValue(value) + +######################################################################## +# Static Text Form +######################################################################## +class static_text(_form_base): + """ + A text box form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param width the width of the form in px + @param bold true to bold-ify the text (default=False) + @param converter forms.str_converter(), int_converter(), float_converter()... + """ + def __init__(self, label='', width=-1, bold=False, converter=converters.str_converter(), **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + self._static_text = wx.StaticText(self._parent, size=wx.Size(width, -1)) + if bold: make_bold(self._static_text) + self._add_widget(self._static_text, label) + + def _update(self, label): self._static_text.SetLabel(label); self._parent.Layout() + +######################################################################## +# Text Box Form +######################################################################## +class text_box(_form_base): + """ + A text box form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param width the width of the form in px + @param converter forms.str_converter(), int_converter(), float_converter()... + """ + def __init__(self, label='', width=-1, converter=converters.eval_converter(), **kwargs): + _form_base.__init__(self, converter=converter, **kwargs) + self._text_box = wx.TextCtrl(self._parent, size=wx.Size(width, -1), style=wx.TE_PROCESS_ENTER) + self._text_box.Bind(wx.EVT_TEXT_ENTER, self._handle) + self._add_widget(self._text_box, label) + + def _handle(self, event): self[INT_KEY] = self._text_box.GetValue() + def _update(self, value): self._text_box.SetValue(value) + +######################################################################## +# Slider Form +# Linear Slider +# Logarithmic Slider +######################################################################## +class slider(_slider_base): + """ + A generic linear slider. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) + @param minimum the minimum value + @param maximum the maximum value + @param num_steps the number of slider steps (or specify step_size) + @param step_size the step between slider jumps (or specify num_steps) + @param cast a cast function, int, or float (default=float) + """ + def __init__(self, minimum=-100, maximum=100, num_steps=100, step_size=None, cast=float, **kwargs): + assert step_size or num_steps + if step_size is not None: num_steps = (maximum - minimum)/step_size + converter = converters.slider_converter(minimum=minimum, maximum=maximum, num_steps=num_steps, cast=cast) + _slider_base.__init__(self, converter=converter, num_steps=num_steps, **kwargs) + +class log_slider(_slider_base): + """ + A generic logarithmic slider. + The sliders min and max values are base**min_exp and base**max_exp. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.SL_HORIZONTAL or wx.SL_VERTICAL (default=horizontal) + @param min_exp the minimum exponent + @param max_exp the maximum exponent + @param base the exponent base in base**exp + @param num_steps the number of slider steps (or specify step_size) + @param step_size the exponent step size (or specify num_steps) + """ + def __init__(self, min_exp=0, max_exp=1, base=10, num_steps=100, step_size=None, **kwargs): + assert step_size or num_steps + if step_size is not None: num_steps = (max_exp - min_exp)/step_size + converter = converters.log_slider_converter(min_exp=min_exp, max_exp=max_exp, num_steps=num_steps, base=base) + _slider_base.__init__(self, converter=converter, num_steps=num_steps, **kwargs) + +######################################################################## +# Gauge Form +######################################################################## +class gauge(_form_base): + """ + A gauge bar. + The gauge displays floating point values between the minimum and maximum. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param label title label for this widget (optional) + @param length the length of the slider in px (optional) + @param style wx.GA_HORIZONTAL or wx.GA_VERTICAL (default=horizontal) + @param minimum the minimum value + @param maximum the maximum value + @param num_steps the number of slider steps (or specify step_size) + @param step_size the step between slider jumps (or specify num_steps) + """ + def __init__(self, label='', length=-1, minimum=-100, maximum=100, num_steps=100, step_size=None, style=wx.GA_HORIZONTAL, **kwargs): + assert step_size or num_steps + if step_size is not None: num_steps = (maximum - minimum)/step_size + converter = converters.slider_converter(minimum=minimum, maximum=maximum, num_steps=num_steps, cast=float) + _form_base.__init__(self, converter=converter, **kwargs) + if style & wx.SL_HORIZONTAL: gauge_size = wx.Size(length, -1) + elif style & wx.SL_VERTICAL: gauge_size = wx.Size(-1, length) + else: raise NotImplementedError + self._gauge = wx.Gauge(self._parent, range=num_steps, size=gauge_size, style=style) + self._add_widget(self._gauge, label, flag=wx.EXPAND) + + def _update(self, value): self._gauge.SetValue(value) + +######################################################################## +# Check Box Form +######################################################################## +class check_box(_form_base): + """ + Create a check box form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param true the value for form when checked (default=True) + @param false the value for form when unchecked (default=False) + @param label title label for this widget (optional) + """ + def __init__(self, label='', true=True, false=False, **kwargs): + _form_base.__init__(self, converter=converters.bool_converter(true=true, false=false), **kwargs) + self._check_box = wx.CheckBox(self._parent, style=wx.CHK_2STATE, label=label) + self._check_box.Bind(wx.EVT_CHECKBOX, self._handle) + self._add_widget(self._check_box) + + def _handle(self, event): self[INT_KEY] = self._check_box.IsChecked() + def _update(self, checked): self._check_box.SetValue(checked) + +######################################################################## +# Drop Down Chooser Form +######################################################################## +class drop_down(_chooser_base): + """ + Create a drop down menu form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param choices list of possible values + @param labels list of labels for each choice (default=choices) + @param label title label for this widget (optional) + @param width the form width in px (optional) + """ + def __init__(self, label='', width=-1, **kwargs): + _chooser_base.__init__(self, **kwargs) + self._drop_down = wx.Choice(self._parent, choices=self._labels, size=wx.Size(width, -1)) + self._drop_down.Bind(wx.EVT_CHOICE, self._handle) + self._add_widget(self._drop_down, label, widget_prop=0, label_prop=1) + + def _handle(self, event): self[INT_KEY] = self._drop_down.GetSelection() + def _update(self, i): self._drop_down.SetSelection(i) + +######################################################################## +# Button Chooser Form +# Circularly move through the choices with each click. +# Can be a single-click button with one choice. +# Can be a 2-state button with two choices. +######################################################################## +class button(_chooser_base): + """ + Create a multi-state button. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param choices list of possible values + @param labels list of labels for each choice (default=choices) + @param width the width of the button in pixels (optional) + @param style style arguments (optional) + @param label title label for this widget (optional) + """ + def __init__(self, label='', style=0, width=-1, **kwargs): + _chooser_base.__init__(self, **kwargs) + self._button = wx.Button(self._parent, size=wx.Size(width, -1), style=style) + self._button.Bind(wx.EVT_BUTTON, self._handle) + self._add_widget(self._button, label, widget_prop=((not style&wx.BU_EXACTFIT) and 1 or 0)) + + def _handle(self, event): self[INT_KEY] = (self[INT_KEY] + 1)%len(self._choices) #circularly increment index + def _update(self, i): self._button.SetLabel(self._labels[i]); self.Layout() + +class toggle_button(button): + """ + Create a dual-state button. + This button will alternate between True and False when clicked. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param width the width of the button in pixels (optional) + @param style style arguments (optional) + @param true_label the button's label in the true state + @param false_label the button's label in the false state + """ + def __init__(self, true_label='On (click to stop)', false_label='Off (click to start)', **kwargs): + button.__init__(self, choices=[True, False], labels=[true_label, false_label], **kwargs) + +class single_button(toggle_button): + """ + Create a single state button. + This button will callback() when clicked. + For use when state holding is not important. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param width the width of the button in pixels (optional) + @param style style arguments (optional) + @param label the button's label + """ + def __init__(self, label='click for callback', **kwargs): + toggle_button.__init__(self, true_label=label, false_label=label, value=True, **kwargs) + +######################################################################## +# Radio Buttons Chooser Form +######################################################################## +class radio_buttons(_chooser_base): + """ + Create a radio button form. + @param parent the parent widget + @param sizer add this widget to sizer if provided (optional) + @param proportion the proportion when added to the sizer (default=0) + @param flag the flag argument when added to the sizer (default=wx.EXPAND) + @param ps the pubsub object (optional) + @param key the pubsub key (optional) + @param value the default value (optional) + @param choices list of possible values + @param labels list of labels for each choice (default=choices) + @param major_dimension the number of rows/cols (default=auto) + @param label title label for this widget (optional) + @param style useful style args: wx.RA_HORIZONTAL, wx.RA_VERTICAL, wx.NO_BORDER (default=wx.RA_HORIZONTAL) + """ + def __init__(self, style=wx.RA_HORIZONTAL, label='', major_dimension=0, **kwargs): + _chooser_base.__init__(self, **kwargs) + #create radio buttons + self._radio_buttons = wx.RadioBox(self._parent, choices=self._labels, style=style, label=label, majorDimension=major_dimension) + self._radio_buttons.Bind(wx.EVT_RADIOBOX, self._handle) + self._add_widget(self._radio_buttons) + + def _handle(self, event): self[INT_KEY] = self._radio_buttons.GetSelection() + def _update(self, i): self._radio_buttons.SetSelection(i) + +######################################################################## +# Notebook Chooser Form +# The notebook pages/tabs are for selecting between choices. +# A page must be added to the notebook for each choice. +######################################################################## +class notebook(_chooser_base): + def __init__(self, pages, notebook, **kwargs): + _chooser_base.__init__(self, **kwargs) + assert len(pages) == len(self._choices) + self._notebook = notebook + self._notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self._handle) + #add pages, setting the label on each tab + for i, page in enumerate(pages): + self._notebook.AddPage(page, self._labels[i]) + self._add_widget(self._notebook) + + def _handle(self, event): self[INT_KEY] = self._notebook.GetSelection() + def _update(self, i): self._notebook.SetSelection(i) + +# ---------------------------------------------------------------- +# Stand-alone test application +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import gui + +class app_gui (object): + def __init__(self, frame, panel, vbox, top_block, options, args): + + def callback(v): print v + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_HORIZONTAL, + label='test radio long string', + callback=callback, + #major_dimension = 2, + ) + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_VERTICAL, + label='test radio long string', + callback=callback, + #major_dimension = 2, + ) + + radio_buttons( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=4, + style=wx.RA_VERTICAL | wx.NO_BORDER, + callback=callback, + #major_dimension = 2, + ) + + button( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + labels=['two', 'four', 'eight', 'sixteen'], + value=2, + label='button value', + callback=callback, + #width=100, + ) + + + drop_down( + sizer=vbox, + parent=panel, + choices=[2, 4, 8, 16], + value=2, + label='Choose One', + callback=callback, + ) + check_box( + sizer=vbox, + parent=panel, + value=False, + label='check me', + callback=callback, + ) + text_box( + sizer=vbox, + parent=panel, + value=3, + label='text box', + callback=callback, + width=200, + ) + + static_text( + sizer=vbox, + parent=panel, + value='bob', + label='static text', + width=-1, + bold=True, + ) + + slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + ) + + log_slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + ) + + slider( + sizer=vbox, + parent=panel, + value=12, + label='slider', + callback=callback, + style=wx.SL_VERTICAL, + length=30, + ) + + toggle_button( + sizer=vbox, + parent=panel, + value=True, + label='toggle it', + callback=callback, + ) + + single_button( + sizer=vbox, + parent=panel, + label='sig test', + callback=callback, + ) + +if __name__ == "__main__": + try: + + # Create the GUI application + app = gui.app( + gui=app_gui, # User interface class + title="Test Forms", # Top window title + ) + + # And run it + app.MainLoop() + + except RuntimeError, e: + print e + sys.exit(1) diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py index dce52ff9b..5f434d70e 100644 --- a/gr-wxgui/src/python/histo_window.py +++ b/gr-wxgui/src/python/histo_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -53,23 +54,31 @@ class control_panel(wx.Panel): wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) SIZE = (100, -1) - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - control_box.AddStretchSpacer() + control_box = forms.static_box_sizer( + parent=self, label='Options', + bold=True, orient=wx.VERTICAL, + ) #num bins - def num_bins_cast(num): - num = int(num) - assert num > 1 - return num - num_bins_ctrl = common.TextBoxController(self, parent, NUM_BINS_KEY, cast=num_bins_cast) - control_box.Add(common.LabelBox(self, ' Num Bins ', num_bins_ctrl), 0, wx.EXPAND) control_box.AddStretchSpacer() + forms.text_box( + sizer=control_box, parent=self, label='Num Bins', + converter=forms.int_converter(), + ps=parent, key=NUM_BINS_KEY, + ) #frame size - frame_size_ctrl = common.TextBoxController(self, parent, FRAME_SIZE_KEY, cast=num_bins_cast) - control_box.Add(common.LabelBox(self, ' Frame Size ', frame_size_ctrl), 0, wx.EXPAND) control_box.AddStretchSpacer() + forms.text_box( + sizer=control_box, parent=self, label='Frame Size', + converter=forms.int_converter(), + ps=parent, key=FRAME_SIZE_KEY, + ) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -98,6 +107,10 @@ class histo_window(wx.Panel, pubsub.pubsub): self.proxy(NUM_BINS_KEY, controller, num_bins_key) self.proxy(FRAME_SIZE_KEY, controller, frame_size_key) self.proxy(MSG_KEY, controller, msg_key) + #initialize values + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 4 #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.bar_plotter(self) @@ -111,12 +124,6 @@ class histo_window(wx.Panel, pubsub.pubsub): main_box.Add(self.plotter, 1, wx.EXPAND) main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[NUM_BINS_KEY] = self[NUM_BINS_KEY] - self[FRAME_SIZE_KEY] = self[FRAME_SIZE_KEY] - self[RUNNING_KEY] = True - self[X_DIVS_KEY] = 8 - self[Y_DIVS_KEY] = 4 #register events self.subscribe(MSG_KEY, self.handle_msg) self.subscribe(X_DIVS_KEY, self.update_grid) diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index f12a18248..8a8249764 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -28,6 +28,7 @@ import wx import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -38,6 +39,9 @@ AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_NUMBER_RATE = gr.prefs().get_long('wxgui', 'number_rate', 5) DEFAULT_WIN_SIZE = (300, 300) DEFAULT_GAUGE_RANGE = 1000 +VALUE_REPR_KEY = 'value_repr' +VALUE_REAL_KEY = 'value_real' +VALUE_IMAG_KEY = 'value_imag' ################################################## # Number window control panel @@ -53,28 +57,45 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent) 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.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) - self.average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(self.average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) + forms.check_box( + sizer=options_box, parent=self, label='Peak Hold', + ps=parent, key=PEAK_HOLD_KEY, + ) + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, + ) + #static text and slider for averaging + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, ) - parent.subscribe(AVERAGE_KEY, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) #run/stop control_box.AddStretchSpacer() - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -107,38 +128,47 @@ class number_window(wx.Panel, pubsub.pubsub): self.peak_val_imag = NEG_INF self.real = real self.units = units - self.minval = minval - self.maxval = maxval self.decimal_places = decimal_places #proxy the keys self.proxy(MSG_KEY, controller, msg_key) self.proxy(AVERAGE_KEY, controller, average_key) self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[RUNNING_KEY] = True + self[VALUE_REAL_KEY] = minval + self[VALUE_IMAG_KEY] = minval #setup the box with display and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) - sizer = wx.BoxSizer(wx.VERTICAL) - main_box.Add(sizer, 1, wx.EXPAND) + sizer = forms.static_box_sizer( + parent=self, sizer=main_box, label=title, + bold=True, orient=wx.VERTICAL, proportion=1, + ) main_box.Add(self.control_panel, 0, wx.EXPAND) - sizer.Add(common.LabelText(self, title), 1, wx.ALIGN_CENTER) - self.text = wx.StaticText(self, size=(size[0], -1)) - sizer.Add(self.text, 1, wx.EXPAND) - self.gauge_real = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) - self.gauge_imag = wx.Gauge(self, range=DEFAULT_GAUGE_RANGE, style=wx.GA_HORIZONTAL) + sizer.AddStretchSpacer() + forms.static_text( + parent=self, sizer=sizer, + ps=self, key=VALUE_REPR_KEY, width=size[0], + converter=forms.str_converter(), + ) + sizer.AddStretchSpacer() + self.gauge_real = forms.gauge( + parent=self, sizer=sizer, style=wx.GA_HORIZONTAL, + ps=self, key=VALUE_REAL_KEY, length=size[0], + minimum=minval, maximum=maxval, num_steps=DEFAULT_GAUGE_RANGE, + ) + self.gauge_imag = forms.gauge( + parent=self, sizer=sizer, style=wx.GA_HORIZONTAL, + ps=self, key=VALUE_IMAG_KEY, length=size[0], + minimum=minval, maximum=maxval, num_steps=DEFAULT_GAUGE_RANGE, + ) #hide/show gauges self.show_gauges(show_gauge) - sizer.Add(self.gauge_real, 1, wx.EXPAND) - sizer.Add(self.gauge_imag, 1, wx.EXPAND) self.SetSizerAndFit(main_box) - #initialize values - self[PEAK_HOLD_KEY] = peak_hold - self[RUNNING_KEY] = True - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] #register events self.subscribe(MSG_KEY, self.handle_msg) - self.Bind(common.EVT_DATA, self.update) def show_gauges(self, show_gauge): """ @@ -146,20 +176,10 @@ class number_window(wx.Panel, pubsub.pubsub): If this is real, never show the imaginary gauge. @param show_gauge true to show """ - if show_gauge: self.gauge_real.Show() - else: self.gauge_real.Hide() - if show_gauge and not self.real: self.gauge_imag.Show() - else: self.gauge_imag.Hide() + self.gauge_real.ShowItems(show_gauge) + self.gauge_imag.ShowItems(show_gauge and not self.real) def handle_msg(self, msg): - """ - Post this message into a data event. - Allow wx to handle the event to avoid threading issues. - @param msg the incoming numbersink data - """ - wx.PostEvent(self, common.DataEvent(msg)) - - def update(self, event): """ Handle a message from the message queue. Convert the string based message into a float or complex. @@ -168,29 +188,23 @@ class number_window(wx.Panel, pubsub.pubsub): @param event event.data is the number sample as a character array """ if not self[RUNNING_KEY]: return - #set gauge - def set_gauge_value(gauge, value): - gauge_val = DEFAULT_GAUGE_RANGE*(value-self.minval)/(self.maxval-self.minval) - gauge_val = max(0, gauge_val) #clip - gauge_val = min(DEFAULT_GAUGE_RANGE, gauge_val) #clip - gauge.SetValue(gauge_val) format_string = "%%.%df"%self.decimal_places if self.real: - sample = numpy.fromstring(event.data, numpy.float32)[-1] + sample = numpy.fromstring(msg, numpy.float32)[-1] if self[PEAK_HOLD_KEY]: sample = self.peak_val_real = max(self.peak_val_real, sample) label_text = "%s %s"%(format_string%sample, self.units) - set_gauge_value(self.gauge_real, sample) + self[VALUE_REAL_KEY] = sample else: - sample = numpy.fromstring(event.data, numpy.complex64)[-1] + sample = numpy.fromstring(msg, numpy.complex64)[-1] if self[PEAK_HOLD_KEY]: self.peak_val_real = max(self.peak_val_real, sample.real) self.peak_val_imag = max(self.peak_val_imag, sample.imag) sample = self.peak_val_real + self.peak_val_imag*1j label_text = "%s + %sj %s"%(format_string%sample.real, format_string%sample.imag, self.units) - set_gauge_value(self.gauge_real, sample.real) - set_gauge_value(self.gauge_imag, sample.imag) + self[VALUE_REAL_KEY] = sample.real + self[VALUE_IMAG_KEY] = sample.imag #set label text - self.text.SetLabel(label_text) + self[VALUE_REPR_KEY] = label_text #clear peak hold if not self[PEAK_HOLD_KEY]: self.peak_val_real = NEG_INF diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index bbc66426a..449046402 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -30,6 +30,7 @@ import time import pubsub from constants import * from gnuradio import gr #for gr.prefs, trigger modes +import forms ################################################## # Constants @@ -42,12 +43,12 @@ COUPLING_MODES = ( ) TRIGGER_MODES = ( ('Freerun', gr.gr_TRIG_MODE_FREE), - ('Automatic', gr.gr_TRIG_MODE_AUTO), + ('Auto', gr.gr_TRIG_MODE_AUTO), ('Normal', gr.gr_TRIG_MODE_NORM), ) TRIGGER_SLOPES = ( - ('Positive +', gr.gr_TRIG_SLOPE_POS), - ('Negative -', gr.gr_TRIG_SLOPE_NEG), + ('Pos +', gr.gr_TRIG_SLOPE_POS), + ('Neg -', gr.gr_TRIG_SLOPE_NEG), ) CHANNEL_COLOR_SPECS = ( (0.3, 0.3, 1.0), @@ -78,7 +79,7 @@ class control_panel(wx.Panel): Create a new control panel. @param parent the wx parent window """ - SIZE = (100, -1) + WIDTH = 90 self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) @@ -86,161 +87,238 @@ class control_panel(wx.Panel): # Axes Options ################################################## control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) + axes_options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axes Options', + bold=True, orient=wx.VERTICAL, + ) ################################################## # Scope Mode Box ################################################## scope_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(scope_mode_box, 0, wx.EXPAND) + axes_options_box.Add(scope_mode_box, 0, wx.EXPAND) #x axis divs - x_buttons_scope = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) - scope_mode_box.Add(common.LabelBox(self, 'Secs/Div', x_buttons_scope), 0, wx.EXPAND) + forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Secs/Div', + on_incr=self._on_incr_t_divs, on_decr=self._on_decr_t_divs, + ) #y axis divs - y_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons_scope.Enable(not x)) - scope_mode_box.Add(common.LabelBox(self, 'Counts/Div', y_buttons_scope), 0, wx.EXPAND) + y_buttons_scope = forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Counts/Div', + on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs, + ) #y axis ref lvl - y_off_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons_scope.Enable(not x)) - scope_mode_box.Add(common.LabelBox(self, 'Y Offset', y_off_buttons_scope), 0, wx.EXPAND) + y_off_buttons_scope = forms.incr_decr_buttons( + parent=self, sizer=scope_mode_box, label='Y Offset', + on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off, + ) #t axis ref lvl scope_mode_box.AddSpacer(5) - t_off_slider = wx.Slider(self, size=SIZE, style=wx.SL_HORIZONTAL) - t_off_slider.SetRange(0, 1000) - def t_off_slider_changed(evt): parent[T_FRAC_OFF_KEY] = float(t_off_slider.GetValue())/t_off_slider.GetMax() - t_off_slider.Bind(wx.EVT_SLIDER, t_off_slider_changed) - parent.subscribe(T_FRAC_OFF_KEY, lambda x: t_off_slider.SetValue(int(round(x*t_off_slider.GetMax())))) - scope_mode_box.Add(common.LabelBox(self, 'T Offset', t_off_slider), 0, wx.EXPAND) + forms.slider( + parent=self, sizer=scope_mode_box, + ps=parent, key=T_FRAC_OFF_KEY, label='T Offset', + minimum=0, maximum=1, num_steps=1000, + ) scope_mode_box.AddSpacer(5) ################################################## # XY Mode Box ################################################## xy_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(xy_mode_box, 0, wx.EXPAND) + axes_options_box.Add(xy_mode_box, 0, wx.EXPAND) #x div controls - x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: x_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'X/Div', x_buttons), 0, wx.EXPAND) + x_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='X/Div', + on_incr=self._on_incr_x_divs, on_decr=self._on_decr_x_divs, + ) #y div controls - y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'Y/Div', y_buttons), 0, wx.EXPAND) + y_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='Y/Div', + on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs, + ) #x offset controls - x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off) - parent.subscribe(AUTORANGE_KEY, lambda x: x_off_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'X Off', x_off_buttons), 0, wx.EXPAND) + x_off_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='X Off', + on_incr=self._on_incr_x_off, on_decr=self._on_decr_x_off, + ) #y offset controls - y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons.Enable(not x)) - xy_mode_box.Add(common.LabelBox(self, 'Y Off', y_off_buttons), 0, wx.EXPAND) + y_off_buttons = forms.incr_decr_buttons( + parent=self, sizer=xy_mode_box, label='Y Off', + on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off, + ) + for widget in (y_buttons_scope, y_off_buttons_scope, x_buttons, y_buttons, x_off_buttons, y_off_buttons): + parent.subscribe(AUTORANGE_KEY, widget.Disable) + widget.Disable(parent[AUTORANGE_KEY]) xy_mode_box.ShowItems(False) #autorange check box - self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) - control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) - control_box.AddStretchSpacer() + forms.check_box( + parent=self, sizer=axes_options_box, label='Autorange', + ps=parent, key=AUTORANGE_KEY, + ) ################################################## # Channel Options ################################################## TRIGGER_PAGE_INDEX = parent.num_inputs XY_PAGE_INDEX = parent.num_inputs+1 - control_box.Add(common.LabelText(self, 'Channel Options'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) + control_box.AddStretchSpacer() + chan_options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Channel Options', + bold=True, orient=wx.VERTICAL, + ) options_notebook = wx.Notebook(self) - control_box.Add(options_notebook, 0, wx.EXPAND) - def options_notebook_changed(evt): - try: - parent[TRIGGER_SHOW_KEY] = options_notebook.GetSelection() == TRIGGER_PAGE_INDEX - parent[XY_MODE_KEY] = options_notebook.GetSelection() == XY_PAGE_INDEX - except wx.PyDeadObjectError: pass - options_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, options_notebook_changed) - def xy_mode_changed(mode): - #ensure xy tab is selected - if mode and options_notebook.GetSelection() != XY_PAGE_INDEX: - options_notebook.SetSelection(XY_PAGE_INDEX) - #ensure xy tab is not selected - elif not mode and options_notebook.GetSelection() == XY_PAGE_INDEX: - options_notebook.SetSelection(0) - #show/hide control buttons - scope_mode_box.ShowItems(not mode) - xy_mode_box.ShowItems(mode) - control_box.Layout() - parent.subscribe(XY_MODE_KEY, xy_mode_changed) + options_notebook_args = list() + CHANNELS = [('Ch %d'%(i+1), i) for i in range(parent.num_inputs)] ################################################## # Channel Menu Boxes ################################################## for i in range(parent.num_inputs): channel_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(channel_menu_panel, 'Ch%d'%(i+1)) + options_notebook_args.append((channel_menu_panel, i, 'Ch%d'%(i+1))) channel_menu_box = wx.BoxSizer(wx.VERTICAL) channel_menu_panel.SetSizer(channel_menu_box) #ac couple check box channel_menu_box.AddStretchSpacer() - coupling_chooser = common.DropDownController(channel_menu_panel, COUPLING_MODES, parent, common.index_key(AC_COUPLE_KEY, i), SIZE) - channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Coupling', coupling_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=channel_menu_panel, sizer=channel_menu_box, + ps=parent, key=common.index_key(AC_COUPLE_KEY, i), + choices=map(lambda x: x[1], COUPLING_MODES), + labels=map(lambda x: x[0], COUPLING_MODES), + label='Coupling', width=WIDTH, + ) #marker channel_menu_box.AddStretchSpacer() - marker_chooser = common.DropDownController(channel_menu_panel, MARKER_TYPES, parent, common.index_key(MARKER_KEY, i), SIZE) - channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=channel_menu_panel, sizer=channel_menu_box, + ps=parent, key=common.index_key(MARKER_KEY, i), + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + label='Marker', width=WIDTH, + ) channel_menu_box.AddStretchSpacer() ################################################## # Trigger Menu Box ################################################## trigger_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(trigger_menu_panel, 'Trig') + options_notebook_args.append((trigger_menu_panel, TRIGGER_PAGE_INDEX, 'Trig')) trigger_menu_box = wx.BoxSizer(wx.VERTICAL) trigger_menu_panel.SetSizer(trigger_menu_box) #trigger mode - trigger_mode_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_MODES, parent, TRIGGER_MODE_KEY, SIZE) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Mode', trigger_mode_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_MODE_KEY, + choices=map(lambda x: x[1], TRIGGER_MODES), + labels=map(lambda x: x[0], TRIGGER_MODES), + label='Mode', width=WIDTH, + ) #trigger slope - trigger_slope_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_SLOPES, parent, TRIGGER_SLOPE_KEY, SIZE) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_slope_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Slope', trigger_slope_chooser), 0, wx.EXPAND) + trigger_slope_chooser = forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_SLOPE_KEY, + choices=map(lambda x: x[1], TRIGGER_SLOPES), + labels=map(lambda x: x[0], TRIGGER_SLOPES), + label='Slope', width=WIDTH, + ) #trigger channel - choices = [('Channel %d'%(i+1), i) for i in range(parent.num_inputs)] - trigger_channel_chooser = common.DropDownController(trigger_menu_panel, choices, parent, TRIGGER_CHANNEL_KEY, SIZE) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_channel_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Channel', trigger_channel_chooser), 0, wx.EXPAND) + trigger_channel_chooser = forms.drop_down( + parent=trigger_menu_panel, sizer=trigger_menu_box, + ps=parent, key=TRIGGER_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel', width=WIDTH, + ) #trigger level hbox = wx.BoxSizer(wx.HORIZONTAL) trigger_menu_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(trigger_menu_panel, label=' Level '), 1, wx.ALIGN_CENTER_VERTICAL) - trigger_level_button = wx.Button(trigger_menu_panel, label='50%', style=wx.BU_EXACTFIT) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_button.Enable(x!=gr.gr_TRIG_MODE_FREE)) - trigger_level_button.Bind(wx.EVT_BUTTON, self.parent.set_auto_trigger_level) - hbox.Add(trigger_level_button, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(10) - trigger_level_buttons = common.IncrDecrButtons(trigger_menu_panel, self._on_incr_trigger_level, self._on_decr_trigger_level) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_buttons.Enable(x!=gr.gr_TRIG_MODE_FREE)) - hbox.Add(trigger_level_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.Add(wx.StaticText(trigger_menu_panel, label='Level:'), 1, wx.ALIGN_CENTER_VERTICAL) + trigger_level_button = forms.single_button( + parent=trigger_menu_panel, sizer=hbox, label='50%', + callback=parent.set_auto_trigger_level, style=wx.BU_EXACTFIT, + ) + hbox.AddSpacer(WIDTH-60) + trigger_level_buttons = forms.incr_decr_buttons( + parent=trigger_menu_panel, sizer=hbox, + on_incr=self._on_incr_trigger_level, on_decr=self._on_decr_trigger_level, + ) + def disable_all(trigger_mode): + for widget in (trigger_slope_chooser, trigger_channel_chooser, trigger_level_buttons, trigger_level_button): + widget.Disable(trigger_mode == gr.gr_TRIG_MODE_FREE) + parent.subscribe(TRIGGER_MODE_KEY, disable_all) + disable_all(parent[TRIGGER_MODE_KEY]) ################################################## # XY Menu Box ################################################## if parent.num_inputs > 1: xy_menu_panel = wx.Panel(options_notebook) - options_notebook.AddPage(xy_menu_panel, 'XY') + options_notebook_args.append((xy_menu_panel, XY_PAGE_INDEX, 'XY')) xy_menu_box = wx.BoxSizer(wx.VERTICAL) xy_menu_panel.SetSizer(xy_menu_box) #x and y channel choosers xy_menu_box.AddStretchSpacer() - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - x_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, X_CHANNEL_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch X', x_channel_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=X_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel X', width=WIDTH, + ) xy_menu_box.AddStretchSpacer() - y_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, Y_CHANNEL_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch Y', y_channel_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=Y_CHANNEL_KEY, + choices=map(lambda x: x[1], CHANNELS), + labels=map(lambda x: x[0], CHANNELS), + label='Channel Y', width=WIDTH, + ) #marker xy_menu_box.AddStretchSpacer() - marker_chooser = common.DropDownController(xy_menu_panel, MARKER_TYPES, parent, XY_MARKER_KEY, SIZE) - xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + forms.drop_down( + parent=xy_menu_panel, sizer=xy_menu_box, + ps=parent, key=XY_MARKER_KEY, + choices=map(lambda x: x[1], MARKER_TYPES), + labels=map(lambda x: x[0], MARKER_TYPES), + label='Marker', width=WIDTH, + ) xy_menu_box.AddStretchSpacer() ################################################## + # Setup Options Notebook + ################################################## + forms.notebook( + parent=self, sizer=chan_options_box, + notebook=options_notebook, + ps=parent, key=CHANNEL_OPTIONS_KEY, + pages=map(lambda x: x[0], options_notebook_args), + choices=map(lambda x: x[1], options_notebook_args), + labels=map(lambda x: x[2], options_notebook_args), + ) + #gui handling for channel options changing + def options_notebook_changed(chan_opt): + try: + parent[TRIGGER_SHOW_KEY] = chan_opt == TRIGGER_PAGE_INDEX + parent[XY_MODE_KEY] = chan_opt == XY_PAGE_INDEX + except wx.PyDeadObjectError: pass + parent.subscribe(CHANNEL_OPTIONS_KEY, options_notebook_changed) + #gui handling for xy mode changing + def xy_mode_changed(mode): + #ensure xy tab is selected + if mode and parent[CHANNEL_OPTIONS_KEY] != XY_PAGE_INDEX: + parent[CHANNEL_OPTIONS_KEY] = XY_PAGE_INDEX + #ensure xy tab is not selected + elif not mode and parent[CHANNEL_OPTIONS_KEY] == XY_PAGE_INDEX: + parent[CHANNEL_OPTIONS_KEY] = 0 + #show/hide control buttons + scope_mode_box.ShowItems(not mode) + xy_mode_box.ShowItems(mode) + control_box.Layout() + parent.subscribe(XY_MODE_KEY, xy_mode_changed) + xy_mode_changed(parent[XY_MODE_KEY]) + ################################################## # Run/Stop Button ################################################## #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) #mouse wheel event @@ -323,28 +401,10 @@ class scope_window(wx.Panel, pubsub.pubsub): self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key) self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key) self.proxy(DECIMATION_KEY, controller, decimation_key) - for i in range(num_inputs): - self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) - #init panel and plot - wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) - self.plotter = plotter.channel_plotter(self) - self.plotter.SetSize(wx.Size(*size)) - self.plotter.set_title(title) - self.plotter.enable_legend(True) - self.plotter.enable_point_label(True) - self.plotter.enable_grid_lines(True) - #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) #initialize values self[RUNNING_KEY] = True - for i in range(self.num_inputs): - self[common.index_key(AC_COUPLE_KEY, i)] = self[common.index_key(AC_COUPLE_KEY, i)] - self[common.index_key(MARKER_KEY, i)] = DEFAULT_MARKER_TYPE self[XY_MARKER_KEY] = 2.0 + self[CHANNEL_OPTIONS_KEY] = 0 self[XY_MODE_KEY] = xy_mode self[X_CHANNEL_KEY] = 0 self[Y_CHANNEL_KEY] = self.num_inputs-1 @@ -364,6 +424,22 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 + for i in range(num_inputs): + self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) + #init panel and plot + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) + self.plotter = plotter.channel_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_legend(True) + self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) + #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) #register events for message self.subscribe(MSG_KEY, self.handle_msg) #register events for grid diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 8dcb4b619..77819b733 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -30,6 +30,7 @@ import math import pubsub from constants import * from gnuradio import gr #for gr.prefs +import forms ################################################## # Constants @@ -64,54 +65,75 @@ class control_panel(wx.Panel): wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - #color mode - control_box.AddStretchSpacer() - color_mode_chooser = common.DropDownController(self, COLOR_MODES, parent, COLOR_MODE_KEY) - control_box.Add(common.LabelBox(self, 'Color', color_mode_chooser), 0, wx.EXPAND) + options_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Options', + bold=True, orient=wx.VERTICAL, + ) #average + forms.check_box( + sizer=options_box, parent=self, label='Average', + ps=parent, key=AVERAGE_KEY, + ) + avg_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Avg Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=AVG_ALPHA_KEY, width=50, + ) + avg_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=AVG_ALPHA_MIN_EXP, + max_exp=AVG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=AVG_ALPHA_KEY, + ) + for widget in (avg_alpha_text, avg_alpha_slider): + parent.subscribe(AVERAGE_KEY, widget.Enable) + widget.Enable(parent[AVERAGE_KEY]) + #begin axes box control_box.AddStretchSpacer() - average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) - control_box.Add(average_check_box, 0, wx.EXPAND) - control_box.AddSpacer(2) - avg_alpha_slider = common.LogSliderController( - self, 'Avg Alpha', - AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent, AVG_ALPHA_KEY, - formatter=lambda x: ': %.4f'%x, + axes_box = forms.static_box_sizer( + parent=self, sizer=control_box, label='Axes Options', + bold=True, orient=wx.VERTICAL, + ) + #num lines buttons + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Time Scale', + on_incr=self._on_incr_time_scale, on_decr=self._on_decr_time_scale, ) - parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) - control_box.Add(avg_alpha_slider, 0, wx.EXPAND) #dyanmic range buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) - control_box.Add(dynamic_range_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Dyn Range', + on_incr=self._on_incr_dynamic_range, on_decr=self._on_decr_dynamic_range, + ) #ref lvl buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(ref_lvl_buttons, 0, wx.ALIGN_CENTER) - #num lines buttons - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) - control_box.Add(time_scale_buttons, 0, wx.ALIGN_CENTER) + forms.incr_decr_buttons( + parent=self, sizer=axes_box, label='Ref Level', + on_incr=self._on_incr_ref_level, on_decr=self._on_decr_ref_level, + ) + #color mode + forms.drop_down( + parent=self, sizer=axes_box, width=100, + ps=parent, key=COLOR_MODE_KEY, label='Color', + choices=map(lambda x: x[1], COLOR_MODES), + labels=map(lambda x: x[0], COLOR_MODES), + ) #autoscale - control_box.AddStretchSpacer() - autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(autoscale_button, 0, wx.EXPAND) + forms.single_button( + parent=self, sizer=axes_box, label='Autoscale', + callback=self.parent.autoscale, + ) #clear - clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) - clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) - control_box.Add(clear_button, 0, wx.EXPAND) + control_box.AddStretchSpacer() + forms.single_button( + parent=self, sizer=control_box, label='Clear', + callback=self._on_clear_button, + ) #run/stop - run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(run_button, 0, wx.EXPAND) + forms.toggle_button( + sizer=control_box, parent=self, + true_label='Stop', false_label='Run', + ps=parent, key=RUNNING_KEY, + ) #set sizer self.SetSizerAndFit(control_box) @@ -181,18 +203,10 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self.plotter.set_title(title) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(False) - #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) #plotter listeners self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode) self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines) #initialize values - self[AVERAGE_KEY] = self[AVERAGE_KEY] - self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] self[DYNAMIC_RANGE_KEY] = dynamic_range self[NUM_LINES_KEY] = num_lines self[Y_DIVS_KEY] = 8 @@ -201,6 +215,12 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self[BASEBAND_FREQ_KEY] = baseband_freq self[COLOR_MODE_KEY] = COLOR_MODES[0][1] self[RUNNING_KEY] = True + #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) #register events self.subscribe(MSG_KEY, self.handle_msg) for key in ( -- cgit From 9988664127b367fa8fee4409f8460673d6f265e1 Mon Sep 17 00:00:00 2001 From: jblum Date: Tue, 23 Jun 2009 20:38:18 +0000 Subject: Merging r11186:11273 from grc branch. Fixes, features, and reorganization for grc. Minor fixes and features for wxgui forms. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11274 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/forms/converters.py | 17 ++++++++++++----- gr-wxgui/src/python/forms/forms.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/converters.py b/gr-wxgui/src/python/forms/converters.py index 123aefeb0..e535cb2dd 100644 --- a/gr-wxgui/src/python/forms/converters.py +++ b/gr-wxgui/src/python/forms/converters.py @@ -85,22 +85,28 @@ class eval_converter(abstract_converter): Possible uses, set a complex number, constellation points. Used in text box. """ - def external_to_internal(self, s): - return str(s) + def __init__(self, formatter=lambda x: '%s'%(x)): + self._formatter = formatter + def external_to_internal(self, v): + return self._formatter(v) def internal_to_external(self, s): return eval(s) def help(self): return "Value must be evaluatable by python's eval." class str_converter(abstract_converter): + def __init__(self, formatter=lambda x: '%s'%(x)): + self._formatter = formatter def external_to_internal(self, v): - return str(v) + return self._formatter(v) def internal_to_external(self, s): return str(s) class int_converter(abstract_converter): + def __init__(self, formatter=lambda x: '%d'%round(x)): + self._formatter = formatter def external_to_internal(self, v): - return str(int(round(v))) + return self._formatter(v) def internal_to_external(self, s): return int(s, 0) def help(self): @@ -127,7 +133,8 @@ class slider_converter(abstract_converter): self._scaler = float(maximum - minimum)/num_steps self._cast = cast def external_to_internal(self, v): - return (v - self._offset)/self._scaler + #slider's internal representation is an integer + return int(round((v - self._offset)/self._scaler)) def internal_to_external(self, v): return self._cast(v*self._scaler + self._offset) def help(self): diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index 10f6a4823..c69315b03 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -115,7 +115,6 @@ class _form_base(pubsub, wx.BoxSizer): self.subscribe(INT_KEY, update) self.subscribe(INT_KEY, self._translate_internal_to_external) self.subscribe(EXT_KEY, self._translate_external_to_internal) - if self._callback: self.subscribe(EXT_KEY, self._callback) def _translate_external_to_internal(self, external): try: @@ -134,6 +133,7 @@ class _form_base(pubsub, wx.BoxSizer): except Exception, e: self._err_msg(internal, e) self[EXT_KEY] = self[EXT_KEY] #reset to last good setting + if self._callback: self._callback(self[EXT_KEY]) def _err_msg(self, value, e): print >> sys.stderr, self, 'Error translating value: "%s"\n\t%s\n\t%s'%(value, e, self._converter.help()) -- cgit From f728f2dd61af00b02525d078bbb748bfb75800e1 Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 26 Jun 2009 15:37:23 +0000 Subject: cast choices to a list because .index method DNE in python2.5 for tuple git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11294 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/forms/converters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/converters.py b/gr-wxgui/src/python/forms/converters.py index e535cb2dd..9f757aa84 100644 --- a/gr-wxgui/src/python/forms/converters.py +++ b/gr-wxgui/src/python/forms/converters.py @@ -53,7 +53,8 @@ class chooser_converter(abstract_converter): Used in the chooser base and all sub-classes. """ def __init__(self, choices): - self._choices = choices + #choices must be a list because tuple does not have .index() in python2.5 + self._choices = list(choices) def external_to_internal(self, choice): return self._choices.index(choice) def internal_to_external(self, index): -- cgit From 25c5d91fb7c4b54f1e7d77fd9af213a3675a8339 Mon Sep 17 00:00:00 2001 From: jblum Date: Mon, 6 Jul 2009 02:28:52 +0000 Subject: Merged r11309:11357 from grc branch. Adds notebook cabability to grc and its wxgui windows/controls. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11358 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/plotter/plotter_base.py | 1 + gr-wxgui/src/python/waterfallsink_gl.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 662365a37..dede5a0ad 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -168,4 +168,5 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ Force a paint event. """ + if not self._gl_init_flag: return wx.PostEvent(self, wx.PaintEvent()) diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index 91c1c7eb5..2d4c959f8 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -40,7 +40,6 @@ class _waterfall_sink_base(gr.hier_block2): self, parent, baseband_freq=0, - y_per_div=None, #ignore (old wrapper) ref_level=50, sample_rate=1, fft_size=512, @@ -52,6 +51,7 @@ class _waterfall_sink_base(gr.hier_block2): ref_scale=2.0, dynamic_range=80, num_lines=256, + **kwargs #do not end with a comma ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate -- cgit From a9154607d6f6fd2bfbafd732dccf9edef35e1e6e Mon Sep 17 00:00:00 2001 From: jcorgan Date: Fri, 10 Jul 2009 01:26:37 +0000 Subject: Refactor msgq thread classes to use gru.msgq_runner git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11407 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/common.py | 21 +++++------- gr-wxgui/src/python/fftsink_nongl.py | 45 +++++++++++------------- gr-wxgui/src/python/scopesink_nongl.py | 55 +++++++++++++----------------- gr-wxgui/src/python/waterfallsink_nongl.py | 49 +++++++++++--------------- 4 files changed, 70 insertions(+), 100 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index c6b9509b2..d555a1f05 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -44,31 +44,26 @@ def register_access_methods(destination, controller): ################################################## # Input Watcher Thread ################################################## -import threading +from gnuradio import gru -class input_watcher(threading.Thread): +class input_watcher(gru.msgq_runner): """ Input watcher thread runs forever. Read messages from the message queue. Forward messages to the message handler. """ def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''): - threading.Thread.__init__(self) - self.setDaemon(1) - self.msgq = msgq self._controller = controller self._msg_key = msg_key self._arg1_key = arg1_key self._arg2_key = arg2_key - self.keep_running = True - self.start() + gru.msgq_runner.__init__(self, msgq, self.handle_msg) + + def handle_msg(self, msg): + if self._arg1_key: self._controller[self._arg1_key] = msg.arg1() + if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() + self._controller[self._msg_key] = msg.to_string() - def run(self): - while self.keep_running: - msg = self.msgq.delete_head() - if self._arg1_key: self._controller[self._arg1_key] = msg.arg1() - if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() - self._controller[self._msg_key] = msg.to_string() ################################################## # Shared Functions diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index a1033e818..ca5e91fdb 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -25,7 +25,6 @@ from gnuradio.wxgui import stdgui2 import wx import plot import numpy -import threading import math DIV_LEVELS = (1, 2, 5, 10, 20) @@ -193,34 +192,28 @@ class DataEvent(wx.PyEvent): self.__class__ (self.GetId()) -class input_watcher (threading.Thread): +class input_watcher (gru.msgq_runner): def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq self.fft_size = fft_size self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de + gru.msgq_runner.__init__(self, msgq, self.handle_msg) + + def handle_msg(self, msg): + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = numpy.fromstring (s, numpy.float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de class control_panel(wx.Panel): diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index bd3187992..5c1379ee6 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -25,7 +25,6 @@ from gnuradio.wxgui import stdgui2 import wx import gnuradio.wxgui.plot as plot import numpy -import threading import struct default_scopesink_size = (640, 240) @@ -193,48 +192,40 @@ class win_info (object): return self.marker -class input_watcher (threading.Thread): +class input_watcher (gru.msgq_runner): def __init__ (self, msgq, event_receiver, frame_decim, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq self.event_receiver = event_receiver self.frame_decim = frame_decim self.iscan = 0 - self.keep_running = True - self.start () - - def run (self): - # print "input_watcher: pid = ", os.getpid () - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - if self.iscan == 0: # only display at frame_decim - self.iscan = self.frame_decim - - 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 - - bytes_per_chan = nsamples * gr.sizeof_float - - records = [] - for ch in range (nchan): - - start = ch * bytes_per_chan - chan_data = s[start:start+bytes_per_chan] - rec = numpy.fromstring (chan_data, numpy.float32) - records.append (rec) + gru.msgq_runner.__init__(self, msgq, self.handle_msg) + def handle_msg(self, msg): + if self.iscan == 0: # only display at frame_decim + self.iscan = self.frame_decim + + 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 + + bytes_per_chan = nsamples * gr.sizeof_float + + records = [] + for ch in range (nchan): + + start = ch * bytes_per_chan + chan_data = s[start:start+bytes_per_chan] + rec = numpy.fromstring (chan_data, numpy.float32) + records.append (rec) + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - + de = DataEvent (records) wx.PostEvent (self.event_receiver, de) records = [] del de - # end if iscan == 0 - self.iscan -= 1 + self.iscan -= 1 class scope_window (wx.Panel): diff --git a/gr-wxgui/src/python/waterfallsink_nongl.py b/gr-wxgui/src/python/waterfallsink_nongl.py index 9d97c4e3c..bb478c7cf 100644 --- a/gr-wxgui/src/python/waterfallsink_nongl.py +++ b/gr-wxgui/src/python/waterfallsink_nongl.py @@ -26,7 +26,6 @@ import wx import gnuradio.wxgui.plot as plot import numpy import os -import threading import math default_fftsink_size = (640,240) @@ -148,37 +147,29 @@ class DataEvent(wx.PyEvent): def Clone (self): self.__class__ (self.GetId()) - - -class input_watcher (threading.Thread): + +class input_watcher (gru.msgq_runner): def __init__ (self, msgq, fft_size, event_receiver, **kwds): - threading.Thread.__init__ (self, **kwds) - self.setDaemon (1) - self.msgq = msgq self.fft_size = fft_size self.event_receiver = event_receiver - self.keep_running = True - self.start () - - def run (self): - while (self.keep_running): - msg = self.msgq.delete_head() # blocking read of message queue - itemsize = int(msg.arg1()) - nitems = int(msg.arg2()) - - s = msg.to_string() # get the body of the msg as a string - - # There may be more than one FFT frame in the message. - # If so, we take only the last one - if nitems > 1: - start = itemsize * (nitems - 1) - s = s[start:start+itemsize] - - complex_data = numpy.fromstring (s, numpy.float32) - de = DataEvent (complex_data) - wx.PostEvent (self.event_receiver, de) - del de - + gru.msgq_runner.__init__(self, msgq, self.handle_msg) + + def handle_msg(self, msg): + itemsize = int(msg.arg1()) + nitems = int(msg.arg2()) + + s = msg.to_string() # get the body of the msg as a string + + # There may be more than one FFT frame in the message. + # If so, we take only the last one + if nitems > 1: + start = itemsize * (nitems - 1) + s = s[start:start+itemsize] + + complex_data = numpy.fromstring (s, numpy.float32) + de = DataEvent (complex_data) + wx.PostEvent (self.event_receiver, de) + del de class waterfall_window (wx.Panel): def __init__ (self, fftsink, parent, id = -1, -- cgit From f9f8e50ab35f931a74b2bb510e1ba2970a6a2194 Mon Sep 17 00:00:00 2001 From: jblum Date: Thu, 16 Jul 2009 05:06:00 +0000 Subject: There is no y per div, but there is a dynamic range. Added **kwargs to nongl for backwards compadibility. Updated todo. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11448 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/waterfallsink_nongl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/waterfallsink_nongl.py b/gr-wxgui/src/python/waterfallsink_nongl.py index bb478c7cf..3c25a9d9f 100644 --- a/gr-wxgui/src/python/waterfallsink_nongl.py +++ b/gr-wxgui/src/python/waterfallsink_nongl.py @@ -75,7 +75,7 @@ class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): def __init__(self, parent, baseband_freq=0, y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): + title='', size=default_fftsink_size, **kwargs): gr.hier_block2.__init__(self, "waterfall_sink_f", gr.io_signature(1, 1, gr.sizeof_float), @@ -106,7 +106,7 @@ class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): def __init__(self, parent, baseband_freq=0, y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size): + title='', size=default_fftsink_size, **kwargs): gr.hier_block2.__init__(self, "waterfall_sink_f", gr.io_signature(1, 1, gr.sizeof_gr_complex), -- cgit From 599279169ecbd363ccbaacaafd8b4bfc9b837c21 Mon Sep 17 00:00:00 2001 From: jblum Date: Fri, 17 Jul 2009 00:36:11 +0000 Subject: fixed fft reording git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11450 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/fft_window.py | 4 ++-- gr-wxgui/src/python/waterfall_window.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index fded1a8fa..ba5711d10 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -239,8 +239,8 @@ class fft_window(wx.Panel, pubsub.pubsub): samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame num_samps = len(samples) #reorder fft - if self.real: samples = samples[:num_samps/2] - else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2])) + if self.real: samples = samples[:(num_samps+1)/2] + else: samples = numpy.concatenate((samples[num_samps/2+1:], samples[:(num_samps+1)/2])) self.samples = samples #peak hold calculation if self[PEAK_HOLD_KEY]: diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 77819b733..c00992e14 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -261,8 +261,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame num_samps = len(samples) #reorder fft - if self.real: samples = samples[:num_samps/2] - else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2])) + if self.real: samples = samples[:(num_samps+1)/2] + else: samples = numpy.concatenate((samples[num_samps/2+1:], samples[:(num_samps+1)/2])) #plot the fft self.plotter.set_samples( samples=samples, -- cgit From 253018c6cdb114f5662a2d7ba8ed748c6e68e3a7 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 14 Aug 2009 18:10:11 +0000 Subject: Added git ignore files auto created from svn:ignore properties. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@11592 221aa14e-8319-0410-a670-987f0aec2ac5 --- gr-wxgui/src/python/.gitignore | 8 ++++++++ gr-wxgui/src/python/forms/.gitignore | 1 + gr-wxgui/src/python/plotter/.gitignore | 3 +++ 3 files changed, 12 insertions(+) create mode 100644 gr-wxgui/src/python/.gitignore create mode 100644 gr-wxgui/src/python/forms/.gitignore create mode 100644 gr-wxgui/src/python/plotter/.gitignore (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/.gitignore b/gr-wxgui/src/python/.gitignore new file mode 100644 index 000000000..f9c5da0db --- /dev/null +++ b/gr-wxgui/src/python/.gitignore @@ -0,0 +1,8 @@ +/Makefile +/Makefile.in +/.deps +/.libs +/*.la +/*.lo +/*.pyc +/*.pyo diff --git a/gr-wxgui/src/python/forms/.gitignore b/gr-wxgui/src/python/forms/.gitignore new file mode 100644 index 000000000..a74b07aee --- /dev/null +++ b/gr-wxgui/src/python/forms/.gitignore @@ -0,0 +1 @@ +/*.pyc diff --git a/gr-wxgui/src/python/plotter/.gitignore b/gr-wxgui/src/python/plotter/.gitignore new file mode 100644 index 000000000..b6950912c --- /dev/null +++ b/gr-wxgui/src/python/plotter/.gitignore @@ -0,0 +1,3 @@ +/Makefile +/Makefile.in +/*.pyc -- cgit From f3c5010f9c9e8b82048a6d631418c9b5b482e593 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 22 Aug 2009 15:08:40 -0700 Subject: added traces to fft window --- gr-wxgui/src/python/constants.py | 2 ++ gr-wxgui/src/python/fft_window.py | 71 ++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 13 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 5e1395701..8ff7fa8fe 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -41,6 +41,8 @@ MSG_KEY = 'msg' NUM_LINES_KEY = 'num_lines' OMEGA_KEY = 'omega' PEAK_HOLD_KEY = 'peak_hold' +TRACE_STORE_KEY = 'trace_store' +TRACE_SHOW_KEY = 'trace_show' REF_LEVEL_KEY = 'ref_level' RUNNING_KEY = 'running' SAMPLE_RATE_KEY = 'sample_rate' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index ba5711d10..87712b1e2 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -39,10 +39,15 @@ SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) -DIV_LEVELS = (1, 2, 5, 10, 20) +DB_DIV_MIN, DB_DIV_MAX = 1, 20 FFT_PLOT_COLOR_SPEC = (0.3, 0.3, 1.0) PEAK_VALS_COLOR_SPEC = (0.0, 0.8, 0.0) -NO_PEAK_VALS = list() +EMPTY_TRACE = list() +TRACES = ('A', 'B') +TRACES_COLOR_SPEC = { + 'A': (1.0, 0.0, 0.0), + 'B': (0.8, 0.0, 0.8), +} ################################################## # FFT window control panel @@ -63,7 +68,7 @@ class control_panel(wx.Panel): control_box.AddStretchSpacer() #checkboxes for average and peak hold options_box = forms.static_box_sizer( - parent=self, sizer=control_box, label='Options', + parent=self, sizer=control_box, label='Trace Options', bold=True, orient=wx.VERTICAL, ) forms.check_box( @@ -90,17 +95,32 @@ class control_panel(wx.Panel): for widget in (avg_alpha_text, avg_alpha_slider): parent.subscribe(AVERAGE_KEY, widget.Enable) widget.Enable(parent[AVERAGE_KEY]) + + #trace menu + for trace in TRACES: + trace_box = wx.BoxSizer(wx.HORIZONTAL) + options_box.Add(trace_box, 0, wx.EXPAND) + forms.check_box( + sizer=trace_box, parent=self, + ps=parent, key=TRACE_SHOW_KEY+trace, + label='Trace %s'%trace, + ) + trace_box.AddSpacer(10) + forms.single_button( + sizer=trace_box, parent=self, + ps=parent, key=TRACE_STORE_KEY+trace, + label='Store', style=wx.BU_EXACTFIT, + ) + trace_box.AddSpacer(10) #radio buttons for div size control_box.AddStretchSpacer() y_ctrl_box = forms.static_box_sizer( parent=self, sizer=control_box, label='Axis Options', bold=True, orient=wx.VERTICAL, ) - forms.radio_buttons( - sizer=y_ctrl_box, parent=self, - ps=parent, key=Y_PER_DIV_KEY, - style=wx.RA_VERTICAL|wx.NO_BORDER, choices=DIV_LEVELS, - labels=map(lambda x: '%s dB/div'%x, DIV_LEVELS), + forms.incr_decr_buttons( + parent=self, sizer=y_ctrl_box, label='dB/Div', + on_incr=self._on_incr_db_div, on_decr=self._on_decr_db_div, ) #ref lvl buttons forms.incr_decr_buttons( @@ -135,6 +155,10 @@ class control_panel(wx.Panel): self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_ref_level(self, event): self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY] + def _on_incr_db_div(self, event): + self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, self.parent[Y_PER_DIV_KEY]*2) + def _on_decr_db_div(self, event): + self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, self.parent[Y_PER_DIV_KEY]/2) ################################################## # FFT window with plotter and control panel @@ -159,13 +183,12 @@ class fft_window(wx.Panel, pubsub.pubsub): msg_key, ): pubsub.pubsub.__init__(self) - #ensure y_per_div - if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0] #setup - self.samples = list() + self.samples = EMPTY_TRACE self.real = real self.fft_size = fft_size self._reset_peak_vals() + self._traces = dict() #proxy the keys self.proxy(MSG_KEY, controller, msg_key) self.proxy(AVERAGE_KEY, controller, average_key) @@ -179,6 +202,19 @@ class fft_window(wx.Panel, pubsub.pubsub): self[REF_LEVEL_KEY] = ref_level self[BASEBAND_FREQ_KEY] = baseband_freq self[RUNNING_KEY] = True + for trace in TRACES: + #a function that returns a function + #so the function wont use local trace + def new_store_trace(my_trace): + def store_trace(*args): + self._traces[my_trace] = self.samples + self.update_grid() + return store_trace + self._traces[trace] = EMPTY_TRACE + self[TRACE_STORE_KEY+trace] = False + self[TRACE_SHOW_KEY+trace] = True + self.subscribe(TRACE_STORE_KEY+trace, new_store_trace(trace)) + self.subscribe(TRACE_SHOW_KEY+trace, self.update_grid) #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) @@ -194,7 +230,7 @@ class fft_window(wx.Panel, pubsub.pubsub): main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) #register events - self.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) + self.subscribe(AVERAGE_KEY, self._reset_peak_vals) self.subscribe(MSG_KEY, self.handle_msg) self.subscribe(SAMPLE_RATE_KEY, self.update_grid) for key in ( @@ -223,7 +259,7 @@ class fft_window(wx.Panel, pubsub.pubsub): #set the range to a clean number of the dynamic range self[Y_PER_DIV_KEY] = common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY]) - def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS + def _reset_peak_vals(self, *args): self.peak_vals = EMPTY_TRACE def handle_msg(self, msg): """ @@ -272,6 +308,15 @@ class fft_window(wx.Panel, pubsub.pubsub): The x axis depends on sample rate, baseband freq, and x divs. The y axis depends on y per div, y divs, and ref level. """ + for trace in TRACES: + channel = '%s'%trace.upper() + if self[TRACE_SHOW_KEY+trace]: + self.plotter.set_waveform( + channel=channel, + samples=self._traces[trace], + color_spec=TRACES_COLOR_SPEC[trace], + ) + else: self.plotter.clear_waveform(channel=channel) #grid parameters sample_rate = self[SAMPLE_RATE_KEY] baseband_freq = self[BASEBAND_FREQ_KEY] -- cgit From b6c19491d34357ab2d6332f91733afe7367da92a Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 22 Aug 2009 22:01:55 -0700 Subject: automatic store for empty traces on enable --- gr-wxgui/src/python/fft_window.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 87712b1e2..0529e6a5d 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -210,11 +210,18 @@ class fft_window(wx.Panel, pubsub.pubsub): self._traces[my_trace] = self.samples self.update_grid() return store_trace + def new_toggle_trace(my_trace): + def toggle_trace(toggle): + #do an automatic store if toggled on and empty trace + if toggle and not len(self._traces[my_trace]): + self._traces[my_trace] = self.samples + self.update_grid() + return toggle_trace self._traces[trace] = EMPTY_TRACE self[TRACE_STORE_KEY+trace] = False - self[TRACE_SHOW_KEY+trace] = True + self[TRACE_SHOW_KEY+trace] = False self.subscribe(TRACE_STORE_KEY+trace, new_store_trace(trace)) - self.subscribe(TRACE_SHOW_KEY+trace, self.update_grid) + self.subscribe(TRACE_SHOW_KEY+trace, new_toggle_trace(trace)) #init panel and plot wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) -- cgit From cadc9548afb7b4a385cea51f48745f0a1c702607 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 28 Aug 2009 18:15:49 -0700 Subject: Recursive resolution of virtual sources. Flow graph generation code working. Also, mod to fft window to use clean/nice Db/div. --- gr-wxgui/src/python/fft_window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 0529e6a5d..926812d8c 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -156,9 +156,9 @@ class control_panel(wx.Panel): def _on_decr_ref_level(self, event): self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY] def _on_incr_db_div(self, event): - self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, self.parent[Y_PER_DIV_KEY]*2) + self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) def _on_decr_db_div(self, event): - self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, self.parent[Y_PER_DIV_KEY]/2) + self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) ################################################## # FFT window with plotter and control panel -- cgit From 0c68c486ec09da471c27b6f4d658ae0ba8f861b7 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 31 Aug 2009 18:18:22 -0700 Subject: Modified log power fft block so ref scale is peak to peak. Tweaked fft sink autoscale routine to come up with better numbers. Modified scope sink ac couple block to use constant tap. The previous tap calculation would cause failure for very small sample rates. --- gr-wxgui/src/python/fft_window.py | 20 ++++++++++++-------- gr-wxgui/src/python/scopesink_gl.py | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 0529e6a5d..4c575f1a6 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -256,15 +256,19 @@ class fft_window(wx.Panel, pubsub.pubsub): if not len(self.samples): return #get the peak level (max of the samples) peak_level = numpy.max(self.samples) - #get the noise floor (averge the smallest samples) - noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4]) - #padding - noise_floor -= abs(noise_floor)*.5 - peak_level += abs(peak_level)*.1 - #set the reference level to a multiple of y divs - self[REF_LEVEL_KEY] = self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY]) + #separate noise samples + noise_samps = numpy.sort(self.samples)[:len(self.samples)/2] + #get the noise floor + noise_floor = numpy.average(noise_samps) + #get the noise deviation + noise_dev = numpy.std(noise_samps) + #determine the maximum and minimum levels + max_level = peak_level + min_level = noise_floor - abs(2*noise_dev) #set the range to a clean number of the dynamic range - self[Y_PER_DIV_KEY] = common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY]) + self[Y_PER_DIV_KEY] = common.get_clean_num(1+(max_level - min_level)/self[Y_DIVS_KEY]) + #set the reference level to a multiple of y per div + self[REF_LEVEL_KEY] = self[Y_PER_DIV_KEY]*round(.5+max_level/self[Y_PER_DIV_KEY]) def _reset_peak_vals(self, *args): self.peak_vals = EMPTY_TRACE diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index b4ae0f339..a5e3ca3ce 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -50,7 +50,7 @@ class ac_couple_block(gr.hier_block2): self.connect(self, lpf, mute, (sub, 1)) #subscribe controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) - controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(2.0/x)) + controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(0.05)) #initialize controller[ac_couple_key] = ac_couple controller[sample_rate_key] = controller[sample_rate_key] -- cgit From eb1d1b5cfb474b087d41337356efc0cdb7342f28 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 2 Sep 2009 14:06:34 -0700 Subject: Fix so that the waterfall texture is initialized with a buffer of the same size. If the fft size was a non power of two, the buffer would be a different size. This would cause a segfault. Particularly because fft_window was throwing out a bin. --- gr-wxgui/src/python/plotter/waterfall_plotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 2e0669961..d32b0ca0a 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -209,7 +209,7 @@ class waterfall_plotter(grid_plotter_base): 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*self._fft_size*4, numpy.uint8).tostring() + 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 -- cgit From a70c291e3cd4fc3d51f2eebb6b39cdb9d46862da Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 2 Sep 2009 14:18:28 -0700 Subject: waterfall and fft use a common autoscale function --- gr-wxgui/src/python/common.py | 25 ++++++++++++++++++++++--- gr-wxgui/src/python/fft_window.py | 12 +----------- gr-wxgui/src/python/waterfall_window.py | 12 +++--------- 3 files changed, 26 insertions(+), 23 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index d555a1f05..9c97ce1ec 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -137,6 +137,25 @@ def get_min_max(samples): scale_factor = 3 mean = numpy.average(samples) rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1]) - min = mean - rms - max = mean + rms - return min, max + min_val = mean - rms + max_val = mean + rms + return min_val, max_val + +def get_min_max_fft(fft_samps): + """ + Get the minimum and maximum bounds for an array of fft samples. + @param samples the array of real values + @return a tuple of min, max + """ + #get the peak level (max of the samples) + peak_level = numpy.max(fft_samps) + #separate noise samples + noise_samps = numpy.sort(fft_samps)[:len(fft_samps)/2] + #get the noise floor + noise_floor = numpy.average(noise_samps) + #get the noise deviation + noise_dev = numpy.std(noise_samps) + #determine the maximum and minimum levels + max_level = peak_level + min_level = noise_floor - abs(2*noise_dev) + return min_level, max_level diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 4c575f1a6..237c8940c 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -254,17 +254,7 @@ class fft_window(wx.Panel, pubsub.pubsub): Set the dynamic range and reference level. """ if not len(self.samples): return - #get the peak level (max of the samples) - peak_level = numpy.max(self.samples) - #separate noise samples - noise_samps = numpy.sort(self.samples)[:len(self.samples)/2] - #get the noise floor - noise_floor = numpy.average(noise_samps) - #get the noise deviation - noise_dev = numpy.std(noise_samps) - #determine the maximum and minimum levels - max_level = peak_level - min_level = noise_floor - abs(2*noise_dev) + min_level, max_level = common.get_min_max_fft(self.samples) #set the range to a clean number of the dynamic range self[Y_PER_DIV_KEY] = common.get_clean_num(1+(max_level - min_level)/self[Y_DIVS_KEY]) #set the reference level to a multiple of y per div diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index c00992e14..28e67a830 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -237,16 +237,10 @@ class waterfall_window(wx.Panel, pubsub.pubsub): Does not affect the current data in the waterfall. """ if not len(self.samples): return - #get the peak level (max of the samples) - peak_level = numpy.max(self.samples) - #get the noise floor (averge the smallest samples) - noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4]) - #padding - noise_floor -= abs(noise_floor)*.5 - peak_level += abs(peak_level)*.1 + min_level, max_level = common.get_min_max_fft(self.samples) #set the range and level - self[REF_LEVEL_KEY] = peak_level - self[DYNAMIC_RANGE_KEY] = peak_level - noise_floor + self[REF_LEVEL_KEY] = max_level + self[DYNAMIC_RANGE_KEY] = max_level - min_level def handle_msg(self, msg): """ -- cgit From 7d4915f78da95b5da4a3525c392667cfd4ed04be Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 9 Sep 2009 13:05:38 -0700 Subject: Round the slider's value, but not the internal representation. Now, the slider can operate on any step size without killing the precision for other forms. --- gr-wxgui/src/python/forms/converters.py | 3 +-- gr-wxgui/src/python/forms/forms.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/converters.py b/gr-wxgui/src/python/forms/converters.py index 9f757aa84..3cc13466a 100644 --- a/gr-wxgui/src/python/forms/converters.py +++ b/gr-wxgui/src/python/forms/converters.py @@ -134,8 +134,7 @@ class slider_converter(abstract_converter): self._scaler = float(maximum - minimum)/num_steps self._cast = cast def external_to_internal(self, v): - #slider's internal representation is an integer - return int(round((v - self._offset)/self._scaler)) + return (v - self._offset)/self._scaler def internal_to_external(self, v): return self._cast(v*self._scaler + self._offset) def help(self): diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index c69315b03..c0f181b4d 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -37,6 +37,9 @@ The forms follow a layered model: Known problems: * An empty label in the radio box still consumes space. * The static text cannot resize the parent at runtime. + * Text box should indicate its that its edited but not committed. + * Colorize? + * Tab out to commit? """ EXT_KEY = 'external' @@ -176,7 +179,7 @@ class _slider_base(_form_base): self._add_widget(self._slider, label, flag=wx.EXPAND) def _handle(self, event): self[INT_KEY] = self._slider.GetValue() - def _update(self, value): self._slider.SetValue(value) + def _update(self, value): self._slider.SetValue(int(round(value))) ######################################################################## # Static Text Form -- cgit From 3b5fc17434361cb7698e9dd9b0f16e4fbdea7f78 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 9 Sep 2009 13:57:38 -0700 Subject: set text box bg color on change --- gr-wxgui/src/python/forms/forms.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index c0f181b4d..8dc58367d 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -37,9 +37,6 @@ The forms follow a layered model: Known problems: * An empty label in the radio box still consumes space. * The static text cannot resize the parent at runtime. - * Text box should indicate its that its edited but not committed. - * Colorize? - * Tab out to commit? """ EXT_KEY = 'external' @@ -227,11 +224,18 @@ class text_box(_form_base): def __init__(self, label='', width=-1, converter=converters.eval_converter(), **kwargs): _form_base.__init__(self, converter=converter, **kwargs) self._text_box = wx.TextCtrl(self._parent, size=wx.Size(width, -1), style=wx.TE_PROCESS_ENTER) + self._default_bg_colour = self._text_box.GetBackgroundColour() self._text_box.Bind(wx.EVT_TEXT_ENTER, self._handle) + self._text_box.Bind(wx.EVT_TEXT, self._update_color) self._add_widget(self._text_box, label) + def _update_color(self, *args): + if self._text_box.GetValue() == self[INT_KEY]: + self._text_box.SetBackgroundColour(self._default_bg_colour) + else: self._text_box.SetBackgroundColour('#EEDDDD') + def _handle(self, event): self[INT_KEY] = self._text_box.GetValue() - def _update(self, value): self._text_box.SetValue(value) + def _update(self, value): self._text_box.SetValue(value); self._update_color() ######################################################################## # Slider Form -- cgit From 880a6ea7447978a2973422ac7abad6d99d7c1f56 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 14 Sep 2009 22:27:50 -0700 Subject: fixed bool converter in forms to work with non bool options --- gr-wxgui/src/python/forms/converters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/converters.py b/gr-wxgui/src/python/forms/converters.py index 3cc13466a..db14d2752 100644 --- a/gr-wxgui/src/python/forms/converters.py +++ b/gr-wxgui/src/python/forms/converters.py @@ -72,12 +72,14 @@ class bool_converter(abstract_converter): self._true = true self._false = false def external_to_internal(self, v): - return bool(v) + if v == self._true: return True + if v == self._false: return False + raise Exception, 'Value "%s" is not a possible option.'%v def internal_to_external(self, v): if v: return self._true else: return self._false def help(self): - return "Value must be cast-able to type bool." + return "Value must be in (%s, %s)."%(self._true, self._false) class eval_converter(abstract_converter): """ -- cgit From 57e810d3f07909947a9fb2daeb507b439d5f4f50 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 29 Sep 2009 15:34:58 -0700 Subject: added access methods for gps stuff --- gr-wxgui/src/python/forms/forms.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index 8dc58367d..19b30ffb0 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -194,15 +194,19 @@ class static_text(_form_base): @param label title label for this widget (optional) @param width the width of the form in px @param bold true to bold-ify the text (default=False) + @param units a suffix to add after the text @param converter forms.str_converter(), int_converter(), float_converter()... """ - def __init__(self, label='', width=-1, bold=False, converter=converters.str_converter(), **kwargs): + def __init__(self, label='', width=-1, bold=False, units='', converter=converters.str_converter(), **kwargs): + self._units = units _form_base.__init__(self, converter=converter, **kwargs) self._static_text = wx.StaticText(self._parent, size=wx.Size(width, -1)) if bold: make_bold(self._static_text) self._add_widget(self._static_text, label) - def _update(self, label): self._static_text.SetLabel(label); self._parent.Layout() + def _update(self, label): + if self._units: label += ' ' + self._units + self._static_text.SetLabel(label); self._parent.Layout() ######################################################################## # Text Box Form -- cgit From 69d09a30b38567c3ae33a227a3959aee43c721a7 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 6 Oct 2009 21:52:41 -0700 Subject: added bind to visible event function to callback when visibility changes within tabs --- gr-wxgui/src/python/common.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 9c97ce1ec..f355fd3ce 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -19,6 +19,41 @@ # Boston, MA 02110-1301, USA. # +import wx + +def bind_to_visible_event(win, callback): + """ + Bind a callback to a window when its visibility changes. + Specifically, callback when the window changes visibility + when a notebook tab event in one of the parents occurs. + @param win the wx window + @param callback a 1 param function + """ + #is the window visible in the hierarchy + def is_wx_window_visible(my_win): + while True: + parent = my_win.GetParent() + if not parent: return True #reached the top of the hierarchy + #if we are hidden, then finish, otherwise keep traversing up + if isinstance(parent, wx.Notebook) and parent.GetCurrentPage() != my_win: return False + my_win = parent + #call the callback, the arg is shown or not + def callback_factory(my_win, my_callback): + cache = [None] + def the_callback(*args): + visible = is_wx_window_visible(my_win) + if cache[0] != visible: my_callback(visible) + cache[0] = visible + return the_callback + handler = callback_factory(win, callback) + #bind the handler to all the parent notebooks + while win: + if isinstance(win, wx.Notebook): + win.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, handler) + if not win.GetParent(): + win.Bind(wx.EVT_ACTIVATE, handler) + win = win.GetParent() + #A macro to apply an index to a key index_key = lambda key, i: "%s_%d"%(key, i+1) -- cgit From 645ab0cfa4ab46e057ab4df74066ab434ad5b90a Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 6 Oct 2009 22:28:09 -0700 Subject: work on a special connect function that registers a callback --- gr-wxgui/src/python/common.py | 20 ++++++++++++++++++++ gr-wxgui/src/python/fftsink_gl.py | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index f355fd3ce..09ce44719 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -54,6 +54,26 @@ def bind_to_visible_event(win, callback): win.Bind(wx.EVT_ACTIVATE, handler) win = win.GetParent() +from gnuradio import gr + +def special_connect(source, sink, hb, win, size): + nulls = [gr.null_sink(size), gr.null_source(size)] + def callback(visible, init=False): + if not init: hb.lock() + if visible: + if not init: hb.disconnect(source, nulls[0]) + if not init: hb.disconnect(nulls[1], sink) + hb.connect(source, sink) + #hb.connect(nulls[1], nulls[0]) + else: + if not init: hb.disconnect(source, sink) + #if not init: hb.disconnect(nulls[1], nulls[0]) + hb.connect(source, nulls[0]) + hb.connect(nulls[1], sink) + if not init: hb.unlock() + callback(False, init=True) #initially connect + bind_to_visible_event(win, callback) + #A macro to apply an index to a key index_key = lambda key, i: "%s_%d"%(key, i+1) diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 3f0a93fc8..4edb11b4a 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -73,8 +73,6 @@ class _fft_sink_base(gr.hier_block2): ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) - #connect - self.connect(self, fft, sink) #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) @@ -106,6 +104,9 @@ class _fft_sink_base(gr.hier_block2): common.register_access_methods(self, self.win) setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS + #connect + common.special_connect(self, fft, hb=self, win=self.win, size=self._item_size) + self.connect(fft, sink) class fft_sink_f(_fft_sink_base): _fft_chain = blks2.logpwrfft_f -- cgit From bbecdd8372f57d49ad0046d9d096f322059005cb Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 01:47:38 -0700 Subject: working special connect for fftsink --- gr-wxgui/src/python/common.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 09ce44719..fe080aa4a 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -39,12 +39,7 @@ def bind_to_visible_event(win, callback): my_win = parent #call the callback, the arg is shown or not def callback_factory(my_win, my_callback): - cache = [None] - def the_callback(*args): - visible = is_wx_window_visible(my_win) - if cache[0] != visible: my_callback(visible) - cache[0] = visible - return the_callback + return lambda *args: my_callback(is_wx_window_visible(my_win)) handler = callback_factory(win, callback) #bind the handler to all the parent notebooks while win: @@ -57,19 +52,25 @@ def bind_to_visible_event(win, callback): from gnuradio import gr def special_connect(source, sink, hb, win, size): - nulls = [gr.null_sink(size), gr.null_source(size)] + nulls = list() + cache = [None] def callback(visible, init=False): + if visible == cache[0]: return + cache[0] = visible if not init: hb.lock() + print 'visible', visible if visible: - if not init: hb.disconnect(source, nulls[0]) - if not init: hb.disconnect(nulls[1], sink) + if not init: + hb.disconnect(source, nulls[0]) + hb.disconnect(nulls[1], nulls[2]) + hb.disconnect(nulls[2], sink) + while nulls: nulls.pop() hb.connect(source, sink) - #hb.connect(nulls[1], nulls[0]) else: if not init: hb.disconnect(source, sink) - #if not init: hb.disconnect(nulls[1], nulls[0]) + nulls.extend([gr.null_sink(size), gr.null_source(size), gr.head(size, 0)]) hb.connect(source, nulls[0]) - hb.connect(nulls[1], sink) + hb.connect(nulls[1], nulls[2], sink) if not init: hb.unlock() callback(False, init=True) #initially connect bind_to_visible_event(win, callback) -- cgit From af5e21e35cda1d9d3e4bd659364dafe181274064 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 09:48:24 -0700 Subject: setup special wxgui connect on sinks, needs testing --- gr-wxgui/src/python/common.py | 25 +++++++++++++++-- gr-wxgui/src/python/constsink_gl.py | 6 ++-- gr-wxgui/src/python/fftsink_gl.py | 5 ++-- gr-wxgui/src/python/histosink_gl.py | 6 ++-- gr-wxgui/src/python/numbersink2.py | 6 ++-- gr-wxgui/src/python/scopesink_gl.py | 49 +++++++++++++++++---------------- gr-wxgui/src/python/waterfallsink_gl.py | 6 ++-- 7 files changed, 63 insertions(+), 40 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index fe080aa4a..dca41c9a3 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -19,6 +19,9 @@ # Boston, MA 02110-1301, USA. # +################################################## +# conditional disconnections of wx flow graph +################################################## import wx def bind_to_visible_event(win, callback): @@ -51,14 +54,14 @@ def bind_to_visible_event(win, callback): from gnuradio import gr -def special_connect(source, sink, hb, win, size): +def conditional_connect(source, sink, hb, win, size): nulls = list() cache = [None] def callback(visible, init=False): if visible == cache[0]: return cache[0] = visible if not init: hb.lock() - print 'visible', visible + print 'visible', visible, source, sink if visible: if not init: hb.disconnect(source, nulls[0]) @@ -75,6 +78,24 @@ def special_connect(source, sink, hb, win, size): callback(False, init=True) #initially connect bind_to_visible_event(win, callback) +class wxgui_hb(object): + def wxgui_connect(self, *points): + """ + Use wxgui connect when the first point is the self source of the hb. + The win property of this object should be set to the wx window. + When this method tries to connect self to the next point, + it will conditionally make this connection based on the visibility state. + """ + try: + assert points[0] == self or points[0][0] == self + conditional_connect( + points[0], points[1], + win=self.win, hb=self, + size=self._hb.input_signature().sizeof_stream_item(0), + ) + if len(points[1:]) > 1: self.connect(*points[1:]) + except (AssertionError, IndexError): self.connect(*points) + #A macro to apply an index to a key index_key = lambda key, i: "%s_%d"%(key, i+1) diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py index b3a1625b0..91bc65d9f 100644 --- a/gr-wxgui/src/python/constsink_gl.py +++ b/gr-wxgui/src/python/constsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Constellation sink block (wrapper for old wxgui) ################################################## -class const_sink_c(gr.hier_block2): +class const_sink_c(gr.hier_block2, common.wxgui_hb): """ A constellation block with a gui window. """ @@ -94,8 +94,6 @@ class const_sink_c(gr.hier_block2): 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._costas, self._retime, agc, sd, sink) #controller def setter(p, k, x): p[k] = x self.controller = pubsub() @@ -131,5 +129,7 @@ class const_sink_c(gr.hier_block2): sample_rate_key=SAMPLE_RATE_KEY, ) common.register_access_methods(self, self.win) + #connect + self.wxgui_connect(self, self._costas, self._retime, agc, sd, sink) diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 4edb11b4a..9d683d697 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # FFT sink block (wrapper for old wxgui) ################################################## -class _fft_sink_base(gr.hier_block2): +class _fft_sink_base(gr.hier_block2, common.wxgui_hb): """ An fft block with real/complex inputs and a gui window. """ @@ -105,8 +105,7 @@ class _fft_sink_base(gr.hier_block2): setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS #connect - common.special_connect(self, fft, hb=self, win=self.win, size=self._item_size) - self.connect(fft, sink) + self.wxgui_connect(self, fft, sink) class fft_sink_f(_fft_sink_base): _fft_chain = blks2.logpwrfft_f diff --git a/gr-wxgui/src/python/histosink_gl.py b/gr-wxgui/src/python/histosink_gl.py index db6606e41..509f746be 100644 --- a/gr-wxgui/src/python/histosink_gl.py +++ b/gr-wxgui/src/python/histosink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # histo sink block (wrapper for old wxgui) ################################################## -class histo_sink_f(gr.hier_block2): +class histo_sink_f(gr.hier_block2, common.wxgui_hb): """ A histogram block and a gui window. """ @@ -56,8 +56,6 @@ class histo_sink_f(gr.hier_block2): histo = gr.histo_sink_f(msgq) histo.set_num_bins(num_bins) histo.set_frame_size(frame_size) - #connect - self.connect(self, histo) #controller self.controller = pubsub() self.controller.subscribe(NUM_BINS_KEY, histo.set_num_bins) @@ -79,6 +77,8 @@ class histo_sink_f(gr.hier_block2): msg_key=MSG_KEY, ) common.register_access_methods(self, self.win) + #connect + self.wxgui_connect(self, histo) # ---------------------------------------------------------------- # Standalone test app diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 7f853e6a4..011acdfd5 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Number sink block (wrapper for old wxgui) ################################################## -class _number_sink_base(gr.hier_block2): +class _number_sink_base(gr.hier_block2, common.wxgui_hb): """ An decimator block with a number window display """ @@ -81,8 +81,6 @@ class _number_sink_base(gr.hier_block2): avg = gr.single_pole_iir_filter_cc(1.0) msgq = gr.msg_queue(2) sink = gr.message_sink(self._item_size, msgq, True) - #connect - self.connect(self, sd, mult, add, avg, sink) #controller self.controller = pubsub() self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) @@ -118,6 +116,8 @@ class _number_sink_base(gr.hier_block2): common.register_access_methods(self, self.controller) #backwards compadibility self.set_show_gauge = self.win.show_gauges + #connect + self.wxgui_connect(self, sd, mult, add, avg, sink) class number_sink_f(_number_sink_base): _item_size = gr.sizeof_float diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index a5e3ca3ce..2882488e3 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -34,7 +34,7 @@ class ac_couple_block(gr.hier_block2): Mute the low pass filter to disable ac coupling. """ - def __init__(self, controller, ac_couple_key, ac_couple, sample_rate_key): + def __init__(self, controller, ac_couple_key, sample_rate_key): gr.hier_block2.__init__( self, "ac_couple", @@ -52,13 +52,13 @@ class ac_couple_block(gr.hier_block2): controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(0.05)) #initialize - controller[ac_couple_key] = ac_couple + controller[ac_couple_key] = controller[ac_couple_key] controller[sample_rate_key] = controller[sample_rate_key] ################################################## # Scope sink block (wrapper for old wxgui) ################################################## -class _scope_sink_base(gr.hier_block2): +class _scope_sink_base(gr.hier_block2, common.wxgui_hb): """ A scope block with a gui window. """ @@ -102,25 +102,10 @@ class _scope_sink_base(gr.hier_block2): self.controller.publish(TRIGGER_SLOPE_KEY, scope.get_trigger_slope) self.controller.subscribe(TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) self.controller.publish(TRIGGER_CHANNEL_KEY, scope.get_trigger_channel) - #connect - if self._real: - for i in range(num_inputs): - self.connect( - (self, i), - ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, i), ac_couple, SAMPLE_RATE_KEY), - (scope, i), - ) - else: - for i in range(num_inputs): - c2f = gr.complex_to_float() - self.connect((self, i), c2f) - for j in range(2): - self.connect( - (c2f, j), - ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), ac_couple, SAMPLE_RATE_KEY), - (scope, 2*i+j), - ) - num_inputs *= 2 + actual_num_inputs = self._real and num_inputs or num_inputs*2 + #init ac couple + for i in range(actual_num_inputs): + self.controller[common.index_key(AC_COUPLE_KEY, i)] = ac_couple #start input watcher common.input_watcher(msgq, self.controller, MSG_KEY) #create window @@ -130,7 +115,7 @@ class _scope_sink_base(gr.hier_block2): size=size, title=title, frame_rate=frame_rate, - num_inputs=num_inputs, + num_inputs=actual_num_inputs, sample_rate_key=SAMPLE_RATE_KEY, t_scale=t_scale, v_scale=v_scale, @@ -144,6 +129,24 @@ class _scope_sink_base(gr.hier_block2): msg_key=MSG_KEY, ) common.register_access_methods(self, self.win) + #connect + if self._real: + for i in range(num_inputs): + self.wxgui_connect( + (self, i), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, i), SAMPLE_RATE_KEY), + (scope, i), + ) + else: + for i in range(num_inputs): + c2f = gr.complex_to_float() + self.wxgui_connect((self, i), c2f) + for j in range(2): + self.connect( + (c2f, j), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), SAMPLE_RATE_KEY), + (scope, 2*i+j), + ) class scope_sink_f(_scope_sink_base): _item_size = gr.sizeof_float diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index 2d4c959f8..37844399e 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Waterfall sink block (wrapper for old wxgui) ################################################## -class _waterfall_sink_base(gr.hier_block2): +class _waterfall_sink_base(gr.hier_block2, common.wxgui_hb): """ An fft block with real/complex inputs and a gui window. """ @@ -73,8 +73,6 @@ class _waterfall_sink_base(gr.hier_block2): ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) - #connect - self.connect(self, fft, sink) #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) @@ -110,6 +108,8 @@ class _waterfall_sink_base(gr.hier_block2): ) common.register_access_methods(self, self.win) setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS + #connect + self.wxgui_connect(self, fft, sink) class waterfall_sink_f(_waterfall_sink_base): _fft_chain = blks2.logpwrfft_f -- cgit From 1e8aefecdc607de260c1797aba27eb1598b9e7b8 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 11:52:33 -0700 Subject: making use of update ui event --- gr-wxgui/src/python/common.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index dca41c9a3..04875e7f2 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -28,7 +28,7 @@ def bind_to_visible_event(win, callback): """ Bind a callback to a window when its visibility changes. Specifically, callback when the window changes visibility - when a notebook tab event in one of the parents occurs. + when a update ui event in the window occurs. @param win the wx window @param callback a 1 param function """ @@ -45,23 +45,18 @@ def bind_to_visible_event(win, callback): return lambda *args: my_callback(is_wx_window_visible(my_win)) handler = callback_factory(win, callback) #bind the handler to all the parent notebooks - while win: - if isinstance(win, wx.Notebook): - win.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, handler) - if not win.GetParent(): - win.Bind(wx.EVT_ACTIVATE, handler) - win = win.GetParent() + win.Bind(wx.EVT_UPDATE_UI, handler) from gnuradio import gr -def conditional_connect(source, sink, hb, win, size): +def conditional_connect_callback_factory(source, sink, hb, win, size): nulls = list() cache = [None] def callback(visible, init=False): if visible == cache[0]: return cache[0] = visible if not init: hb.lock() - print 'visible', visible, source, sink + #print 'visible', visible, source, sink if visible: if not init: hb.disconnect(source, nulls[0]) @@ -75,8 +70,12 @@ def conditional_connect(source, sink, hb, win, size): hb.connect(source, nulls[0]) hb.connect(nulls[1], nulls[2], sink) if not init: hb.unlock() - callback(False, init=True) #initially connect - bind_to_visible_event(win, callback) + return callback + +def conditional_connect(source, sink, hb, win, size): + handler = conditional_connect_callback_factory(source, sink, hb, win, size) + handler(False, init=True) #initially connect + bind_to_visible_event(win, handler) class wxgui_hb(object): def wxgui_connect(self, *points): -- cgit From 64a5167c53eeb2d2a657507397b402abe22f67b6 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 18:01:56 -0700 Subject: moved the wxgui connect helper functions into the wrapper class --- gr-wxgui/src/python/common.py | 137 ++++++++++++++++++++++++------------------ 1 file changed, 80 insertions(+), 57 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 04875e7f2..aae4d63cf 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -23,78 +23,101 @@ # conditional disconnections of wx flow graph ################################################## import wx - -def bind_to_visible_event(win, callback): - """ - Bind a callback to a window when its visibility changes. - Specifically, callback when the window changes visibility - when a update ui event in the window occurs. - @param win the wx window - @param callback a 1 param function - """ - #is the window visible in the hierarchy - def is_wx_window_visible(my_win): - while True: - parent = my_win.GetParent() - if not parent: return True #reached the top of the hierarchy - #if we are hidden, then finish, otherwise keep traversing up - if isinstance(parent, wx.Notebook) and parent.GetCurrentPage() != my_win: return False - my_win = parent - #call the callback, the arg is shown or not - def callback_factory(my_win, my_callback): - return lambda *args: my_callback(is_wx_window_visible(my_win)) - handler = callback_factory(win, callback) - #bind the handler to all the parent notebooks - win.Bind(wx.EVT_UPDATE_UI, handler) - from gnuradio import gr -def conditional_connect_callback_factory(source, sink, hb, win, size): - nulls = list() - cache = [None] - def callback(visible, init=False): - if visible == cache[0]: return - cache[0] = visible - if not init: hb.lock() - #print 'visible', visible, source, sink - if visible: - if not init: - hb.disconnect(source, nulls[0]) - hb.disconnect(nulls[1], nulls[2]) - hb.disconnect(nulls[2], sink) - while nulls: nulls.pop() - hb.connect(source, sink) - else: - if not init: hb.disconnect(source, sink) - nulls.extend([gr.null_sink(size), gr.null_source(size), gr.head(size, 0)]) - hb.connect(source, nulls[0]) - hb.connect(nulls[1], nulls[2], sink) - if not init: hb.unlock() - return callback - -def conditional_connect(source, sink, hb, win, size): - handler = conditional_connect_callback_factory(source, sink, hb, win, size) - handler(False, init=True) #initially connect - bind_to_visible_event(win, handler) - class wxgui_hb(object): + """ + The wxgui hier block helper/wrapper class: + A hier block should inherit from this class to make use of the wxgui connect method. + To use, call wxgui_connect in place of regular connect; self.win must be defined. + The implementation will conditionally connect or disconnect the self (source) of the hb. + This condition depends on weather or not the window is visible with the parent notebooks. + This condition will be re-checked on every ui update event. + """ + def wxgui_connect(self, *points): """ Use wxgui connect when the first point is the self source of the hb. The win property of this object should be set to the wx window. When this method tries to connect self to the next point, it will conditionally make this connection based on the visibility state. + All other points will be connected normally. """ try: assert points[0] == self or points[0][0] == self - conditional_connect( - points[0], points[1], - win=self.win, hb=self, - size=self._hb.input_signature().sizeof_stream_item(0), - ) + self._conditional_connect(points[0], points[1]) if len(points[1:]) > 1: self.connect(*points[1:]) except (AssertionError, IndexError): self.connect(*points) + def _conditional_connect(self, source, sink): + """ + Create a handler for visibility changes. + Initially call the handler to setup the fg. + Bind the handler to the visibility meta event. + """ + handler = self._conditional_connect_handler_factory( + source=source, sink=sink, win=self.win, hb=self, + size=self._hb.input_signature().sizeof_stream_item(0), + ) + handler(False, init=True) #initially connect + self._bind_to_visible_event(win=self.win, handler=handler) + + @staticmethod + def _conditional_connect_handler_factory(source, sink, hb, win, size): + """ + Create a function that will handle the re-connections based on a flag. + The current state of the connection is stored in the namespace. + """ + nulls = list() + cache = [None] + def callback(visible, init=False): + if visible == cache[0]: return + cache[0] = visible + if not init: hb.lock() + #print 'visible', visible, source, sink + if visible: + if not init: + hb.disconnect(source, nulls[0]) + hb.disconnect(nulls[1], nulls[2]) + hb.disconnect(nulls[2], sink) + while nulls: nulls.pop() + hb.connect(source, sink) + else: + if not init: hb.disconnect(source, sink) + nulls.extend([gr.null_sink(size), gr.null_source(size), gr.head(size, 0)]) + hb.connect(source, nulls[0]) + hb.connect(nulls[1], nulls[2], sink) + if not init: hb.unlock() + return callback + + @staticmethod + def _bind_to_visible_event(win, handler): + """ + Bind a handler to a window when its visibility changes. + Specifically, call the handler when the window visibility changes. + This condition is checked on every update ui event. + @param win the wx window + @param handler a function of 1 param + """ + #is the window visible in the hierarchy + def is_wx_window_visible(my_win): + while True: + parent = my_win.GetParent() + if not parent: return True #reached the top of the hierarchy + #if we are hidden, then finish, otherwise keep traversing up + if isinstance(parent, wx.Notebook) and parent.GetCurrentPage() != my_win: return False + my_win = parent + #call the handler, the arg is shown or not + def handler_factory(my_win, my_handler): + return lambda *args: my_handler(is_wx_window_visible(my_win)) + handler = handler_factory(win, handler) + #bind the handler to all the parent notebooks + win.Bind(wx.EVT_UPDATE_UI, handler) + +################################################## +# Helpful Functions +################################################## + #A macro to apply an index to a key index_key = lambda key, i: "%s_%d"%(key, i+1) -- cgit From dfa5e0a5bccb6b3539c1230281b5bec60196f8a2 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 7 Oct 2009 18:47:21 -0700 Subject: simplify some params --- gr-wxgui/src/python/common.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index aae4d63cf..9bf3094f2 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -55,39 +55,37 @@ class wxgui_hb(object): Initially call the handler to setup the fg. Bind the handler to the visibility meta event. """ - handler = self._conditional_connect_handler_factory( - source=source, sink=sink, win=self.win, hb=self, - size=self._hb.input_signature().sizeof_stream_item(0), - ) + handler = self._conditional_connect_handler_factory(source=source, sink=sink) handler(False, init=True) #initially connect self._bind_to_visible_event(win=self.win, handler=handler) - @staticmethod - def _conditional_connect_handler_factory(source, sink, hb, win, size): + def _conditional_connect_handler_factory(self, source, sink): """ Create a function that will handle the re-connections based on a flag. The current state of the connection is stored in the namespace. + !!!#TODO This entire method could be replaced with a mute block that starves the stream. """ nulls = list() cache = [None] + size = self._hb.input_signature().sizeof_stream_item(0) def callback(visible, init=False): if visible == cache[0]: return cache[0] = visible - if not init: hb.lock() + if not init: self.lock() #print 'visible', visible, source, sink if visible: if not init: - hb.disconnect(source, nulls[0]) - hb.disconnect(nulls[1], nulls[2]) - hb.disconnect(nulls[2], sink) + self.disconnect(source, nulls[0]) + self.disconnect(nulls[1], nulls[2]) + self.disconnect(nulls[2], sink) while nulls: nulls.pop() - hb.connect(source, sink) + self.connect(source, sink) else: - if not init: hb.disconnect(source, sink) + if not init: self.disconnect(source, sink) nulls.extend([gr.null_sink(size), gr.null_source(size), gr.head(size, 0)]) - hb.connect(source, nulls[0]) - hb.connect(nulls[1], nulls[2], sink) - if not init: hb.unlock() + self.connect(source, nulls[0]) + self.connect(nulls[1], nulls[2], sink) + if not init: self.unlock() return callback @staticmethod -- cgit From 23fa9183aca592865c0652f87709950af5ccd011 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 8 Oct 2009 20:12:34 -0700 Subject: point label transpareny, horizontal offset, and toggle on/off capability --- gr-wxgui/src/python/plotter/common.py | 1 + gr-wxgui/src/python/plotter/grid_plotter_base.py | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/common.py b/gr-wxgui/src/python/plotter/common.py index 7699986aa..4c50cd787 100644 --- a/gr-wxgui/src/python/plotter/common.py +++ b/gr-wxgui/src/python/plotter/common.py @@ -102,6 +102,7 @@ class point_label_thread(threading.Thread, mutex): #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())) #start the thread threading.Thread.__init__(self) self.start() diff --git a/gr-wxgui/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py index 39bed1811..a9bd02731 100644 --- a/gr-wxgui/src/python/plotter/grid_plotter_base.py +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -36,8 +36,9 @@ AXIS_LABEL_PADDING = 5 TICK_LABEL_PADDING = 5 TITLE_LABEL_PADDING = 7 POINT_LABEL_FONT_SIZE = 8 -POINT_LABEL_COLOR_SPEC = (1, 1, .5) +POINT_LABEL_COLOR_SPEC = (1, 1, 0.5, 0.75) POINT_LABEL_PADDING = 3 +POINT_LABEL_OFFSET = 10 GRID_LINE_DASH_LEN = 4 ################################################## @@ -395,8 +396,12 @@ class grid_plotter_base(plotter_base): 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.glColor3f(*POINT_LABEL_COLOR_SPEC) - if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + 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)) -- cgit From 38d5389f3054164a2f04d6e4e8fe381aa5ee03fc Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Thu, 8 Oct 2009 21:46:53 -0700 Subject: using gr copy in the wxgui connect, added gr copy to grc xml --- gr-wxgui/src/python/common.py | 56 +++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 9bf3094f2..fa11b3152 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -30,7 +30,7 @@ class wxgui_hb(object): The wxgui hier block helper/wrapper class: A hier block should inherit from this class to make use of the wxgui connect method. To use, call wxgui_connect in place of regular connect; self.win must be defined. - The implementation will conditionally connect or disconnect the self (source) of the hb. + The implementation will conditionally enable the copy block after the source (self). This condition depends on weather or not the window is visible with the parent notebooks. This condition will be re-checked on every ui update event. """ @@ -45,47 +45,29 @@ class wxgui_hb(object): """ try: assert points[0] == self or points[0][0] == self - self._conditional_connect(points[0], points[1]) - if len(points[1:]) > 1: self.connect(*points[1:]) - except (AssertionError, IndexError): self.connect(*points) + copy = gr.copy(self._hb.input_signature().sizeof_stream_item(0)) + handler = self._handler_factory(copy.set_enabled) + handler(False) #initially disable the copy block + self._bind_to_visible_event(win=self.win, handler=handler) + points = list(points) + points.insert(1, copy) #insert the copy block into the chain + except (AssertionError, IndexError): pass + self.connect(*points) #actually connect the blocks - def _conditional_connect(self, source, sink): - """ - Create a handler for visibility changes. - Initially call the handler to setup the fg. - Bind the handler to the visibility meta event. - """ - handler = self._conditional_connect_handler_factory(source=source, sink=sink) - handler(False, init=True) #initially connect - self._bind_to_visible_event(win=self.win, handler=handler) - - def _conditional_connect_handler_factory(self, source, sink): + @staticmethod + def _handler_factory(handler): """ - Create a function that will handle the re-connections based on a flag. - The current state of the connection is stored in the namespace. - !!!#TODO This entire method could be replaced with a mute block that starves the stream. + Create a function that will cache the visibility flag, + and only call the handler when that flag changes. + @param handler the function to call on a change + @return a function of 1 argument """ - nulls = list() cache = [None] - size = self._hb.input_signature().sizeof_stream_item(0) - def callback(visible, init=False): - if visible == cache[0]: return + def callback(visible): + if cache[0] == visible: return cache[0] = visible - if not init: self.lock() - #print 'visible', visible, source, sink - if visible: - if not init: - self.disconnect(source, nulls[0]) - self.disconnect(nulls[1], nulls[2]) - self.disconnect(nulls[2], sink) - while nulls: nulls.pop() - self.connect(source, sink) - else: - if not init: self.disconnect(source, sink) - nulls.extend([gr.null_sink(size), gr.null_source(size), gr.head(size, 0)]) - self.connect(source, nulls[0]) - self.connect(nulls[1], nulls[2], sink) - if not init: self.unlock() + #print visible, handler + handler(visible) return callback @staticmethod -- cgit From a154cc5c12ccf65b480ae86e0c984c4a66ad69a3 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 9 Oct 2009 13:41:15 -0700 Subject: registered key to hide/show control panel in wxgui windows --- gr-wxgui/src/python/const_window.py | 2 ++ gr-wxgui/src/python/constants.py | 1 + gr-wxgui/src/python/fft_window.py | 2 ++ gr-wxgui/src/python/histo_window.py | 2 ++ gr-wxgui/src/python/number_window.py | 2 ++ gr-wxgui/src/python/scope_window.py | 2 ++ gr-wxgui/src/python/waterfall_window.py | 2 ++ 7 files changed, 13 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index b128a4a98..f7c7caf07 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -64,6 +64,8 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = forms.static_box_sizer( parent=self, label='Options', bold=True, orient=wx.VERTICAL, diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 8ff7fa8fe..825f71c32 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -69,3 +69,4 @@ MINIMUM_KEY = 'minimum' NUM_BINS_KEY = 'num_bins' FRAME_SIZE_KEY = 'frame_size' CHANNEL_OPTIONS_KEY = 'channel_options' +SHOW_CONTROL_PANEL_KEY = 'show_control_panel' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index e025c28dd..4ee5520f7 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -64,6 +64,8 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() #checkboxes for average and peak hold diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py index 5f434d70e..a1b520f9c 100644 --- a/gr-wxgui/src/python/histo_window.py +++ b/gr-wxgui/src/python/histo_window.py @@ -52,6 +52,8 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = wx.BoxSizer(wx.VERTICAL) SIZE = (100, -1) control_box = forms.static_box_sizer( diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index 8a8249764..ab9d1ebc0 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -58,6 +58,8 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold control_box.AddStretchSpacer() diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 449046402..9346a73d8 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -82,6 +82,8 @@ class control_panel(wx.Panel): WIDTH = 90 self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = wx.BoxSizer(wx.VERTICAL) ################################################## # Axes Options diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 28e67a830..611138dfb 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -63,6 +63,8 @@ class control_panel(wx.Panel): """ self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + parent[SHOW_CONTROL_PANEL_KEY] = True + parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() options_box = forms.static_box_sizer( -- cgit From 9a9582715d87521b53b1f0bb64dc8e133ce01954 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 9 Oct 2009 15:35:43 -0700 Subject: tweaks to scope autoscaling --- gr-wxgui/src/python/common.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index fa11b3152..a03c36d88 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -213,12 +213,13 @@ def get_min_max(samples): @param samples the array of real values @return a tuple of min, max """ - scale_factor = 3 + factor = 2.0 mean = numpy.average(samples) - rms = numpy.max([scale_factor*((numpy.sum((samples-mean)**2)/len(samples))**.5), .1]) - min_val = mean - rms - max_val = mean + rms - return min_val, max_val + std = numpy.std(samples) + fft = numpy.abs(numpy.fft.fft(samples - mean)) + envelope = 2*numpy.max(fft)/len(samples) + ampl = max(std, envelope) or 0.1 + return mean - factor*ampl, mean + factor*ampl def get_min_max_fft(fft_samps): """ -- cgit From 25a8f7a8f09f37ea6f31ce02523170c64d71a561 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Fri, 9 Oct 2009 16:02:22 -0700 Subject: use clean numbers for waterfall ref and range --- gr-wxgui/src/python/waterfall_window.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 611138dfb..b7904e4d9 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -41,6 +41,7 @@ DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30) DEFAULT_WIN_SIZE = (600, 300) DIV_LEVELS = (1, 2, 5, 10, 20) MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200 +DYNAMIC_RANGE_STEP = 10. COLOR_MODES = ( ('RGB1', 'rgb1'), ('RGB2', 'rgb2'), @@ -145,13 +146,13 @@ class control_panel(wx.Panel): def _on_clear_button(self, event): self.parent[NUM_LINES_KEY] = self.parent[NUM_LINES_KEY] def _on_incr_dynamic_range(self, event): - self.parent[DYNAMIC_RANGE_KEY] = min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE) + self.parent[DYNAMIC_RANGE_KEY] = min(MAX_DYNAMIC_RANGE, common.get_clean_incr(self.parent[DYNAMIC_RANGE_KEY])) def _on_decr_dynamic_range(self, event): - self.parent[DYNAMIC_RANGE_KEY] = max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE) + self.parent[DYNAMIC_RANGE_KEY] = max(MIN_DYNAMIC_RANGE, common.get_clean_decr(self.parent[DYNAMIC_RANGE_KEY])) def _on_incr_ref_level(self, event): - self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1 + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]/DYNAMIC_RANGE_STEP def _on_decr_ref_level(self, event): - self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1 + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]/DYNAMIC_RANGE_STEP def _on_incr_time_scale(self, event): old_rate = self.parent[FRAME_RATE_KEY] self.parent[FRAME_RATE_KEY] *= 0.75 @@ -241,8 +242,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub): if not len(self.samples): return min_level, max_level = common.get_min_max_fft(self.samples) #set the range and level - self[REF_LEVEL_KEY] = max_level - self[DYNAMIC_RANGE_KEY] = max_level - min_level + self[DYNAMIC_RANGE_KEY] = common.get_clean_num(max_level - min_level) + self[REF_LEVEL_KEY] = DYNAMIC_RANGE_STEP*round(.5+max_level/DYNAMIC_RANGE_STEP) def handle_msg(self, msg): """ -- cgit From e9e2ce03af5fb68e168be4e68ef4183a7eb775d5 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 12 Oct 2009 18:35:10 -0700 Subject: fix so all handlers get called on event --- gr-wxgui/src/python/common.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index fa11b3152..a75f6810d 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -89,7 +89,10 @@ class wxgui_hb(object): my_win = parent #call the handler, the arg is shown or not def handler_factory(my_win, my_handler): - return lambda *args: my_handler(is_wx_window_visible(my_win)) + def callback(evt): + my_handler(is_wx_window_visible(my_win)) + evt.Skip() #skip so all bound handlers are called + return callback handler = handler_factory(win, handler) #bind the handler to all the parent notebooks win.Bind(wx.EVT_UPDATE_UI, handler) -- cgit From 538e6fa4380b55ed65dfab275c75302946b228bf Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Mon, 12 Oct 2009 18:35:10 -0700 Subject: fix so all handlers get called on event --- gr-wxgui/src/python/common.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index fa11b3152..a75f6810d 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -89,7 +89,10 @@ class wxgui_hb(object): my_win = parent #call the handler, the arg is shown or not def handler_factory(my_win, my_handler): - return lambda *args: my_handler(is_wx_window_visible(my_win)) + def callback(evt): + my_handler(is_wx_window_visible(my_win)) + evt.Skip() #skip so all bound handlers are called + return callback handler = handler_factory(win, handler) #bind the handler to all the parent notebooks win.Bind(wx.EVT_UPDATE_UI, handler) -- cgit From 76aa7a496c3d5c2a40b7ff190d42b25e51d0cbc0 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sun, 18 Oct 2009 14:25:55 -0700 Subject: added v offset option to scope --- gr-wxgui/src/python/scope_window.py | 5 +++-- gr-wxgui/src/python/scopesink_gl.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 9346a73d8..08a025e1e 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -376,6 +376,7 @@ class scope_window(wx.Panel, pubsub.pubsub): sample_rate_key, t_scale, v_scale, + v_offset, xy_mode, ac_couple_key, trigger_level_key, @@ -415,8 +416,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self[X_PER_DIV_KEY] = v_scale self[Y_PER_DIV_KEY] = v_scale self[T_OFF_KEY] = 0 - self[X_OFF_KEY] = 0 - self[Y_OFF_KEY] = 0 + self[X_OFF_KEY] = v_offset + self[Y_OFF_KEY] = v_offset self[T_DIVS_KEY] = 8 self[X_DIVS_KEY] = 8 self[Y_DIVS_KEY] = 8 diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 2882488e3..358361de6 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -71,6 +71,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): size=scope_window.DEFAULT_WIN_SIZE, v_scale=0, t_scale=0, + v_offset=0, xy_mode=False, ac_couple=False, num_inputs=1, @@ -119,6 +120,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): sample_rate_key=SAMPLE_RATE_KEY, t_scale=t_scale, v_scale=v_scale, + v_offset=v_offset, xy_mode=xy_mode, ac_couple_key=AC_COUPLE_KEY, trigger_level_key=TRIGGER_LEVEL_KEY, -- cgit From 230e062e51d43f389520207cf147838c666a1f21 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 28 Oct 2009 16:17:24 -0700 Subject: Added window option to wxgui fft and waterfall sink. Added rectangular window function to window.py. Average stays hidden in waterfall, fft, and numbersink wrappers (only avg_alpha shows/hides). Fixed options in waterfall wrapper to model after fft and numbersink average params. --- gr-wxgui/src/python/fftsink_gl.py | 3 +++ gr-wxgui/src/python/fftsink_nongl.py | 4 ++-- gr-wxgui/src/python/waterfallsink_gl.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 9d683d697..9b2f9a487 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -52,6 +52,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): title='', size=fft_window.DEFAULT_WIN_SIZE, peak_hold=False, + win=None, + **kwargs #do not end with a comma ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate @@ -70,6 +72,7 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): ref_scale=ref_scale, avg_alpha=avg_alpha, average=average, + win=win, ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index ca5e91fdb..b3a48a716 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -93,7 +93,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, **kwargs): gr.hier_block2.__init__(self, "fft_sink_f", gr.io_signature(1, 1, gr.sizeof_float), @@ -136,7 +136,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, **kwargs): gr.hier_block2.__init__(self, "fft_sink_c", gr.io_signature(1, 1, gr.sizeof_gr_complex), diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index 37844399e..c6b0df65c 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -51,6 +51,7 @@ class _waterfall_sink_base(gr.hier_block2, common.wxgui_hb): ref_scale=2.0, dynamic_range=80, num_lines=256, + win=None, **kwargs #do not end with a comma ): #ensure avg alpha @@ -70,6 +71,7 @@ class _waterfall_sink_base(gr.hier_block2, common.wxgui_hb): ref_scale=ref_scale, avg_alpha=avg_alpha, average=average, + win=win, ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) -- cgit From 92f2da3450b3ae0a5d16d322ad42c7812c4ffc62 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 15 Oct 2009 09:11:59 -0700 Subject: Created skeleton wxgui term window component --- gr-wxgui/src/python/Makefile.am | 2 ++ gr-wxgui/src/python/term_window.py | 28 ++++++++++++++++++++++++++++ gr-wxgui/src/python/termsink.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 gr-wxgui/src/python/term_window.py create mode 100644 gr-wxgui/src/python/termsink.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index e06298a2d..0b4550b38 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -53,6 +53,8 @@ ourpython_PYTHON = \ scopesink_nongl.py \ scopesink_gl.py \ scope_window.py \ + term_window.py \ + termsink.py \ waterfallsink2.py \ waterfallsink_nongl.py \ waterfallsink_gl.py \ diff --git a/gr-wxgui/src/python/term_window.py b/gr-wxgui/src/python/term_window.py new file mode 100644 index 000000000..cae19c07a --- /dev/null +++ b/gr-wxgui/src/python/term_window.py @@ -0,0 +1,28 @@ +# +# 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 pubsub + +class term_window(wx.Panel, pubsub.pubsub): + def __init__(self, parent): + pubsub.pubsub.__init__(self) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) diff --git a/gr-wxgui/src/python/termsink.py b/gr-wxgui/src/python/termsink.py new file mode 100644 index 000000000..92aba47f4 --- /dev/null +++ b/gr-wxgui/src/python/termsink.py @@ -0,0 +1,38 @@ +# +# 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 term_window +import common +from gnuradio import gr + +class termsink(gr.hier_block2, common.wxgui_hb): + def __init__(self,parent): + gr.hier_block2.__init__( + self, + "termsink", + gr.io_signature(0, 0, 0), + gr.io_signature(0, 0, 0), + ) + + self.win = term_window.term_window( + parent=parent, + ) + -- cgit From 00613b260a36923509eab1811256815269dcd99c Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 15 Oct 2009 11:00:30 -0700 Subject: Add placeholder panel for console, use old style window size --- gr-wxgui/src/python/term_window.py | 25 +++++++++++++++++++++++-- gr-wxgui/src/python/termsink.py | 7 +++++-- 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/term_window.py b/gr-wxgui/src/python/term_window.py index cae19c07a..8658e54a6 100644 --- a/gr-wxgui/src/python/term_window.py +++ b/gr-wxgui/src/python/term_window.py @@ -22,7 +22,28 @@ import wx import pubsub +DEFAULT_WIN_SIZE = (600, 300) + class term_window(wx.Panel, pubsub.pubsub): - def __init__(self, parent): + def __init__(self, + parent, + size, + ): + pubsub.pubsub.__init__(self) - wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, + parent, + size=size, + style=wx.SIMPLE_BORDER, + ) + + self.text_ctrl = wx.TextCtrl(self, + wx.ID_ANY, + value="BOO", + size=size, + style=wx.TE_MULTILINE|wx.TE_READONLY, + ) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(self.text_ctrl, 1, wx.EXPAND) + self.SetSizerAndFit(main_sizer) diff --git a/gr-wxgui/src/python/termsink.py b/gr-wxgui/src/python/termsink.py index 92aba47f4..2c583b115 100644 --- a/gr-wxgui/src/python/termsink.py +++ b/gr-wxgui/src/python/termsink.py @@ -24,7 +24,10 @@ import common from gnuradio import gr class termsink(gr.hier_block2, common.wxgui_hb): - def __init__(self,parent): + def __init__(self, + parent, + ): + gr.hier_block2.__init__( self, "termsink", @@ -34,5 +37,5 @@ class termsink(gr.hier_block2, common.wxgui_hb): self.win = term_window.term_window( parent=parent, + size=term_window.DEFAULT_WIN_SIZE, ) - -- cgit From ab901e7d4cb6e5e8b1b46dac8a7af74acf72cb8c Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Wed, 21 Oct 2009 17:11:03 -0700 Subject: Basic terminal window that takes raw text on input msgq and appends it Works, but needs "--line-buffered" mode for GR buffering between blocks --- gr-wxgui/src/python/term_window.py | 29 +++++++++++++++++++++++++---- gr-wxgui/src/python/termsink.py | 26 ++++++++++++++------------ 2 files changed, 39 insertions(+), 16 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/term_window.py b/gr-wxgui/src/python/term_window.py index 8658e54a6..77270b1f3 100644 --- a/gr-wxgui/src/python/term_window.py +++ b/gr-wxgui/src/python/term_window.py @@ -20,17 +20,27 @@ # import wx -import pubsub DEFAULT_WIN_SIZE = (600, 300) +APPEND_EVENT = wx.NewEventType() +EVT_APPEND_EVENT = wx.PyEventBinder(APPEND_EVENT, 0) -class term_window(wx.Panel, pubsub.pubsub): +class AppendEvent(wx.PyEvent): + def __init__(self, text): + wx.PyEvent.__init__(self) + self.SetEventType(APPEND_EVENT) + self.text = text + + def Clone(self): + self.__class__(self.GetId()) + + +class term_window(wx.Panel): def __init__(self, parent, size, ): - pubsub.pubsub.__init__(self) wx.Panel.__init__(self, parent, size=size, @@ -39,7 +49,7 @@ class term_window(wx.Panel, pubsub.pubsub): self.text_ctrl = wx.TextCtrl(self, wx.ID_ANY, - value="BOO", + value="", size=size, style=wx.TE_MULTILINE|wx.TE_READONLY, ) @@ -47,3 +57,14 @@ class term_window(wx.Panel, pubsub.pubsub): main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(self.text_ctrl, 1, wx.EXPAND) self.SetSizerAndFit(main_sizer) + + EVT_APPEND_EVENT(self, self.evt_append) + + def append_text(self, text): + evt = AppendEvent(text) + wx.PostEvent(self, evt) + del evt + + def evt_append(self, evt): + print "appending", len(evt.text), "bytes" + self.text_ctrl.AppendText(evt.text) diff --git a/gr-wxgui/src/python/termsink.py b/gr-wxgui/src/python/termsink.py index 2c583b115..addfa5810 100644 --- a/gr-wxgui/src/python/termsink.py +++ b/gr-wxgui/src/python/termsink.py @@ -20,22 +20,24 @@ # import term_window -import common -from gnuradio import gr +from gnuradio import gru -class termsink(gr.hier_block2, common.wxgui_hb): +class termsink(object): def __init__(self, parent, + msgq, + size=term_window.DEFAULT_WIN_SIZE, ): - - gr.hier_block2.__init__( - self, - "termsink", - gr.io_signature(0, 0, 0), - gr.io_signature(0, 0, 0), - ) - + self.win = term_window.term_window( parent=parent, - size=term_window.DEFAULT_WIN_SIZE, + size=size, ) + + self.runner = gru.msgq_runner(msgq, self.handle_msg) + + def handle_msg(self, msg): + # Just append text for now + text = msg.to_string() + print "handle_msg: received", len(text), "bytes" + self.win.append_text(text) -- cgit From 6f0685769f7a7728a779502435cd2dd17b8d65e2 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 29 Oct 2009 07:26:39 -0700 Subject: Consolidated termsink into one class --- gr-wxgui/src/python/Makefile.am | 11 +++--- gr-wxgui/src/python/term_window.py | 70 -------------------------------------- gr-wxgui/src/python/termsink.py | 56 ++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 86 deletions(-) delete mode 100644 gr-wxgui/src/python/term_window.py (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 0b4550b38..dfa156f62 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -1,23 +1,23 @@ # # Copyright 2004,2005,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. -# +# include $(top_srcdir)/Makefile.common @@ -53,7 +53,6 @@ ourpython_PYTHON = \ scopesink_nongl.py \ scopesink_gl.py \ scope_window.py \ - term_window.py \ termsink.py \ waterfallsink2.py \ waterfallsink_nongl.py \ diff --git a/gr-wxgui/src/python/term_window.py b/gr-wxgui/src/python/term_window.py deleted file mode 100644 index 77270b1f3..000000000 --- a/gr-wxgui/src/python/term_window.py +++ /dev/null @@ -1,70 +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 - -DEFAULT_WIN_SIZE = (600, 300) -APPEND_EVENT = wx.NewEventType() -EVT_APPEND_EVENT = wx.PyEventBinder(APPEND_EVENT, 0) - -class AppendEvent(wx.PyEvent): - def __init__(self, text): - wx.PyEvent.__init__(self) - self.SetEventType(APPEND_EVENT) - self.text = text - - def Clone(self): - self.__class__(self.GetId()) - - -class term_window(wx.Panel): - def __init__(self, - parent, - size, - ): - - wx.Panel.__init__(self, - parent, - size=size, - style=wx.SIMPLE_BORDER, - ) - - self.text_ctrl = wx.TextCtrl(self, - wx.ID_ANY, - value="", - size=size, - style=wx.TE_MULTILINE|wx.TE_READONLY, - ) - - main_sizer = wx.BoxSizer(wx.VERTICAL) - main_sizer.Add(self.text_ctrl, 1, wx.EXPAND) - self.SetSizerAndFit(main_sizer) - - EVT_APPEND_EVENT(self, self.evt_append) - - def append_text(self, text): - evt = AppendEvent(text) - wx.PostEvent(self, evt) - del evt - - def evt_append(self, evt): - print "appending", len(evt.text), "bytes" - self.text_ctrl.AppendText(evt.text) diff --git a/gr-wxgui/src/python/termsink.py b/gr-wxgui/src/python/termsink.py index addfa5810..45a94e396 100644 --- a/gr-wxgui/src/python/termsink.py +++ b/gr-wxgui/src/python/termsink.py @@ -19,25 +19,61 @@ # Boston, MA 02110-1301, USA. # -import term_window from gnuradio import gru +import wx -class termsink(object): +DEFAULT_WIN_SIZE = (600, 300) +APPEND_EVENT = wx.NewEventType() +EVT_APPEND_EVENT = wx.PyEventBinder(APPEND_EVENT, 0) + +class AppendEvent(wx.PyEvent): + def __init__(self, text): + wx.PyEvent.__init__(self) + self.SetEventType(APPEND_EVENT) + self.text = text + + def Clone(self): + self.__class__(self.GetId()) + +class termsink(wx.Panel): def __init__(self, parent, msgq, - size=term_window.DEFAULT_WIN_SIZE, + size=DEFAULT_WIN_SIZE, ): - - self.win = term_window.term_window( - parent=parent, - size=size, - ) + wx.Panel.__init__(self, + parent, + size=size, + style=wx.SIMPLE_BORDER, + ) + + self.text_ctrl = wx.TextCtrl(self, + wx.ID_ANY, + value="", + size=size, + style=wx.TE_MULTILINE|wx.TE_READONLY, + ) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(self.text_ctrl, 1, wx.EXPAND) + self.SetSizerAndFit(main_sizer) + + EVT_APPEND_EVENT(self, self.evt_append) self.runner = gru.msgq_runner(msgq, self.handle_msg) def handle_msg(self, msg): - # Just append text for now + # This gets called in the queue runner thread context + # For now, just add whatever the user sends to the text control text = msg.to_string() print "handle_msg: received", len(text), "bytes" - self.win.append_text(text) + + # Create a wxPython event and post it to the event queue + evt = AppendEvent(text) + wx.PostEvent(self, evt) + del evt + + def evt_append(self, evt): + # This gets called by the wxPython event queue runner + print "appending", len(evt.text), "bytes" + self.text_ctrl.AppendText(evt.text) -- cgit From 392b7f9002cb08dddc1ca0ced18f899a0958ba1f Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Tue, 3 Nov 2009 07:11:45 -0800 Subject: gr-wxgui: cleanup for merge --- gr-wxgui/src/python/Makefile.am | 2 +- gr-wxgui/src/python/termsink.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index dfa156f62..2382d599c 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004,2005,2008 Free Software Foundation, Inc. +# Copyright 2004,2005,2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/termsink.py b/gr-wxgui/src/python/termsink.py index 45a94e396..a0cfd575d 100644 --- a/gr-wxgui/src/python/termsink.py +++ b/gr-wxgui/src/python/termsink.py @@ -66,7 +66,6 @@ class termsink(wx.Panel): # This gets called in the queue runner thread context # For now, just add whatever the user sends to the text control text = msg.to_string() - print "handle_msg: received", len(text), "bytes" # Create a wxPython event and post it to the event queue evt = AppendEvent(text) @@ -75,5 +74,4 @@ class termsink(wx.Panel): def evt_append(self, evt): # This gets called by the wxPython event queue runner - print "appending", len(evt.text), "bytes" self.text_ctrl.AppendText(evt.text) -- cgit From f6a770799b8725546044a59067f1ab9937ee3af1 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 26 Dec 2009 22:43:29 -0500 Subject: bug fix for waterfall plotter, it seems that numpy choose changed --- gr-wxgui/src/python/plotter/waterfall_plotter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index d32b0ca0a..f83e27e72 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -26,6 +26,7 @@ import common import numpy import gltext import math +import struct LEGEND_LEFT_PAD = 7 LEGEND_NUM_BLOCKS = 256 @@ -37,6 +38,9 @@ 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.pack('BBBB', *x) +unpack_color = lambda s: struct.unpack('BBBB', s) + 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. @@ -53,10 +57,10 @@ def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]): #linear interpolation if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1 raise Exception - return [numpy.array(map( + return [pack_color(map( lambda pw: int(255*_fcn(i/255.0, pw)), (red_pts, green_pts, blue_pts, alpha_pts), - ), numpy.uint8).tostring() for i in range(0, 256) + )) for i in range(0, 256) ] COLORS = { @@ -179,8 +183,8 @@ class waterfall_plotter(grid_plotter_base): block_height = float(legend_height)/LEGEND_NUM_BLOCKS x = self.width - self.padding_right + LEGEND_LEFT_PAD for i in range(LEGEND_NUM_BLOCKS): - color = COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))] - GL.glColor4f(*map(lambda c: ord(c)/255.0, color)) + 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 @@ -261,7 +265,7 @@ class waterfall_plotter(grid_plotter_base): samples = numpy.clip(samples, 0, 255) #clip samples = numpy.array(samples, numpy.uint8) #convert the samples to RGBA data - data = numpy.choose(samples, COLORS[self._color_mode]).tostring() + data = ''.join([COLORS[self._color_mode][sample] for sample in samples]) self._buffer.append(data) self._waterfall_cache.changed(True) self.unlock() -- cgit From bd18b8192c78bb7af4a9c3d3a6c367cde3527311 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 6 Jan 2010 22:08:27 -0800 Subject: Use numpy to do color table lookup. You can index a numpy array with another numpy array to get an array of looked-up values. I did not know that numpy could do that! --- gr-wxgui/src/python/plotter/waterfall_plotter.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index f83e27e72..0af64b826 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008, 2009 Free Software Foundation, Inc. +# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -38,8 +38,8 @@ 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.pack('BBBB', *x) -unpack_color = lambda s: struct.unpack('BBBB', s) +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)]): """ @@ -57,11 +57,10 @@ def _get_rbga(red_pts, green_pts, blue_pts, alpha_pts=[(0, 0), (1, 0)]): #linear interpolation if x <= x2: return float(y1 - y2)/(x1 - x2)*(x - x1) + y1 raise Exception - return [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) - ] + 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 @@ -265,7 +264,7 @@ class waterfall_plotter(grid_plotter_base): samples = numpy.clip(samples, 0, 255) #clip samples = numpy.array(samples, numpy.uint8) #convert the samples to RGBA data - data = ''.join([COLORS[self._color_mode][sample] for sample in samples]) + data = COLORS[self._color_mode][samples].tostring() self._buffer.append(data) self._waterfall_cache.changed(True) self.unlock() -- cgit From 49fa13f9fce2037d176c86bf326a7e25a78b72a5 Mon Sep 17 00:00:00 2001 From: Martin Dudok van Heel Date: Mon, 26 Apr 2010 19:40:41 +0200 Subject: Add analog CRT screen afterglow emulation for gr-wxgui --- gr-wxgui/src/python/constants.py | 2 + gr-wxgui/src/python/fft_window.py | 51 ++++++++++++++ gr-wxgui/src/python/fftsink_gl.py | 37 ++++++++-- gr-wxgui/src/python/fftsink_nongl.py | 60 +++++++++++++--- gr-wxgui/src/python/plot.py | 98 ++++++++++++++++++++++++-- gr-wxgui/src/python/plotter/channel_plotter.py | 1 + gr-wxgui/src/python/plotter/plotter_base.py | 36 +++++++++- gr-wxgui/src/python/scope_window.py | 52 ++++++++++++++ gr-wxgui/src/python/scopesink_gl.py | 32 +++++++-- 9 files changed, 340 insertions(+), 29 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 8ff7fa8fe..517a55283 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -27,6 +27,8 @@ ALPHA_KEY = 'alpha' AUTORANGE_KEY = 'autorange' AVERAGE_KEY = 'average' AVG_ALPHA_KEY = 'avg_alpha' +EMULATE_ANALOG_KEY = 'emulate_analog' +ANALOG_ALPHA_KEY = 'analog_alpha' BASEBAND_FREQ_KEY = 'baseband_freq' BETA_KEY = 'beta' COLOR_MODE_KEY = 'color_mode' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index e025c28dd..67bb65b26 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -37,6 +37,7 @@ import forms ################################################## SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 +ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DB_DIV_MIN, DB_DIV_MAX = 1, 20 @@ -95,7 +96,38 @@ class control_panel(wx.Panel): for widget in (avg_alpha_text, avg_alpha_slider): parent.subscribe(AVERAGE_KEY, widget.Enable) widget.Enable(parent[AVERAGE_KEY]) + parent.subscribe(AVERAGE_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[AVERAGE_KEY]) + + parent.subscribe(AVERAGE_KEY, self._update_layout) + + forms.check_box( + sizer=options_box, parent=self, label='Emulate Analog', + ps=parent, key=EMULATE_ANALOG_KEY, + ) + #static text and slider for analog alpha + analog_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Analog Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ) + analog_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=ANALOG_ALPHA_MIN_EXP, + max_exp=ANALOG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ANALOG_ALPHA_KEY, + ) + for widget in (analog_alpha_text, analog_alpha_slider): + parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) + widget.Enable(parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + #trace menu for trace in TRACES: trace_box = wx.BoxSizer(wx.HORIZONTAL) @@ -142,6 +174,7 @@ class control_panel(wx.Panel): ) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event def on_mouse_wheel(event): if event.GetWheelRotation() < 0: self._on_incr_ref_level(event) @@ -159,6 +192,14 @@ class control_panel(wx.Panel): self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) def _on_decr_db_div(self, event): self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) + ################################################## + # subscriber handlers + ################################################## + def _update_layout(self,key): + # Just ignore the key value we get + # we only need to now that the visability or size of something has changed + self.parent.Layout() + #self.parent.Fit() ################################################## # FFT window with plotter and control panel @@ -181,7 +222,10 @@ class fft_window(wx.Panel, pubsub.pubsub): avg_alpha_key, peak_hold, msg_key, + emulate_analog, + analog_alpha, ): + pubsub.pubsub.__init__(self) #setup self.samples = EMPTY_TRACE @@ -202,6 +246,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self[REF_LEVEL_KEY] = ref_level self[BASEBAND_FREQ_KEY] = baseband_freq self[RUNNING_KEY] = True + self[EMULATE_ANALOG_KEY] = emulate_analog + self[ANALOG_ALPHA_KEY] = analog_alpha for trace in TRACES: #a function that returns a function #so the function wont use local trace @@ -230,6 +276,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) + self.plotter.set_emulate_analog(emulate_analog) + self.plotter.set_analog_alpha(analog_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -245,9 +293,12 @@ class fft_window(wx.Panel, pubsub.pubsub): Y_PER_DIV_KEY, X_DIVS_KEY, Y_DIVS_KEY, REF_LEVEL_KEY, ): self.subscribe(key, self.update_grid) + self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) + self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) #initial update self.update_grid() + def autoscale(self, *args): """ Autoscale the fft plot to the last frame. diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 9d683d697..564764487 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -27,6 +27,7 @@ import common from gnuradio import gr, blks2 from pubsub import pubsub from constants import * +import math ################################################## # FFT sink block (wrapper for old wxgui) @@ -52,9 +53,20 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): title='', size=fft_window.DEFAULT_WIN_SIZE, peak_hold=False, + emulate_analog=False, + analog_alpha=None, ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate + #ensure analog alpha + if analog_alpha is None: + actual_fft_rate=float(sample_rate/fft_size)/float(max(1,int(float((sample_rate/fft_size)/fft_rate)))) + #print "requested_fft_rate ",fft_rate + #print "actual_fft_rate ",actual_fft_rate + analog_cutoff_freq=0.5 # Hertz + #calculate alpha from wanted cutoff freq + analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) + #init gr.hier_block2.__init__( self, @@ -73,6 +85,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) + + #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) @@ -100,6 +114,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): avg_alpha_key=AVG_ALPHA_KEY, peak_hold=peak_hold, msg_key=MSG_KEY, + emulate_analog=emulate_analog, + analog_alpha=analog_alpha, ) common.register_access_methods(self, self.win) setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS @@ -131,11 +147,14 @@ class test_app_block (stdgui2.std_top_block): fft_size = 256 # build our flow graph - input_rate = 20.48e3 + input_rate = 2048.0e3 + + #Generate some noise + noise =gr.noise_source_c(gr.GR_UNIFORM, 1.0/10) # Generate a complex sinusoid #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 57.50e3, 1) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. @@ -146,17 +165,25 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect(src1, thr1, sink1) + combine1=gr.add_cc() + self.connect(src1, (combine1,0)) + self.connect(noise,(combine1,1)) + self.connect(combine1,thr1, sink1) #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 57.50e3, 1) thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect(src2, thr2, sink2) + combine2=gr.add_ff() + c2f2=gr.complex_to_float() + + self.connect(src2, (combine2,0)) + self.connect(noise,c2f2,(combine2,1)) + self.connect(combine2, thr2,sink2) def main (): app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index ca5e91fdb..f1c1f4396 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -37,7 +37,7 @@ class fft_sink_base(object): y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', peak_hold=False): + average=False, avg_alpha=None, title='', peak_hold=False,emulate_analog=False,analog_alpha=0.2): # initialize common attributes self.baseband_freq = baseband_freq @@ -52,6 +52,9 @@ class fft_sink_base(object): self.avg_alpha = 2.0 / fft_rate else: self.avg_alpha = avg_alpha + self.emulate_analog = emulate_analog + self.analog_alpha = analog_alpha + self.title = title self.peak_hold = peak_hold self.input_is_real = input_is_real @@ -75,6 +78,14 @@ class fft_sink_base(object): self.peak_hold = enable self.win.set_peak_hold(enable) + def set_emulate_analog(self, enable): + self.emulate_analog = enable + self.win.set_emulate_analog(enable) + + def set_analog_alpha(self, analog_alpha): + self.analog_alpha = analog_alpha + self.win.set_analog_alpha(analog_alpha) + def set_avg_alpha(self, avg_alpha): self.avg_alpha = avg_alpha @@ -93,7 +104,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2): gr.hier_block2.__init__(self, "fft_sink_f", gr.io_signature(1, 1, gr.sizeof_float), @@ -104,7 +115,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) + peak_hold=peak_hold,emulate_analog=emulate_analog,analog_alpha=analog_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, @@ -131,12 +142,14 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) self.set_peak_hold(self.peak_hold) + self.set_emulate_analog(self.emulate_analog) + self.set_analog_alpha(self.analog_alpha) class fft_sink_c(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2): gr.hier_block2.__init__(self, "fft_sink_c", gr.io_signature(1, 1, gr.sizeof_gr_complex), @@ -147,7 +160,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) + peak_hold=peak_hold, emulate_analog=emulate_analog,analog_alpha=analog_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, @@ -173,6 +186,8 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) + self.set_emulate_analog(self.emulate_analog) + self.set_analog_alpha(self.analog_alpha) self.set_peak_hold(self.peak_hold) @@ -236,6 +251,9 @@ class control_panel(wx.Panel): self.average_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Average") self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average) control_box.Add(self.average_check_box, 0, wx.EXPAND) + self.emulate_analog_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Emulate Analog") + self.emulate_analog_check_box.Bind(wx.EVT_CHECKBOX, parent.on_emulate_analog) + control_box.Add(self.emulate_analog_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, parent.on_peak_hold) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) @@ -276,6 +294,7 @@ class control_panel(wx.Panel): """ #update checkboxes self.average_check_box.SetValue(self.parent.fftsink.average) + self.emulate_analog_check_box.SetValue(self.parent.fftsink.emulate_analog) self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) #update radio buttons try: @@ -306,6 +325,10 @@ class fft_window (wx.Panel): self.peak_hold = False self.peak_vals = None + + self.emulate_analog=False + self.analog_alpha=0.2 + self.plot.SetEnableGrid (True) # self.SetEnableZoom (True) @@ -394,6 +417,14 @@ class fft_window (wx.Panel): y_range = ymin, ymax self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) + def set_emulate_analog(self, enable): + self.emulate_analog = enable + self.plot.set_emulate_analog( enable) + + def set_analog_alpha(self, analog_alpha): + self.analog_alpha = analog_alpha + self.plot.set_analog_alpha(analog_alpha) + def set_peak_hold(self, enable): self.peak_hold = enable self.peak_vals = None @@ -403,6 +434,11 @@ class fft_window (wx.Panel): self.fftsink.set_average(evt.IsChecked()) self.control_panel.update() + def on_emulate_analog(self, evt): + # print "on_analog" + self.fftsink.set_emulate_analog(evt.IsChecked()) + self.control_panel.update() + def on_peak_hold(self, evt): # print "on_peak_hold" self.fftsink.set_peak_hold(evt.IsChecked()) @@ -486,9 +522,11 @@ class fft_window (wx.Panel): self.id_y_per_div_10 = wx.NewId() self.id_y_per_div_20 = wx.NewId() self.id_average = wx.NewId() + self.id_emulate_analog = wx.NewId() self.id_peak_hold = wx.NewId() self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.plot.Bind(wx.EVT_MENU, self.on_emulate_analog, id=self.id_emulate_analog) self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) @@ -504,6 +542,7 @@ class fft_window (wx.Panel): menu = wx.Menu() self.popup_menu = menu menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_emulate_analog, "Emulate Analog") menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") menu.Append(self.id_incr_ref_level, "Incr Ref Level") menu.Append(self.id_decr_ref_level, "Decr Ref Level") @@ -519,6 +558,7 @@ class fft_window (wx.Panel): self.checkmarks = { self.id_average : lambda : self.fftsink.average, + self.id_emulate_analog : lambda : self.fftsink.emulate_analog, self.id_peak_hold : lambda : self.fftsink.peak_hold, self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, @@ -561,11 +601,11 @@ class test_app_block (stdgui2.std_top_block): fft_size = 256 # build our flow graph - input_rate = 20.48e3 + input_rate = 100*20.48e3 # Generate a complex sinusoid - #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. @@ -578,8 +618,8 @@ class test_app_block (stdgui2.std_top_block): self.connect(src1, thr1, sink1) - #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index c104b0ea5..c5557cb6b 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -36,6 +36,9 @@ # # May 27, 2007 Johnathan Corgan (jcorgan@corganenterprises.com) # - Converted from numarray to numpy +# +# Apr 23, 2010 Martin Dudok van Heel (http://www.olifantasia.com/gnuradio/contact_olifantasia.gif) +# - Added Emulate Analog option (emulate after glow of an analog CRT display using IIR) """ This is a simple light weight plotting module that can be used with @@ -422,6 +425,11 @@ class PlotCanvas(wx.Window): def __init__(self, parent, id = -1, pos=wx.DefaultPosition, size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""): + + self.emulate_analog=False + self.alpha=0.3 + self.decimation=10 + self.decim_counter=0 """Constucts a window, which can be a child of a frame, dialog or any other non-control window""" @@ -488,6 +496,14 @@ class PlotCanvas(wx.Window): # platforms at initialization, but little harm done. self.OnSize(None) # sets the initial size based on client size # UNCONDITIONAL, needed to create self._Buffer + + + def set_emulate_analog(self, enable): + self.emulate_analog = enable + + def set_analog_alpha(self, analog_alpha): + self.alpha = analog_alpha + # SaveFile def SaveFile(self, fileName= ''): @@ -791,12 +807,19 @@ class PlotCanvas(wx.Window): if dc == None: # sets new dc and clears it - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.Clear() - + if self.emulate_analog: + dc = wx.MemoryDC() + dc.SelectObject(self._Buffer) + dc.Clear() + else: + dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) + dc.Clear() + dc.BeginDrawing() # dc.Clear() - + + + # set font size for every thing but title and legend dc.SetFont(self._getFont(self._fontSizeAxis)) @@ -818,6 +841,15 @@ class PlotCanvas(wx.Window): self.last_draw = (graphics, xAxis, yAxis) # saves most recient values + if False: + ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) + #dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) #wx.SOLID wx.TRANSPARENT ) ) + #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT + dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) + #dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) ) + #dc.SetLogicalFunction(wx.COPY) + # Get ticks and textExtents for axis if required if self._xSpec is not 'none': if self._xUseScopeTicks: @@ -874,8 +906,11 @@ class PlotCanvas(wx.Window): scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _numpy.array((1,-1)) shift = -p1*scale + self.plotbox_origin + textSize_shift * _numpy.array((1,-1)) self._pointScale= scale # make available for mouse events - self._pointShift= shift + self._pointShift= shift + + #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) + #dc.SetLogicalFunction(wx.COPY) graphics.scaleAndShift(scale, shift) graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing @@ -885,11 +920,44 @@ class PlotCanvas(wx.Window): dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight) # Draw the lines and markers #start = _time.clock() + graphics.draw(dc) # print "entire graphics drawing took: %f second"%(_time.clock() - start) # remove the clipping region dc.DestroyClippingRegion() dc.EndDrawing() + + + if self.emulate_analog: + dc=None + self._Buffer.CopyToBuffer(self._Bufferarray) #, format=wx.BitmapBufferFormat_RGB, stride=-1) + ## do the IIR filter + alpha_int=int(float(self.alpha*256)) + if True: + _numpy.add(self._Bufferarray,0,self._Buffer3array) + _numpy.multiply(self._Buffer3array,alpha_int,self._Buffer3array) + _numpy.multiply(self._Buffer2array,(256-alpha_int),self._Buffer2array) + _numpy.add(self._Buffer3array,self._Buffer2array,self._Buffer2array) + _numpy.right_shift(self._Buffer2array,8,self._Buffer2array) + elif False: + self._Buffer2array=(self._Bufferarray.astype(_numpy.uint32) *alpha_int + self._Buffer2array*(256-alpha_int)).__rshift__(8) + elif False: + self._Buffer2array *=(256-alpha_int) + self._Buffer2array +=self._Bufferarray.astype(_numpy.uint32)*alpha_int + self._Buffer2array /=256 + + ##copy back to image buffer + self._Buffer2.CopyFromBuffer(self._Buffer2array.astype(_numpy.uint8)) #, format=wx.BitmapBufferFormat_RGB, stride=-1) + + #draw to the screen + #self.decim_counter=self.decim_counter+1 + if True: #self.decim_counter>self.decimation: + #self.decim_counter=0 + dc2 = wx.ClientDC( self ) + dc2.BeginDrawing() + dc2.DrawBitmap(self._Buffer2, 0, 0, False) + #dc2.DrawBitmap(self._Buffer, 0, 0, False) + dc2.EndDrawing() def Redraw(self, dc= None): """Redraw the existing plot.""" @@ -1031,6 +1099,8 @@ class PlotCanvas(wx.Window): if self.last_PointLabel != None: self._drawPointLabel(self.last_PointLabel) #erase old self.last_PointLabel = None + + #paint current buffer to screen dc = wx.BufferedPaintDC(self, self._Buffer) def OnSize(self,event): @@ -1041,7 +1111,23 @@ class PlotCanvas(wx.Window): # Make new offscreen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. - self._Buffer = wx.EmptyBitmap(Size[0],Size[1]) + self._Buffer = wx.EmptyBitmap(Size[0],Size[1],24) + + + if True: #self.emulate_analog: + #self._Bufferarray = _numpy.zeros((Size[0], Size[1],3), dtype=_numpy.uint8) + self._Bufferarray = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint8) + + # Make new second offscreen bitmap: this bitmap will always have the + # last drawing in it, so it can be used to do display time dependent processing + # like averaging (IIR) or show differences between updates + self._Buffer2 = wx.EmptyBitmap(Size[0],Size[1],24) + # now the extra buffers for the IIR processing + # note the different datatype uint32 + self._Buffer2array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float + self._Buffer3array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float + # optional you can set the ufunct buffer size to improve speed + #_numpy.setbufsize(16*((Size[0]* Size[1]*3)/16 +1)) self._setSize() self.last_PointLabel = None #reset pointLabel diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index ff0a3a160..f046ae5a9 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -47,6 +47,7 @@ class channel_plotter(grid_plotter_base): """ #init grid_plotter_base.__init__(self, parent, MIN_PADDING) + self.set_emulate_analog(False) #setup legend cache self._legend_cache = self.new_gl_cache(self._draw_legend, 50) self.enable_legend(False) diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index dede5a0ad..2fbd5fb9d 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -87,7 +87,10 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): @param parent the parent widgit """ attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) - wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) + wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList); + self.emulate_analog=False + self.analog_alpha=2.0/15 + self.clear_accum=True self._gl_init_flag = False self._resized_flag = True self._init_fcns = list() @@ -97,6 +100,13 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self.Bind(wx.EVT_SIZE, self._on_size) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + def set_emulate_analog(self,enable): + self.emulate_analog=enable + self.clear_accum=True + + def set_analog_alpha(self,analog_alpha): + self.analog_alpha=analog_alpha + def new_gl_cache(self, draw_fcn, draw_pri=50): """ Create a new gl cache. @@ -131,6 +141,7 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ self.lock() self._resized_flag = True + self.clear_accum=True self.unlock() def _on_paint(self, event): @@ -160,7 +171,30 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self._resized_flag = False #clear, draw functions, swap GL.glClear(GL.GL_COLOR_BUFFER_BIT) + + if False: + GL.glEnable (GL.GL_LINE_SMOOTH) + GL.glEnable (GL.GL_POLYGON_SMOOTH) + GL.glEnable (GL.GL_BLEND) + GL.glBlendFunc (GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) + GL.glHint (GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST) #GL.GL_DONT_CARE) + GL.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST) + #GL.glLineWidth (1.5) + + GL.glEnable(GL.GL_MULTISAMPLE) #Enable Multisampling anti-aliasing + + for fcn in self._draw_fcns: fcn[1]() + + if self.emulate_analog: + if self.clear_accum: + #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) + GL.glAccum(GL.GL_LOAD, 1.0) + self.clear_accum=False + + GL.glAccum(GL.GL_MULT, 1.0-self.analog_alpha) + GL.glAccum(GL.GL_ACCUM, self.analog_alpha) + GL.glAccum(GL.GL_RETURN, 1.0) self.SwapBuffers() self.unlock() diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 449046402..a6c7bdb41 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -36,6 +36,8 @@ import forms # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) +ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 +SLIDER_STEPS = 100 DEFAULT_WIN_SIZE = (600, 300) COUPLING_MODES = ( ('DC', False), @@ -83,6 +85,37 @@ class control_panel(wx.Panel): self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) + + ################################################## + # Emulate Analog + ################################################## + + forms.check_box( + sizer=control_box, parent=self, label='Emulate Analog', + ps=parent, key=EMULATE_ANALOG_KEY, + ) + #static text and slider for analog alpha + analog_alpha_text = forms.static_text( + sizer=control_box, parent=self, label='Analog Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ) + analog_alpha_slider = forms.log_slider( + sizer=control_box, parent=self, + min_exp=ANALOG_ALPHA_MIN_EXP, + max_exp=ANALOG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ANALOG_ALPHA_KEY, + ) + for widget in (analog_alpha_text, analog_alpha_slider): + parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) + widget.Enable(parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + + parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + ################################################## # Axes Options ################################################## @@ -359,6 +392,15 @@ class control_panel(wx.Panel): def _on_decr_y_off(self, event): self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY] + ################################################## + # subscriber handlers + ################################################## + def _update_layout(self,key): + # Just ignore the key value we get + # we only need to now that the visability or size of something has changed + self.parent.Layout() + #self.parent.Fit() + ################################################## # Scope window with plotter and control panel ################################################## @@ -382,6 +424,8 @@ class scope_window(wx.Panel, pubsub.pubsub): trigger_channel_key, decimation_key, msg_key, + emulate_analog, + analog_alpha, ): pubsub.pubsub.__init__(self) #check num inputs @@ -424,6 +468,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 + self[EMULATE_ANALOG_KEY] = emulate_analog + self[ANALOG_ALPHA_KEY] = analog_alpha for i in range(num_inputs): self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot @@ -434,6 +480,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) + self.plotter.set_emulate_analog(emulate_analog) + self.plotter.set_analog_alpha(analog_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -451,6 +499,9 @@ class scope_window(wx.Panel, pubsub.pubsub): XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY, TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, ]: self.subscribe(key, self.update_grid) + #register events for plotter settings + self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) + self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) #initial update self.update_grid() @@ -615,3 +666,4 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) #redraw current sample self.handle_samples() + diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 2882488e3..204434ce6 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -27,6 +27,7 @@ import common from gnuradio import gr from pubsub import pubsub from constants import * +import math class ac_couple_block(gr.hier_block2): """ @@ -75,8 +76,17 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): ac_couple=False, num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, + emulate_analog=False, + analog_alpha=None, **kwargs #do not end with a comma ): + #ensure analog alpha + if analog_alpha is None: + actual_frame_rate=float(frame_rate) + analog_cutoff_freq=0.5 # Hertz + #calculate alpha from wanted cutoff freq + analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_frame_rate) + if not t_scale: t_scale = 10.0/sample_rate #init gr.hier_block2.__init__( @@ -127,6 +137,8 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): trigger_channel_key=TRIGGER_CHANNEL_KEY, decimation_key=DECIMATION_KEY, msg_key=MSG_KEY, + emulate_analog=emulate_analog, + analog_alpha=analog_alpha, ) common.register_access_methods(self, self.win) #connect @@ -167,10 +179,11 @@ class test_top_block (stdgui2.std_top_block): def __init__(self, frame, panel, vbox, argv): stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + default_input_rate = 1e6 if len(argv) > 1: - frame_decim = int(argv[1]) + input_rate = int(argv[1]) else: - frame_decim = 1 + input_rate = default_input_rate if len(argv) > 2: v_scale = float(argv[2]) # start up at this v_scale value @@ -180,14 +193,17 @@ class test_top_block (stdgui2.std_top_block): if len(argv) > 3: t_scale = float(argv[3]) # start up at this t_scale value else: - t_scale = .00003 # old behavior + t_scale = .00003*default_input_rate/input_rate # old behavior - print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + print "input rate %s v_scale %s t_scale %s" % (input_rate,v_scale,t_scale) - input_rate = 1e6 # Generate a complex sinusoid - self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) + ampl=1.0e3 + self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3*input_rate/default_input_rate, ampl) + self.noise =gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 11.1*25.1e3*input_rate/default_input_rate, ampl/10) + #self.noise =gr.noise_source_c(gr.GR_GAUSSIAN, ampl/10) + self.combine=gr.add_cc() # We add this throttle block so that this demo doesn't suck down # all the CPU available. You normally wouldn't use it... @@ -199,7 +215,9 @@ class test_top_block (stdgui2.std_top_block): # Ultimately this will be # self.connect("src0 throttle scope") - self.connect(self.src0, self.thr, scope) + self.connect(self.src0,(self.combine,0)) + self.connect(self.noise,(self.combine,1)) + self.connect(self.combine, self.thr, scope) def main (): app = stdgui2.stdapp (test_top_block, "O'Scope Test App") -- cgit From 8219bc01748d79ca1d995e65914b8c4033a89999 Mon Sep 17 00:00:00 2001 From: Marcus Leech Date: Wed, 28 Apr 2010 09:08:13 -0700 Subject: gr-wxgui: Added additional color table entries --- gr-wxgui/src/python/scope_window.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 08a025e1e..f7c0ffa82 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -55,6 +55,9 @@ CHANNEL_COLOR_SPECS = ( (0.0, 0.8, 0.0), (1.0, 0.0, 0.0), (0.8, 0.0, 0.8), + (0.7, 0.7, 0.0), + (0.15, 0.90, 0.98), + ) TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0) AUTORANGE_UPDATE_RATE = 0.5 #sec -- cgit From 3a730f46faf1942c713350b312a1dfeffb587550 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 13 May 2010 13:11:13 -0700 Subject: gr-wxgui: Renamed "emulate analog" feature to "use persistence" --- gr-wxgui/src/python/constants.py | 4 +- gr-wxgui/src/python/fft_window.py | 50 +++++++++--------- gr-wxgui/src/python/fftsink_gl.py | 14 +++--- gr-wxgui/src/python/fftsink_nongl.py | 70 +++++++++++++------------- gr-wxgui/src/python/plot.py | 18 +++---- gr-wxgui/src/python/plotter/channel_plotter.py | 2 +- gr-wxgui/src/python/plotter/plotter_base.py | 18 +++---- gr-wxgui/src/python/scope_window.py | 48 +++++++++--------- gr-wxgui/src/python/scopesink_gl.py | 12 ++--- 9 files changed, 118 insertions(+), 118 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 2e7f33a0c..93b281aa5 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -27,8 +27,8 @@ ALPHA_KEY = 'alpha' AUTORANGE_KEY = 'autorange' AVERAGE_KEY = 'average' AVG_ALPHA_KEY = 'avg_alpha' -EMULATE_ANALOG_KEY = 'emulate_analog' -ANALOG_ALPHA_KEY = 'analog_alpha' +USE_PERSISTENCE_KEY = 'use_persistence' +PERSIST_ALPHA_KEY = 'persist_alpha' BASEBAND_FREQ_KEY = 'baseband_freq' BETA_KEY = 'beta' COLOR_MODE_KEY = 'color_mode' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index c56dbd7e6..a460fe995 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -37,7 +37,7 @@ import forms ################################################## SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 -ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 +PERSIST_ALPHA_MIN_EXP, PERSIST_ALPHA_MAX_EXP = -2, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DB_DIV_MIN, DB_DIV_MAX = 1, 20 @@ -105,30 +105,30 @@ class control_panel(wx.Panel): parent.subscribe(AVERAGE_KEY, self._update_layout) forms.check_box( - sizer=options_box, parent=self, label='Emulate Analog', - ps=parent, key=EMULATE_ANALOG_KEY, + sizer=options_box, parent=self, label='Persistence', + ps=parent, key=USE_PERSISTENCE_KEY, ) - #static text and slider for analog alpha - analog_alpha_text = forms.static_text( - sizer=options_box, parent=self, label='Analog Alpha', + #static text and slider for persist alpha + persist_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Persist Alpha', converter=forms.float_converter(lambda x: '%.4f'%x), - ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ps=parent, key=PERSIST_ALPHA_KEY, width=50, ) - analog_alpha_slider = forms.log_slider( + persist_alpha_slider = forms.log_slider( sizer=options_box, parent=self, - min_exp=ANALOG_ALPHA_MIN_EXP, - max_exp=ANALOG_ALPHA_MAX_EXP, + min_exp=PERSIST_ALPHA_MIN_EXP, + max_exp=PERSIST_ALPHA_MAX_EXP, num_steps=SLIDER_STEPS, - ps=parent, key=ANALOG_ALPHA_KEY, + ps=parent, key=PERSIST_ALPHA_KEY, ) - for widget in (analog_alpha_text, analog_alpha_slider): - parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) - widget.Enable(parent[EMULATE_ANALOG_KEY]) - parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + for widget in (persist_alpha_text, persist_alpha_slider): + parent.subscribe(USE_PERSISTENCE_KEY, widget.Enable) + widget.Enable(parent[USE_PERSISTENCE_KEY]) + parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) #allways show initially, so room is reserved for them - widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) #trace menu for trace in TRACES: @@ -224,8 +224,8 @@ class fft_window(wx.Panel, pubsub.pubsub): avg_alpha_key, peak_hold, msg_key, - emulate_analog, - analog_alpha, + use_persistence, + persist_alpha, ): pubsub.pubsub.__init__(self) @@ -248,8 +248,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self[REF_LEVEL_KEY] = ref_level self[BASEBAND_FREQ_KEY] = baseband_freq self[RUNNING_KEY] = True - self[EMULATE_ANALOG_KEY] = emulate_analog - self[ANALOG_ALPHA_KEY] = analog_alpha + self[USE_PERSISTENCE_KEY] = use_persistence + self[PERSIST_ALPHA_KEY] = persist_alpha for trace in TRACES: #a function that returns a function #so the function wont use local trace @@ -278,8 +278,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) - self.plotter.set_emulate_analog(emulate_analog) - self.plotter.set_analog_alpha(analog_alpha) + self.plotter.set_use_persistence(use_persistence) + self.plotter.set_persist_alpha(persist_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -295,8 +295,8 @@ class fft_window(wx.Panel, pubsub.pubsub): Y_PER_DIV_KEY, X_DIVS_KEY, Y_DIVS_KEY, REF_LEVEL_KEY, ): self.subscribe(key, self.update_grid) - self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) - self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) + self.subscribe(USE_PERSISTENCE_KEY, self.plotter.set_use_persistence) + self.subscribe(PERSIST_ALPHA_KEY, self.plotter.set_persist_alpha) #initial update self.update_grid() diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 0d725ea14..e0306d919 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -1,5 +1,5 @@ # -# Copyright 2008,2009 Free Software Foundation, Inc. +# Copyright 2008,2009,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -54,20 +54,20 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): size=fft_window.DEFAULT_WIN_SIZE, peak_hold=False, win=None, - emulate_analog=False, - analog_alpha=None, + use_persistence=False, + persist_alpha=None, **kwargs #do not end with a comma ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate #ensure analog alpha - if analog_alpha is None: + if persist_alpha is None: actual_fft_rate=float(sample_rate/fft_size)/float(max(1,int(float((sample_rate/fft_size)/fft_rate)))) #print "requested_fft_rate ",fft_rate #print "actual_fft_rate ",actual_fft_rate analog_cutoff_freq=0.5 # Hertz #calculate alpha from wanted cutoff freq - analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) + persist_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) #init gr.hier_block2.__init__( @@ -117,8 +117,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): avg_alpha_key=AVG_ALPHA_KEY, peak_hold=peak_hold, msg_key=MSG_KEY, - emulate_analog=emulate_analog, - analog_alpha=analog_alpha, + use_persistence=use_persistence, + persist_alpha=persist_alpha, ) common.register_access_methods(self, self.win) setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index 8735e98ae..0854d6668 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -37,7 +37,7 @@ class fft_sink_base(object): y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', peak_hold=False,emulate_analog=False,analog_alpha=0.2): + average=False, avg_alpha=None, title='', peak_hold=False,use_persistence=False,persist_alpha=0.2): # initialize common attributes self.baseband_freq = baseband_freq @@ -52,8 +52,8 @@ class fft_sink_base(object): self.avg_alpha = 2.0 / fft_rate else: self.avg_alpha = avg_alpha - self.emulate_analog = emulate_analog - self.analog_alpha = analog_alpha + self.use_persistence = use_persistence + self.persist_alpha = persist_alpha self.title = title self.peak_hold = peak_hold @@ -78,13 +78,13 @@ class fft_sink_base(object): self.peak_hold = enable self.win.set_peak_hold(enable) - def set_emulate_analog(self, enable): - self.emulate_analog = enable - self.win.set_emulate_analog(enable) + def set_use_persistence(self, enable): + self.use_persistence = enable + self.win.set_use_persistence(enable) - def set_analog_alpha(self, analog_alpha): - self.analog_alpha = analog_alpha - self.win.set_analog_alpha(analog_alpha) + def set_persist_alpha(self, persist_alpha): + self.persist_alpha = persist_alpha + self.win.set_persist_alpha(persist_alpha) def set_avg_alpha(self, avg_alpha): self.avg_alpha = avg_alpha @@ -104,7 +104,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2, **kwargs): + title='', size=default_fftsink_size, peak_hold=False, use_persistence=False,persist_alpha=0.2, **kwargs): gr.hier_block2.__init__(self, "fft_sink_f", gr.io_signature(1, 1, gr.sizeof_float), @@ -115,7 +115,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold,emulate_analog=emulate_analog,analog_alpha=analog_alpha) + peak_hold=peak_hold,use_persistence=use_persistence,persist_alpha=persist_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, @@ -142,14 +142,14 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) self.set_peak_hold(self.peak_hold) - self.set_emulate_analog(self.emulate_analog) - self.set_analog_alpha(self.analog_alpha) + self.set_use_persistence(self.use_persistence) + self.set_persist_alpha(self.persist_alpha) class fft_sink_c(gr.hier_block2, fft_sink_base): 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, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2, **kwargs): + title='', size=default_fftsink_size, peak_hold=False, use_persistence=False,persist_alpha=0.2, **kwargs): gr.hier_block2.__init__(self, "fft_sink_c", gr.io_signature(1, 1, gr.sizeof_gr_complex), @@ -160,7 +160,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold, emulate_analog=emulate_analog,analog_alpha=analog_alpha) + peak_hold=peak_hold, use_persistence=use_persistence,persist_alpha=persist_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, @@ -186,8 +186,8 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) - self.set_emulate_analog(self.emulate_analog) - self.set_analog_alpha(self.analog_alpha) + self.set_use_persistence(self.use_persistence) + self.set_persist_alpha(self.persist_alpha) self.set_peak_hold(self.peak_hold) @@ -251,9 +251,9 @@ class control_panel(wx.Panel): self.average_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Average") self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average) control_box.Add(self.average_check_box, 0, wx.EXPAND) - self.emulate_analog_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Emulate Analog") - self.emulate_analog_check_box.Bind(wx.EVT_CHECKBOX, parent.on_emulate_analog) - control_box.Add(self.emulate_analog_check_box, 0, wx.EXPAND) + self.use_persistence_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Persistence") + self.use_persistence_check_box.Bind(wx.EVT_CHECKBOX, parent.on_use_persistence) + control_box.Add(self.use_persistence_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, parent.on_peak_hold) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) @@ -294,7 +294,7 @@ class control_panel(wx.Panel): """ #update checkboxes self.average_check_box.SetValue(self.parent.fftsink.average) - self.emulate_analog_check_box.SetValue(self.parent.fftsink.emulate_analog) + self.use_persistence_check_box.SetValue(self.parent.fftsink.use_persistence) self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) #update radio buttons try: @@ -326,8 +326,8 @@ class fft_window (wx.Panel): self.peak_hold = False self.peak_vals = None - self.emulate_analog=False - self.analog_alpha=0.2 + self.use_persistence=False + self.persist_alpha=0.2 self.plot.SetEnableGrid (True) @@ -417,13 +417,13 @@ class fft_window (wx.Panel): y_range = ymin, ymax self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) - def set_emulate_analog(self, enable): - self.emulate_analog = enable - self.plot.set_emulate_analog( enable) + def set_use_persistence(self, enable): + self.use_persistence = enable + self.plot.set_use_persistence( enable) - def set_analog_alpha(self, analog_alpha): - self.analog_alpha = analog_alpha - self.plot.set_analog_alpha(analog_alpha) + def set_persist_alpha(self, persist_alpha): + self.persist_alpha = persist_alpha + self.plot.set_persist_alpha(persist_alpha) def set_peak_hold(self, enable): self.peak_hold = enable @@ -434,9 +434,9 @@ class fft_window (wx.Panel): self.fftsink.set_average(evt.IsChecked()) self.control_panel.update() - def on_emulate_analog(self, evt): + def on_use_persistence(self, evt): # print "on_analog" - self.fftsink.set_emulate_analog(evt.IsChecked()) + self.fftsink.set_use_persistence(evt.IsChecked()) self.control_panel.update() def on_peak_hold(self, evt): @@ -522,11 +522,11 @@ class fft_window (wx.Panel): self.id_y_per_div_10 = wx.NewId() self.id_y_per_div_20 = wx.NewId() self.id_average = wx.NewId() - self.id_emulate_analog = wx.NewId() + self.id_use_persistence = wx.NewId() self.id_peak_hold = wx.NewId() self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) - self.plot.Bind(wx.EVT_MENU, self.on_emulate_analog, id=self.id_emulate_analog) + self.plot.Bind(wx.EVT_MENU, self.on_use_persistence, id=self.id_use_persistence) self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) @@ -542,7 +542,7 @@ class fft_window (wx.Panel): menu = wx.Menu() self.popup_menu = menu menu.AppendCheckItem(self.id_average, "Average") - menu.AppendCheckItem(self.id_emulate_analog, "Emulate Analog") + menu.AppendCheckItem(self.id_use_persistence, "Persistence") menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") menu.Append(self.id_incr_ref_level, "Incr Ref Level") menu.Append(self.id_decr_ref_level, "Decr Ref Level") @@ -558,7 +558,7 @@ class fft_window (wx.Panel): self.checkmarks = { self.id_average : lambda : self.fftsink.average, - self.id_emulate_analog : lambda : self.fftsink.emulate_analog, + self.id_use_persistence : lambda : self.fftsink.use_persistence, self.id_peak_hold : lambda : self.fftsink.peak_hold, self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index c5557cb6b..35b7ea9d1 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -38,7 +38,7 @@ # - Converted from numarray to numpy # # Apr 23, 2010 Martin Dudok van Heel (http://www.olifantasia.com/gnuradio/contact_olifantasia.gif) -# - Added Emulate Analog option (emulate after glow of an analog CRT display using IIR) +# - Added Persistence option (emulate after glow of an analog CRT display using IIR) """ This is a simple light weight plotting module that can be used with @@ -426,7 +426,7 @@ class PlotCanvas(wx.Window): def __init__(self, parent, id = -1, pos=wx.DefaultPosition, size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""): - self.emulate_analog=False + self.use_persistence=False self.alpha=0.3 self.decimation=10 self.decim_counter=0 @@ -498,11 +498,11 @@ class PlotCanvas(wx.Window): # UNCONDITIONAL, needed to create self._Buffer - def set_emulate_analog(self, enable): - self.emulate_analog = enable + def set_use_persistence(self, enable): + self.use_persistence = enable - def set_analog_alpha(self, analog_alpha): - self.alpha = analog_alpha + def set_persist_alpha(self, persist_alpha): + self.alpha = persist_alpha # SaveFile @@ -807,7 +807,7 @@ class PlotCanvas(wx.Window): if dc == None: # sets new dc and clears it - if self.emulate_analog: + if self.use_persistence: dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.Clear() @@ -928,7 +928,7 @@ class PlotCanvas(wx.Window): dc.EndDrawing() - if self.emulate_analog: + if self.use_persistence: dc=None self._Buffer.CopyToBuffer(self._Bufferarray) #, format=wx.BitmapBufferFormat_RGB, stride=-1) ## do the IIR filter @@ -1114,7 +1114,7 @@ class PlotCanvas(wx.Window): self._Buffer = wx.EmptyBitmap(Size[0],Size[1],24) - if True: #self.emulate_analog: + if True: #self.use_persistence: #self._Bufferarray = _numpy.zeros((Size[0], Size[1],3), dtype=_numpy.uint8) self._Bufferarray = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint8) diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index f046ae5a9..cf38786f2 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -47,7 +47,7 @@ class channel_plotter(grid_plotter_base): """ #init grid_plotter_base.__init__(self, parent, MIN_PADDING) - self.set_emulate_analog(False) + self.set_use_persistence(False) #setup legend cache self._legend_cache = self.new_gl_cache(self._draw_legend, 50) self.enable_legend(False) diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 2fbd5fb9d..73d697cfd 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -88,8 +88,8 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList); - self.emulate_analog=False - self.analog_alpha=2.0/15 + self.use_persistence=False + self.persist_alpha=2.0/15 self.clear_accum=True self._gl_init_flag = False self._resized_flag = True @@ -100,12 +100,12 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self.Bind(wx.EVT_SIZE, self._on_size) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) - def set_emulate_analog(self,enable): - self.emulate_analog=enable + def set_use_persistence(self,enable): + self.use_persistence=enable self.clear_accum=True - def set_analog_alpha(self,analog_alpha): - self.analog_alpha=analog_alpha + def set_persist_alpha(self,analog_alpha): + self.persist_alpha=analog_alpha def new_gl_cache(self, draw_fcn, draw_pri=50): """ @@ -186,14 +186,14 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): for fcn in self._draw_fcns: fcn[1]() - if self.emulate_analog: + if self.use_persistence: if self.clear_accum: #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) GL.glAccum(GL.GL_LOAD, 1.0) self.clear_accum=False - GL.glAccum(GL.GL_MULT, 1.0-self.analog_alpha) - GL.glAccum(GL.GL_ACCUM, self.analog_alpha) + GL.glAccum(GL.GL_MULT, 1.0-self.persist_alpha) + GL.glAccum(GL.GL_ACCUM, self.persist_alpha) GL.glAccum(GL.GL_RETURN, 1.0) self.SwapBuffers() self.unlock() diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index aace8688f..db8ed8a94 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -36,7 +36,7 @@ import forms # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) -ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 +PERSIST_ALPHA_MIN_EXP, PERSIST_ALPHA_MAX_EXP = -2, 0 SLIDER_STEPS = 100 DEFAULT_WIN_SIZE = (600, 300) COUPLING_MODES = ( @@ -92,34 +92,34 @@ class control_panel(wx.Panel): control_box = wx.BoxSizer(wx.VERTICAL) ################################################## - # Emulate Analog + # Persistence ################################################## forms.check_box( - sizer=control_box, parent=self, label='Emulate Analog', - ps=parent, key=EMULATE_ANALOG_KEY, + sizer=control_box, parent=self, label='Persistence', + ps=parent, key=USE_PERSISTENCE_KEY, ) #static text and slider for analog alpha - analog_alpha_text = forms.static_text( + persist_alpha_text = forms.static_text( sizer=control_box, parent=self, label='Analog Alpha', converter=forms.float_converter(lambda x: '%.4f'%x), - ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ps=parent, key=PERSIST_ALPHA_KEY, width=50, ) - analog_alpha_slider = forms.log_slider( + persist_alpha_slider = forms.log_slider( sizer=control_box, parent=self, - min_exp=ANALOG_ALPHA_MIN_EXP, - max_exp=ANALOG_ALPHA_MAX_EXP, + min_exp=PERSIST_ALPHA_MIN_EXP, + max_exp=PERSIST_ALPHA_MAX_EXP, num_steps=SLIDER_STEPS, - ps=parent, key=ANALOG_ALPHA_KEY, + ps=parent, key=PERSIST_ALPHA_KEY, ) - for widget in (analog_alpha_text, analog_alpha_slider): - parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) - widget.Enable(parent[EMULATE_ANALOG_KEY]) - parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + for widget in (persist_alpha_text, persist_alpha_slider): + parent.subscribe(USE_PERSISTENCE_KEY, widget.Enable) + widget.Enable(parent[USE_PERSISTENCE_KEY]) + parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) #allways show initially, so room is reserved for them - widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) ################################################## # Axes Options @@ -430,8 +430,8 @@ class scope_window(wx.Panel, pubsub.pubsub): trigger_channel_key, decimation_key, msg_key, - emulate_analog, - analog_alpha, + use_persistence, + persist_alpha, ): pubsub.pubsub.__init__(self) #check num inputs @@ -474,8 +474,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 - self[EMULATE_ANALOG_KEY] = emulate_analog - self[ANALOG_ALPHA_KEY] = analog_alpha + self[USE_PERSISTENCE_KEY] = use_persistence + self[PERSIST_ALPHA_KEY] = persist_alpha for i in range(num_inputs): self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot @@ -486,8 +486,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) - self.plotter.set_emulate_analog(emulate_analog) - self.plotter.set_analog_alpha(analog_alpha) + self.plotter.set_use_persistence(use_persistence) + self.plotter.set_persist_alpha(persist_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -506,8 +506,8 @@ class scope_window(wx.Panel, pubsub.pubsub): TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, ]: self.subscribe(key, self.update_grid) #register events for plotter settings - self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) - self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) + self.subscribe(USE_PERSISTENCE_KEY, self.plotter.set_use_persistence) + self.subscribe(PERSIST_ALPHA_KEY, self.plotter.set_persist_alpha) #initial update self.update_grid() diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index a12517883..b3222b43b 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -77,16 +77,16 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): ac_couple=False, num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, - emulate_analog=False, - analog_alpha=None, + use_persistence=False, + persist_alpha=None, **kwargs #do not end with a comma ): #ensure analog alpha - if analog_alpha is None: + if persist_alpha is None: actual_frame_rate=float(frame_rate) analog_cutoff_freq=0.5 # Hertz #calculate alpha from wanted cutoff freq - analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_frame_rate) + persist_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_frame_rate) if not t_scale: t_scale = 10.0/sample_rate #init @@ -139,8 +139,8 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): trigger_channel_key=TRIGGER_CHANNEL_KEY, decimation_key=DECIMATION_KEY, msg_key=MSG_KEY, - emulate_analog=emulate_analog, - analog_alpha=analog_alpha, + use_persistence=use_persistence, + persist_alpha=persist_alpha, ) common.register_access_methods(self, self.win) #connect -- cgit From 2057623cf8f9e9738954b146d3a23577110f7906 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 13 May 2010 13:16:46 -0700 Subject: gr-wxgui: update copyrights --- gr-wxgui/src/python/constants.py | 2 +- gr-wxgui/src/python/fft_window.py | 2 +- gr-wxgui/src/python/fftsink_nongl.py | 2 +- gr-wxgui/src/python/plot.py | 2 +- gr-wxgui/src/python/plotter/channel_plotter.py | 2 +- gr-wxgui/src/python/plotter/plotter_base.py | 2 +- gr-wxgui/src/python/scope_window.py | 2 +- gr-wxgui/src/python/scopesink_gl.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 93b281aa5..9612f36dd 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index a460fe995..f4f485f4b 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -1,5 +1,5 @@ # -# Copyright 2008, 2009 Free Software Foundation, Inc. +# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index 0854d6668..508b4e772 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006,2007,2009 Free Software Foundation, Inc. +# Copyright 2003,2004,2005,2006,2007,2009,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index 35b7ea9d1..e0bc4ca60 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -6,7 +6,7 @@ # # Created: 2003/11/03 # RCS-ID: $Id$ -# Copyright: (c) 2002,2007 +# Copyright: (c) 2002,2007,2010 # Licence: Use as you wish. #----------------------------------------------------------------------------- # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net) diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index cf38786f2..a3a2b6451 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008, 2009 Free Software Foundation, Inc. +# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 73d697cfd..b856215e9 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -1,5 +1,5 @@ # -# Copyright 2008, 2009 Free Software Foundation, Inc. +# Copyright 2008, 2009, 2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index db8ed8a94..c03b71f1e 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index b3222b43b..ebf9b2939 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # -- cgit From dc79a11cb809b33f397603e7641b155120be9ee8 Mon Sep 17 00:00:00 2001 From: Marcus D Leech Date: Sun, 16 May 2010 08:44:16 -0700 Subject: gr-wxgui: adds stripchart trigger mode to graphics sinks --- gr-wxgui/src/python/common.py | 12 ++++++++++-- gr-wxgui/src/python/scope_window.py | 10 +++++++++- gr-wxgui/src/python/scopesink_gl.py | 2 ++ gr-wxgui/src/python/waterfall_window.py | 7 +++++++ 4 files changed, 28 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 17a7dc0de..3641ae644 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -25,6 +25,8 @@ import wx from gnuradio import gr +RUN_ALWAYS = gr.prefs().get_bool ('wxgui', 'run_always', False) + class wxgui_hb(object): """ The wxgui hier block helper/wrapper class: @@ -47,7 +49,10 @@ class wxgui_hb(object): assert points[0] == self or points[0][0] == self copy = gr.copy(self._hb.input_signature().sizeof_stream_item(0)) handler = self._handler_factory(copy.set_enabled) - handler(False) #initially disable the copy block + if RUN_ALWAYS == False: + handler(False) #initially disable the copy block + else: + handler(True) #initially enable the copy block self._bind_to_visible_event(win=self.win, handler=handler) points = list(points) points.insert(1, copy) #insert the copy block into the chain @@ -67,7 +72,10 @@ class wxgui_hb(object): if cache[0] == visible: return cache[0] = visible #print visible, handler - handler(visible) + if RUN_ALWAYS == False: + handler(visible) + else: + handler(True) return callback @staticmethod diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index f7c0ffa82..e8c3a6ecf 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -36,6 +36,7 @@ import forms # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) +DEFAULT_TRIG_MODE = gr.prefs().get_long('wxgui', 'trig_mode', gr.gr_TRIG_MODE_AUTO) DEFAULT_WIN_SIZE = (600, 300) COUPLING_MODES = ( ('DC', False), @@ -45,6 +46,7 @@ TRIGGER_MODES = ( ('Freerun', gr.gr_TRIG_MODE_FREE), ('Auto', gr.gr_TRIG_MODE_AUTO), ('Normal', gr.gr_TRIG_MODE_NORM), + ('Stripchart', gr.gr_TRIG_MODE_STRIPCHART), ) TRIGGER_SLOPES = ( ('Pos +', gr.gr_TRIG_SLOPE_POS), @@ -388,6 +390,7 @@ class scope_window(wx.Panel, pubsub.pubsub): trigger_channel_key, decimation_key, msg_key, + trig_mode, ): pubsub.pubsub.__init__(self) #check num inputs @@ -427,9 +430,14 @@ class scope_window(wx.Panel, pubsub.pubsub): self[FRAME_RATE_KEY] = frame_rate self[TRIGGER_LEVEL_KEY] = 0 self[TRIGGER_CHANNEL_KEY] = 0 - self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO + self[TRIGGER_MODE_KEY] = trig_mode + self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 + + if self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_STRIPCHART: + self[T_FRAC_OFF_KEY] = 0.0 + for i in range(num_inputs): self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 358361de6..c7dbd4f79 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -75,6 +75,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): xy_mode=False, ac_couple=False, num_inputs=1, + trig_mode=scope_window.DEFAULT_TRIG_MODE, frame_rate=scope_window.DEFAULT_FRAME_RATE, **kwargs #do not end with a comma ): @@ -122,6 +123,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): v_scale=v_scale, v_offset=v_offset, xy_mode=xy_mode, + trig_mode=trig_mode, ac_couple_key=AC_COUPLE_KEY, trigger_level_key=TRIGGER_LEVEL_KEY, trigger_mode_key=TRIGGER_MODE_KEY, diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index b7904e4d9..6536ada10 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -38,6 +38,7 @@ import forms SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30) +DEFAULT_COLOR_MODE = gr.prefs().get_string('wxgui', 'waterfall_color', 'rgb1') DEFAULT_WIN_SIZE = (600, 300) DIV_LEVELS = (1, 2, 5, 10, 20) MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200 @@ -156,6 +157,9 @@ class control_panel(wx.Panel): def _on_incr_time_scale(self, event): old_rate = self.parent[FRAME_RATE_KEY] self.parent[FRAME_RATE_KEY] *= 0.75 + if self.parent[FRAME_RATE_KEY] < 1.0: + self.parent[FRAME_RATE_KEY] = 1.0 + if self.parent[FRAME_RATE_KEY] == old_rate: self.parent[DECIMATION_KEY] += 1 def _on_decr_time_scale(self, event): @@ -217,6 +221,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub): self[REF_LEVEL_KEY] = ref_level self[BASEBAND_FREQ_KEY] = baseband_freq self[COLOR_MODE_KEY] = COLOR_MODES[0][1] + self[COLOR_MODE_KEY] = DEFAULT_COLOR_MODE self[RUNNING_KEY] = True #setup the box with plot and controls self.control_panel = control_panel(self) @@ -280,6 +285,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub): #grid parameters sample_rate = self[SAMPLE_RATE_KEY] frame_rate = self[FRAME_RATE_KEY] + if frame_rate < 1.0 : + frame_rate = 1.0 baseband_freq = self[BASEBAND_FREQ_KEY] num_lines = self[NUM_LINES_KEY] y_divs = self[Y_DIVS_KEY] -- cgit From 7361b03b245776938f0576435e3ccf4806622e3e Mon Sep 17 00:00:00 2001 From: Catalin Patulea Date: Mon, 5 Jul 2010 11:45:52 -0700 Subject: gr-wxgui: fix non-gl scope sink only updating one channel This fixes a bug where, when using nongl scopesink, only the real part of a complex input is shown. After applying this patch, both Ch1 and Ch2 can be seen on the scope display. --- gr-wxgui/src/python/scopesink_nongl.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index 5c1379ee6..bf2c50917 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -217,13 +217,13 @@ class input_watcher (gru.msgq_runner): chan_data = s[start:start+bytes_per_chan] rec = numpy.fromstring (chan_data, numpy.float32) records.append (rec) - - # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - - de = DataEvent (records) - wx.PostEvent (self.event_receiver, de) - records = [] - del de + + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) + + de = DataEvent (records) + wx.PostEvent (self.event_receiver, de) + records = [] + del de self.iscan -= 1 -- cgit From 1620ab8499a7854ff1248a94782a800fb016d1f2 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 21 Jul 2010 14:47:16 -0700 Subject: wxgui: fix to use wx.Colour which is the actual name of the class (wx python wrapper may be missing wx.Color wrapper) --- gr-wxgui/src/python/plotter/gltext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/gltext.py b/gr-wxgui/src/python/plotter/gltext.py index 67f62ca56..1b3c047dc 100644 --- a/gr-wxgui/src/python/plotter/gltext.py +++ b/gr-wxgui/src/python/plotter/gltext.py @@ -193,7 +193,7 @@ class TextElement(object): img = wx.ImageFromBitmap(bmp) alpha = img.GetData() - if isinstance(self._foreground, wx.Color): + if isinstance(self._foreground, wx.Colour): """ If we have a static color... """ -- cgit From a0c01f9f9ca5743189ce5c84ee3f2b1cea819335 Mon Sep 17 00:00:00 2001 From: Eric Blossom Date: Sun, 12 Sep 2010 19:10:56 -0700 Subject: Avoid divide by zero in persistence code when using default args --- gr-wxgui/src/python/fftsink_gl.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index e0306d919..6cfaeff60 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -19,6 +19,8 @@ # Boston, MA 02110-1301, USA. # +from __future__ import division + ################################################## # Imports ################################################## -- cgit From dd74b98a42f5e79c4d464cfa745b3f8af51e486e Mon Sep 17 00:00:00 2001 From: Marcus Leech Date: Sun, 17 Oct 2010 17:20:04 -0400 Subject: Adds a new parameter "y_axis_label" to scopesink2 and the GRC .xml file that contains the string for the Y axis label. It defaults to 'Counts' to be consistent with the old version. --- gr-wxgui/src/python/constants.py | 1 + gr-wxgui/src/python/scope_window.py | 4 +++- gr-wxgui/src/python/scopesink_gl.py | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 9612f36dd..070be0808 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -66,6 +66,7 @@ X_PER_DIV_KEY = 'x_per_div' Y_DIVS_KEY = 'y_divs' Y_OFF_KEY = 'y_off' Y_PER_DIV_KEY = 'y_per_div' +Y_AXIS_LABEL = 'y_axis_label' MAXIMUM_KEY = 'maximum' MINIMUM_KEY = 'minimum' NUM_BINS_KEY = 'num_bins' diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index a9917782f..89a808cec 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -435,6 +435,7 @@ class scope_window(wx.Panel, pubsub.pubsub): use_persistence, persist_alpha, trig_mode, + y_axis_label, ): pubsub.pubsub.__init__(self) #check num inputs @@ -471,6 +472,7 @@ class scope_window(wx.Panel, pubsub.pubsub): self[T_DIVS_KEY] = 8 self[X_DIVS_KEY] = 8 self[Y_DIVS_KEY] = 8 + self[Y_AXIS_LABEL] = y_axis_label self[FRAME_RATE_KEY] = frame_rate self[TRIGGER_LEVEL_KEY] = 0 self[TRIGGER_CHANNEL_KEY] = 0 @@ -676,7 +678,7 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.set_x_label('Time', 's') self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True) #update the y axis - self.plotter.set_y_label('Counts') + self.plotter.set_y_label(self[Y_AXIS_LABEL]) self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) #redraw current sample self.handle_samples() diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 15be23d5a..5ae897400 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -77,6 +77,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): ac_couple=False, num_inputs=1, trig_mode=scope_window.DEFAULT_TRIG_MODE, + y_axis_label='Counts', frame_rate=scope_window.DEFAULT_FRAME_RATE, use_persistence=False, persist_alpha=None, @@ -134,6 +135,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): v_offset=v_offset, xy_mode=xy_mode, trig_mode=trig_mode, + y_axis_label=y_axis_label, ac_couple_key=AC_COUPLE_KEY, trigger_level_key=TRIGGER_LEVEL_KEY, trigger_mode_key=TRIGGER_MODE_KEY, -- cgit From accb9f2fe8fd8f6a1e114adac5b15304b0e0012d Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Wed, 20 Jul 2011 19:04:32 -0700 Subject: gr: squashed cmakelists.txt into one commit --- gr-wxgui/src/python/CMakeLists.txt | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 gr-wxgui/src/python/CMakeLists.txt (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/CMakeLists.txt b/gr-wxgui/src/python/CMakeLists.txt new file mode 100644 index 000000000..81e3d64ca --- /dev/null +++ b/gr-wxgui/src/python/CMakeLists.txt @@ -0,0 +1,88 @@ +# Copyright 2011 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. + +######################################################################## +INCLUDE(GrPython) + +######################################################################## +# Install python files into wxgui module +######################################################################## +GR_PYTHON_INSTALL( + FILES + __init__.py + common.py + constants.py + constsink_gl.py + const_window.py + form.py + fftsink2.py + fftsink_nongl.py + fftsink_gl.py + fft_window.py + gui.py + histosink_gl.py + histo_window.py + numbersink2.py + number_window.py + plot.py + powermate.py + pubsub.py + scopesink2.py + scopesink_nongl.py + scopesink_gl.py + scope_window.py + termsink.py + waterfallsink2.py + waterfallsink_nongl.py + waterfallsink_gl.py + waterfall_window.py + slider.py + stdgui2.py + DESTINATION ${GR_PYTHON_DIR}/wxgui + COMPONENT "wxgui" +) + +######################################################################## +# Install python files into wxgui forms sub-module +######################################################################## +GR_PYTHON_INSTALL( + FILES + forms/__init__.py + forms/forms.py + forms/converters.py + DESTINATION ${GR_PYTHON_DIR}/wxgui/forms + COMPONENT "wxgui" +) + +######################################################################## +# Install python files into wxgui plotter sub-module +######################################################################## +GR_PYTHON_INSTALL( + FILES + plotter/__init__.py + plotter/bar_plotter.py + plotter/channel_plotter.py + plotter/common.py + plotter/gltext.py + plotter/grid_plotter_base.py + plotter/plotter_base.py + plotter/waterfall_plotter.py + DESTINATION ${GR_PYTHON_DIR}/wxgui/plotter + COMPONENT "wxgui" +) -- cgit From 097b9bd5b2374c381380bc26f40677f8bf470c25 Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Tue, 26 Jul 2011 11:47:51 -0700 Subject: wxgui: fix to install wxgui python into gnuradio site-packages, not top level --- gr-wxgui/src/python/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/CMakeLists.txt b/gr-wxgui/src/python/CMakeLists.txt index 81e3d64ca..da86d4a81 100644 --- a/gr-wxgui/src/python/CMakeLists.txt +++ b/gr-wxgui/src/python/CMakeLists.txt @@ -54,7 +54,7 @@ GR_PYTHON_INSTALL( waterfall_window.py slider.py stdgui2.py - DESTINATION ${GR_PYTHON_DIR}/wxgui + DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui COMPONENT "wxgui" ) @@ -66,7 +66,7 @@ GR_PYTHON_INSTALL( forms/__init__.py forms/forms.py forms/converters.py - DESTINATION ${GR_PYTHON_DIR}/wxgui/forms + DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui/forms COMPONENT "wxgui" ) @@ -83,6 +83,6 @@ GR_PYTHON_INSTALL( plotter/grid_plotter_base.py plotter/plotter_base.py plotter/waterfall_plotter.py - DESTINATION ${GR_PYTHON_DIR}/wxgui/plotter + DESTINATION ${GR_PYTHON_DIR}/gnuradio/wxgui/plotter COMPONENT "wxgui" ) -- cgit From a4f3c09ae4714da5ed1d5289e2f59bb864bf4074 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Sat, 8 Oct 2011 14:41:11 -0400 Subject: wxgui: Updating constellation plot to work with gr-digital. --- gr-wxgui/src/python/const_window.py | 22 +++++++++------------- gr-wxgui/src/python/constants.py | 1 + gr-wxgui/src/python/constsink_gl.py | 25 +++++++++++++++---------- 3 files changed, 25 insertions(+), 23 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index f7c7caf07..2ad89b2a3 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -36,7 +36,7 @@ import forms # Constants ################################################## SLIDER_STEPS = 200 -ALPHA_MIN_EXP, ALPHA_MAX_EXP = -6, -0.301 +LOOP_BW_MIN_EXP, LOOP_BW_MAX_EXP = -6, 0.0 GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP = -6, -0.301 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'const_rate', 5) DEFAULT_WIN_SIZE = (500, 400) @@ -70,19 +70,19 @@ class control_panel(wx.Panel): parent=self, label='Options', bold=True, orient=wx.VERTICAL, ) - #alpha + #loop_bw control_box.AddStretchSpacer() forms.text_box( - sizer=control_box, parent=self, label='Alpha', + sizer=control_box, parent=self, label='Loop Bandwidth', converter=forms.float_converter(), - ps=parent, key=ALPHA_KEY, + ps=parent, key=LOOP_BW_KEY, ) forms.log_slider( sizer=control_box, parent=self, - min_exp=ALPHA_MIN_EXP, - max_exp=ALPHA_MAX_EXP, + min_exp=LOOP_BW_MIN_EXP, + max_exp=LOOP_BW_MAX_EXP, num_steps=SLIDER_STEPS, - ps=parent, key=ALPHA_KEY, + ps=parent, key=LOOP_BW_KEY, ) #gain_mu control_box.AddStretchSpacer() @@ -127,8 +127,7 @@ class const_window(wx.Panel, pubsub.pubsub): size, title, msg_key, - alpha_key, - beta_key, + loop_bw_key, gain_mu_key, gain_omega_key, omega_key, @@ -137,8 +136,7 @@ class const_window(wx.Panel, pubsub.pubsub): pubsub.pubsub.__init__(self) #proxy the keys self.proxy(MSG_KEY, controller, msg_key) - self.proxy(ALPHA_KEY, controller, alpha_key) - self.proxy(BETA_KEY, controller, beta_key) + self.proxy(LOOP_BW_KEY, controller, loop_bw_key) self.proxy(GAIN_MU_KEY, controller, gain_mu_key) self.proxy(GAIN_OMEGA_KEY, controller, gain_omega_key) self.proxy(OMEGA_KEY, controller, omega_key) @@ -164,8 +162,6 @@ class const_window(wx.Panel, pubsub.pubsub): main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) #alpha and gain mu 2nd orders - def set_beta(alpha): self[BETA_KEY] = .25*alpha**2 - self.subscribe(ALPHA_KEY, set_beta) def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 self.subscribe(GAIN_MU_KEY, set_gain_omega) #register events diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 070be0808..08cc6a634 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -73,3 +73,4 @@ NUM_BINS_KEY = 'num_bins' FRAME_SIZE_KEY = 'frame_size' CHANNEL_OPTIONS_KEY = 'channel_options' SHOW_CONTROL_PANEL_KEY = 'show_control_panel' +LOOP_BW_KEY = 'loop_bw' diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py index 91bc65d9f..51434df68 100644 --- a/gr-wxgui/src/python/constsink_gl.py +++ b/gr-wxgui/src/python/constsink_gl.py @@ -27,6 +27,12 @@ import common from gnuradio import gr, blks2 from pubsub import pubsub from constants import * +import sys +try: + from gnuradio import digital +except ImportError: + sys.stderr.write("Error: could not import gnuradio.digital, please install gr-digitial.\n") + sys.exit(1) ################################################## # Constellation sink block (wrapper for old wxgui) @@ -47,7 +53,7 @@ class const_sink_c(gr.hier_block2, common.wxgui_hb): #mpsk recv params M=4, theta=0, - alpha=0.005, + loop_bw=6.28/100.0, fmax=0.06, mu=0.5, gain_mu=0.005, @@ -68,16 +74,18 @@ class const_sink_c(gr.hier_block2, common.wxgui_hb): 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 # Costas frequency/phase recovery loop # Critically damped 2nd order PLL - self._costas = gr.costas_loop_cc(alpha, beta, fmax, fmin, M) + self._costas = digital.costas_loop_cc(loop_bw, M) # Timing recovery loop # Critically damped 2nd order DLL - self._retime = gr.clock_recovery_mm_cc(omega, gain_omega, mu, gain_mu, omega_limit) + self._retime = digital.clock_recovery_mm_cc(omega, + gain_omega, + mu, gain_mu, + omega_limit) #sync = gr.mpsk_receiver_cc( # M, #psk order # theta, @@ -97,10 +105,8 @@ class const_sink_c(gr.hier_block2, common.wxgui_hb): #controller def setter(p, k, x): p[k] = x self.controller = pubsub() - self.controller.subscribe(ALPHA_KEY, self._costas.set_alpha) - self.controller.publish(ALPHA_KEY, self._costas.alpha) - self.controller.subscribe(BETA_KEY, self._costas.set_beta) - self.controller.publish(BETA_KEY, self._costas.beta) + self.controller.subscribe(LOOP_BW_KEY, self._costas.set_loop_bandwidth) + self.controller.publish(LOOP_BW_KEY, self._costas.get_loop_bandwidth) self.controller.subscribe(GAIN_MU_KEY, self._retime.set_gain_mu) self.controller.publish(GAIN_MU_KEY, self._retime.gain_mu) self.controller.subscribe(OMEGA_KEY, self._retime.set_omega) @@ -121,8 +127,7 @@ class const_sink_c(gr.hier_block2, common.wxgui_hb): size=size, title=title, msg_key=MSG_KEY, - alpha_key=ALPHA_KEY, - beta_key=BETA_KEY, + loop_bw_key=LOOP_BW_KEY, gain_mu_key=GAIN_MU_KEY, gain_omega_key=GAIN_OMEGA_KEY, omega_key=OMEGA_KEY, -- cgit From 71c0f14a46f85027b95f2f5f6d3d219cc9e3783e Mon Sep 17 00:00:00 2001 From: Josh Blum Date: Sat, 8 Oct 2011 17:11:12 -0700 Subject: gr: the CMakeLists.txt took a chill pill --- gr-wxgui/src/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/CMakeLists.txt b/gr-wxgui/src/python/CMakeLists.txt index da86d4a81..24e06acd5 100644 --- a/gr-wxgui/src/python/CMakeLists.txt +++ b/gr-wxgui/src/python/CMakeLists.txt @@ -18,7 +18,7 @@ # Boston, MA 02110-1301, USA. ######################################################################## -INCLUDE(GrPython) +include(GrPython) ######################################################################## # Install python files into wxgui module -- cgit From 07bed9fa807f63fb155c6cbe1439e5871f4914dd Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Sun, 27 Nov 2011 16:42:44 -0500 Subject: docs: added python doxygen docs to gr-wxgui --- gr-wxgui/src/python/__init__.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/__init__.py b/gr-wxgui/src/python/__init__.py index 8b1378917..a1cfeb223 100644 --- a/gr-wxgui/src/python/__init__.py +++ b/gr-wxgui/src/python/__init__.py @@ -1 +1,25 @@ +# +# Copyright 2011 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. +# +''' +This is the gr-wxgui package. This package provides a GUI interface +using the Wx backend. +''' -- cgit From 00420d32081d8252bb37142b2be19a8a7c4dc4c4 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Thu, 8 Dec 2011 13:48:48 -0800 Subject: Removed autotools, gr-waveform, some cleanup Nick Foster owes Nick Corgan a six-pack of beer! --- gr-wxgui/src/python/.gitignore | 8 ---- gr-wxgui/src/python/Makefile.am | 69 --------------------------------- gr-wxgui/src/python/forms/.gitignore | 1 - gr-wxgui/src/python/plotter/.gitignore | 3 -- gr-wxgui/src/python/plotter/Makefile.am | 40 ------------------- 5 files changed, 121 deletions(-) delete mode 100644 gr-wxgui/src/python/.gitignore delete mode 100644 gr-wxgui/src/python/Makefile.am delete mode 100644 gr-wxgui/src/python/forms/.gitignore delete mode 100644 gr-wxgui/src/python/plotter/.gitignore delete mode 100644 gr-wxgui/src/python/plotter/Makefile.am (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/.gitignore b/gr-wxgui/src/python/.gitignore deleted file mode 100644 index f9c5da0db..000000000 --- a/gr-wxgui/src/python/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -/Makefile -/Makefile.in -/.deps -/.libs -/*.la -/*.lo -/*.pyc -/*.pyo diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am deleted file mode 100644 index 2382d599c..000000000 --- a/gr-wxgui/src/python/Makefile.am +++ /dev/null @@ -1,69 +0,0 @@ -# -# Copyright 2004,2005,2008,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. -# - -include $(top_srcdir)/Makefile.common - -SUBDIRS = plotter - -# Install this stuff so that it ends up as the gnuradio.wxgui module -# This usually ends up at: -# ${prefix}/lib/python${python_version}/site-packages/gnuradio/wxgui - -ourpythondir = $(grpythondir)/wxgui -ourlibdir = $(grpyexecdir)/wxgui - -ourpython_PYTHON = \ - __init__.py \ - common.py \ - constants.py \ - constsink_gl.py \ - const_window.py \ - form.py \ - fftsink2.py \ - fftsink_nongl.py \ - fftsink_gl.py \ - fft_window.py \ - gui.py \ - histosink_gl.py \ - histo_window.py \ - numbersink2.py \ - number_window.py \ - plot.py \ - powermate.py \ - pubsub.py \ - scopesink2.py \ - scopesink_nongl.py \ - scopesink_gl.py \ - scope_window.py \ - termsink.py \ - waterfallsink2.py \ - waterfallsink_nongl.py \ - waterfallsink_gl.py \ - waterfall_window.py \ - slider.py \ - stdgui2.py - -formspythondir = $(grpythondir)/wxgui/forms - -formspython_PYTHON = \ - forms/__init__.py \ - forms/forms.py \ - forms/converters.py diff --git a/gr-wxgui/src/python/forms/.gitignore b/gr-wxgui/src/python/forms/.gitignore deleted file mode 100644 index a74b07aee..000000000 --- a/gr-wxgui/src/python/forms/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/*.pyc diff --git a/gr-wxgui/src/python/plotter/.gitignore b/gr-wxgui/src/python/plotter/.gitignore deleted file mode 100644 index b6950912c..000000000 --- a/gr-wxgui/src/python/plotter/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/Makefile -/Makefile.in -/*.pyc diff --git a/gr-wxgui/src/python/plotter/Makefile.am b/gr-wxgui/src/python/plotter/Makefile.am deleted file mode 100644 index d00f0a425..000000000 --- a/gr-wxgui/src/python/plotter/Makefile.am +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright 2004,2005,2008,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. -# - -include $(top_srcdir)/Makefile.common - -# Install this stuff so that it ends up as the gnuradio.wxgui module -# This usually ends up at: -# ${prefix}/lib/python${python_version}/site-packages/gnuradio/wxgui - -ourpythondir = $(grpythondir)/wxgui/plotter -ourlibdir = $(grpyexecdir)/wxgui/plotter - -ourpython_PYTHON = \ - __init__.py \ - bar_plotter.py \ - channel_plotter.py \ - common.py \ - gltext.py \ - grid_plotter_base.py \ - plotter_base.py \ - waterfall_plotter.py - -- cgit From 5b9769b7d7f2734bf4c5e7aff347fa74da41b5d2 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Tue, 3 Jan 2012 15:36:13 -0500 Subject: scheduler: added argument to wxGUI's stdapp that allows a user to specify the max_noutput_items when building a wxGUI application. --- gr-wxgui/src/python/stdgui2.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py index e510f174c..f397fd01e 100644 --- a/gr-wxgui/src/python/stdgui2.py +++ b/gr-wxgui/src/python/stdgui2.py @@ -27,23 +27,27 @@ from gnuradio import gr class stdapp (wx.App): - def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2): + def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2, + max_noutput_items=None): self.top_block_maker = top_block_maker self.title = title self._nstatus = nstatus + self._max_noutput_items = max_noutput_items # All our initialization must come before calling wx.App.__init__. # OnInit is called from somewhere in the guts of __init__. wx.App.__init__ (self, redirect=False) def OnInit (self): - frame = stdframe (self.top_block_maker, self.title, self._nstatus) + frame = stdframe (self.top_block_maker, self.title, self._nstatus, + self._max_noutput_items) frame.Show (True) self.SetTopWindow (frame) return True class stdframe (wx.Frame): - def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2): + def __init__ (self, top_block_maker, title="GNU Radio", nstatus=2, + max_nouts=None): # print "stdframe.__init__" wx.Frame.__init__(self, None, -1, title) @@ -57,7 +61,7 @@ class stdframe (wx.Frame): self.SetMenuBar (mainmenu) self.Bind (wx.EVT_CLOSE, self.OnCloseWindow) - self.panel = stdpanel (self, self, top_block_maker) + self.panel = stdpanel (self, self, top_block_maker, max_nouts) vbox = wx.BoxSizer(wx.VERTICAL) vbox.Add(self.panel, 1, wx.EXPAND) self.SetSizer(vbox) @@ -72,7 +76,8 @@ class stdframe (wx.Frame): return self.panel.top_block class stdpanel (wx.Panel): - def __init__ (self, parent, frame, top_block_maker): + def __init__ (self, parent, frame, top_block_maker, + max_nouts=None): # print "stdpanel.__init__" wx.Panel.__init__ (self, parent, -1) self.frame = frame @@ -83,7 +88,10 @@ class stdpanel (wx.Panel): self.SetAutoLayout (True) vbox.Fit (self) - self.top_block.start () + if(max_nouts is not None): + self.top_block.start (max_nouts) + else: + self.top_block.start () class std_top_block (gr.top_block): def __init__ (self, parent, panel, vbox, argv): -- cgit From f919f9dcbb54a08e6e26d6c229ce92fb784fa1b2 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Fri, 13 Apr 2012 18:36:53 -0400 Subject: Removed whitespace and added dtools/bin/remove-whitespace as a tool to do this in the future. The sed script was provided by Moritz Fischer. --- gr-wxgui/src/python/__init__.py | 10 +- gr-wxgui/src/python/common.py | 2 +- gr-wxgui/src/python/fft_window.py | 4 +- gr-wxgui/src/python/fftsink_gl.py | 4 +- gr-wxgui/src/python/fftsink_nongl.py | 94 +- gr-wxgui/src/python/form.py | 26 +- gr-wxgui/src/python/forms/__init__.py | 2 +- gr-wxgui/src/python/forms/forms.py | 28 +- gr-wxgui/src/python/gui.py | 18 +- gr-wxgui/src/python/plot.py | 214 ++--- gr-wxgui/src/python/plotter/common.py | 2 +- gr-wxgui/src/python/plotter/gltext.py | 1006 +++++++++++----------- gr-wxgui/src/python/plotter/grid_plotter_base.py | 2 +- gr-wxgui/src/python/plotter/plotter_base.py | 2 +- gr-wxgui/src/python/plotter/waterfall_plotter.py | 2 +- gr-wxgui/src/python/powermate.py | 46 +- gr-wxgui/src/python/scope_window.py | 8 +- gr-wxgui/src/python/scopesink_gl.py | 14 +- gr-wxgui/src/python/scopesink_nongl.py | 80 +- gr-wxgui/src/python/stdgui2.py | 14 +- gr-wxgui/src/python/waterfall_window.py | 4 +- gr-wxgui/src/python/waterfallsink_nongl.py | 46 +- 22 files changed, 814 insertions(+), 814 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/__init__.py b/gr-wxgui/src/python/__init__.py index a1cfeb223..68f8f4b5e 100644 --- a/gr-wxgui/src/python/__init__.py +++ b/gr-wxgui/src/python/__init__.py @@ -1,23 +1,23 @@ # # Copyright 2011 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. -# +# ''' This is the gr-wxgui package. This package provides a GUI interface diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index 3641ae644..1410d29df 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -121,7 +121,7 @@ def _register_access_method(destination, controller, key): def set(value): controller[key] = value setattr(destination, 'set_'+key, set) def get(): return controller[key] - setattr(destination, 'get_'+key, get) + setattr(destination, 'get_'+key, get) def register_access_methods(destination, controller): """ diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index f4f485f4b..99c1cf6e1 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -127,7 +127,7 @@ class control_panel(wx.Panel): parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) #allways show initially, so room is reserved for them widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - + parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) #trace menu @@ -201,7 +201,7 @@ class control_panel(wx.Panel): # Just ignore the key value we get # we only need to now that the visability or size of something has changed self.parent.Layout() - #self.parent.Fit() + #self.parent.Fit() ################################################## # FFT window with plotter and control panel diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 6cfaeff60..6b268f6cb 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -63,14 +63,14 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate #ensure analog alpha - if persist_alpha is None: + if persist_alpha is None: actual_fft_rate=float(sample_rate/fft_size)/float(max(1,int(float((sample_rate/fft_size)/fft_rate)))) #print "requested_fft_rate ",fft_rate #print "actual_fft_rate ",actual_fft_rate analog_cutoff_freq=0.5 # Hertz #calculate alpha from wanted cutoff freq persist_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) - + #init gr.hier_block2.__init__( self, diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index 508b4e772..c756d8a3b 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -1,31 +1,31 @@ #!/usr/bin/env python # # Copyright 2003,2004,2005,2006,2007,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. -# +# from gnuradio import gr, gru, window from gnuradio.wxgui import stdgui2 import wx import plot import numpy -import math +import math DIV_LEVELS = (1, 2, 5, 10, 20) @@ -33,7 +33,7 @@ default_fftsink_size = (640,240) default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) class fft_sink_base(object): - def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, + def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, @@ -73,7 +73,7 @@ class fft_sink_base(object): else: self.avg.set_taps(1.0) self.win.peak_vals = None - + def set_peak_hold(self, enable): self.peak_hold = enable self.win.set_peak_hold(enable) @@ -98,7 +98,7 @@ class fft_sink_base(object): def _set_n(self): self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + class fft_sink_f(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, ref_scale=2.0, @@ -116,17 +116,17 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, peak_hold=peak_hold,use_persistence=use_persistence,persist_alpha=persist_alpha) - + self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + mywindow = window.blackmanharris(self.fft_size) self.fft = gr.fft_vfc(self.fft_size, True, mywindow) power = 0 for tap in mywindow: power += tap*tap - + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) @@ -135,7 +135,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): -10*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale - + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) @@ -165,13 +165,13 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + mywindow = window.blackmanharris(self.fft_size) self.fft = gr.fft_vcc(self.fft_size, True, mywindow) power = 0 for tap in mywindow: power += tap*tap - + self.c2mag = gr.complex_to_mag(self.fft_size) self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size) @@ -180,7 +180,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): -10*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale - + self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True) self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink) @@ -203,7 +203,7 @@ class DataEvent(wx.PyEvent): self.SetEventType (myDATA_EVENT) self.data = data - def Clone (self): + def Clone (self): self.__class__ (self.GetId()) @@ -231,20 +231,20 @@ class input_watcher (gru.msgq_runner): del de class control_panel(wx.Panel): - - class LabelText(wx.StaticText): + + class LabelText(wx.StaticText): def __init__(self, window, label): wx.StaticText.__init__(self, window, -1, label) font = self.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) font.SetUnderlined(True) self.SetFont(font) - + def __init__(self, parent): self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) - + #checkboxes for average and peak hold control_box.AddStretchSpacer() control_box.Add(self.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) @@ -255,9 +255,9 @@ class control_panel(wx.Panel): self.use_persistence_check_box.Bind(wx.EVT_CHECKBOX, parent.on_use_persistence) control_box.Add(self.use_persistence_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, parent.on_peak_hold) + self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) - + #radio buttons for div size control_box.AddStretchSpacer() control_box.Add(self.LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER) @@ -269,12 +269,12 @@ class control_panel(wx.Panel): 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(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - button_box = wx.BoxSizer(wx.HORIZONTAL) + button_box = wx.BoxSizer(wx.HORIZONTAL) self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT) self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level) button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER) @@ -287,7 +287,7 @@ class control_panel(wx.Panel): self.SetSizerAndFit(control_box) #update self.update() - + def update(self): """ Read the state of the fft plot settings and update the control panel. @@ -296,14 +296,14 @@ class control_panel(wx.Panel): self.average_check_box.SetValue(self.parent.fftsink.average) self.use_persistence_check_box.SetValue(self.parent.fftsink.use_persistence) self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) - #update radio buttons + #update radio buttons try: index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div) self.radio_buttons[index].SetValue(True) except: pass - + def on_radio_button_change(self, evt): - selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] + selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] index = self.radio_buttons.index(selected_radio_button) self.parent.fftsink.set_y_per_div(DIV_LEVELS[index]) @@ -311,41 +311,41 @@ class fft_window (wx.Panel): def __init__ (self, fftsink, parent, id = -1, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.DEFAULT_FRAME_STYLE, name = ""): - + self.fftsink = fftsink - #init panel and plot - wx.Panel.__init__(self, parent, -1) - self.plot = plot.PlotCanvas(self, id, pos, size, style, name) + #init panel and plot + wx.Panel.__init__(self, parent, -1) + self.plot = plot.PlotCanvas(self, id, pos, size, style, name) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer (wx.HORIZONTAL) main_box.Add (self.plot, 1, wx.EXPAND) main_box.Add (self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) - + self.peak_hold = False self.peak_vals = None self.use_persistence=False self.persist_alpha=0.2 - + self.plot.SetEnableGrid (True) # self.SetEnableZoom (True) # self.SetBackgroundColour ('black') - + self.build_popup_menu() self.set_baseband_freq(self.fftsink.baseband_freq) - + EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click) self.plot.Bind(wx.EVT_MOTION, self.evt_motion) - + self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self) def set_scale(self, freq): - x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) + x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq)) if x >= 1e9: self._scale_factor = 1e-9 self._units = "GHz" @@ -364,7 +364,7 @@ class fft_window (wx.Panel): self.peak_vals = None self.set_scale(baseband_freq) self.fftsink.set_baseband_freq(baseband_freq) - + def on_close_window (self, event): print "fft_window:on_close_window" self.keep_running = False @@ -381,7 +381,7 @@ class fft_window (wx.Panel): self.peak_vals = numpy.maximum(dB, self.peak_vals) if self.fftsink.input_is_real: # only plot 1/2 the points - x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate + x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate * self._scale_factor / L)) + self.fftsink.baseband_freq * self._scale_factor) self._points = numpy.zeros((len(x_vals), 2), numpy.float64) @@ -415,7 +415,7 @@ class fft_window (wx.Panel): ymax = self.fftsink.ref_level ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs y_range = ymin, ymax - self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) + self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) def set_use_persistence(self, enable): self.use_persistence = enable @@ -489,7 +489,7 @@ class fft_window (wx.Panel): def evt_motion(self, event): if not hasattr(self, "_points"): return # Got here before first window data update - + # Clip to plotted values (ux, uy) = self.plot.GetXY(event) # Scaled position x_vals = numpy.array(self._points[:,0]) @@ -510,7 +510,7 @@ class fft_window (wx.Panel): tip.Enable(True) tip.SetDelay(0) self.SetToolTip(tip) - + def build_popup_menu(self): self.id_incr_ref_level = wx.NewId() self.id_decr_ref_level = wx.NewId() @@ -524,7 +524,7 @@ class fft_window (wx.Panel): self.id_average = wx.NewId() self.id_use_persistence = wx.NewId() self.id_peak_hold = wx.NewId() - + self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) self.plot.Bind(wx.EVT_MENU, self.on_use_persistence, id=self.id_use_persistence) self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) @@ -537,7 +537,7 @@ class fft_window (wx.Panel): self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5) self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10) self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20) - + # make a menu menu = wx.Menu() self.popup_menu = menu diff --git a/gr-wxgui/src/python/form.py b/gr-wxgui/src/python/form.py index b55b04d73..0442e49c8 100644 --- a/gr-wxgui/src/python/form.py +++ b/gr-wxgui/src/python/form.py @@ -1,24 +1,24 @@ #!/usr/bin/env python # # Copyright 2005 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 gnuradio import eng_notation @@ -32,7 +32,7 @@ def button_with_callback(parent, label, callback): btn = wx.Button(parent, new_id, label) wx.EVT_BUTTON(parent, new_id, lambda evt: callback()) return btn - + # ---------------------------------------------------------------- # Format converters @@ -128,7 +128,7 @@ class field(object): sizer.Add(label_widget, 0, wx.EXPAND) sizer.Add(widget, weight, wx.EXPAND) return widget - + def _error_msg(self): prefix = '' if self.label: @@ -223,7 +223,7 @@ class quantized_slider_field(field): self.max = range[1] self.step_size = float(range[2]) nsteps = int((self.max-self.min)/self.step_size) - + new_id = wx.NewId() w = wx.Slider(parent, new_id, 0, 0, nsteps, size=wx.Size(250, -1), style=wx.SL_HORIZONTAL) @@ -272,7 +272,7 @@ class radiobox_field(field): style=wx.RA_SPECIFY_ROWS | wx.RA_HORIZONTAL else: style=wx.RA_SPECIFY_COLS | wx.RA_HORIZONTAL - + w = wx.RadioBox(parent, new_id, label=label, style=style, majorDimension=major_dimension, choices=choices) self.f = self._pair_with_label(w, parent=parent, sizer=sizer, label=None, weight=weight) @@ -301,7 +301,7 @@ class form(dict): """ vals = [f.get_value_with_check() for f in self.values()] return [t[1] for t in vals if t[1] is not None] - + def get_key_vals(self): d = {} for (key, f) in self.items(): @@ -310,7 +310,7 @@ class form(dict): def _nop(*args): pass - + def check_input_and_call(self, callback, status_handler=_nop): """ Return a function that checks the form for errors, and then if it's OK, @@ -352,7 +352,7 @@ class demo_app_flow_graph (stdgui2.std_top_block): return True self.form = form() - + self.form['static1'] = \ static_text_field(parent=panel, sizer=vbox, label="Static Text", @@ -382,7 +382,7 @@ class demo_app_flow_graph (stdgui2.std_top_block): def _set_status_msg(self, msg): self.frame.GetStatusBar().SetStatusText(msg, 0) - + def main (): app = stdgui2.stdapp(demo_app_flow_graph, "wxgui form demo", nstatus=1) app.MainLoop () diff --git a/gr-wxgui/src/python/forms/__init__.py b/gr-wxgui/src/python/forms/__init__.py index 3f9f4c735..3068b18fe 100644 --- a/gr-wxgui/src/python/forms/__init__.py +++ b/gr-wxgui/src/python/forms/__init__.py @@ -21,7 +21,7 @@ """ The following classes will be available through gnuradio.wxgui.forms: -""" +""" ######################################################################## # External Converters diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index 19b30ffb0..f1d0038ab 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -29,7 +29,7 @@ The forms follow a layered model: * translation layer * translates the between the external and internal layers * handles parsing errors between layers - * external layer + * external layer * provided external access to the user * set_value, get_value, and optional callback * set and get through optional pubsub and key @@ -511,9 +511,9 @@ from gnuradio.wxgui import gui class app_gui (object): def __init__(self, frame, panel, vbox, top_block, options, args): - + def callback(v): print v - + radio_buttons( sizer=vbox, parent=panel, @@ -525,7 +525,7 @@ class app_gui (object): callback=callback, #major_dimension = 2, ) - + radio_buttons( sizer=vbox, parent=panel, @@ -537,7 +537,7 @@ class app_gui (object): callback=callback, #major_dimension = 2, ) - + radio_buttons( sizer=vbox, parent=panel, @@ -548,7 +548,7 @@ class app_gui (object): callback=callback, #major_dimension = 2, ) - + button( sizer=vbox, parent=panel, @@ -559,8 +559,8 @@ class app_gui (object): callback=callback, #width=100, ) - - + + drop_down( sizer=vbox, parent=panel, @@ -584,7 +584,7 @@ class app_gui (object): callback=callback, width=200, ) - + static_text( sizer=vbox, parent=panel, @@ -593,7 +593,7 @@ class app_gui (object): width=-1, bold=True, ) - + slider( sizer=vbox, parent=panel, @@ -601,7 +601,7 @@ class app_gui (object): label='slider', callback=callback, ) - + log_slider( sizer=vbox, parent=panel, @@ -609,7 +609,7 @@ class app_gui (object): label='slider', callback=callback, ) - + slider( sizer=vbox, parent=panel, @@ -619,7 +619,7 @@ class app_gui (object): style=wx.SL_VERTICAL, length=30, ) - + toggle_button( sizer=vbox, parent=panel, @@ -627,7 +627,7 @@ class app_gui (object): label='toggle it', callback=callback, ) - + single_button( sizer=vbox, parent=panel, diff --git a/gr-wxgui/src/python/gui.py b/gr-wxgui/src/python/gui.py index 2f59af593..ccc773eab 100644 --- a/gr-wxgui/src/python/gui.py +++ b/gr-wxgui/src/python/gui.py @@ -1,23 +1,23 @@ # # 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 gnuradio import gr @@ -47,7 +47,7 @@ class top_panel(wx.Panel): p = wx.Panel(self) p.SetSize((640,480)) vbox.Add(p, 1, wx.EXPAND) - + self.SetSizer(vbox) self.SetAutoLayout(True) vbox.Fit(self) @@ -62,7 +62,7 @@ class top_panel(wx.Panel): # Top-level window frame with menu and status bars. # class top_frame(wx.Frame): - def __init__ (self, top_block, gui, options, args, + def __init__ (self, top_block, gui, options, args, title, nstatus, start, realtime): wx.Frame.__init__(self, None, -1, title) @@ -109,11 +109,11 @@ class top_frame(wx.Frame): # -# Top-level wxPython application object. User creates or subclasses this +# Top-level wxPython application object. User creates or subclasses this # in their GUI script. # class app(wx.App): - def __init__ (self, top_block=None, gui=None, options=None, args=None, + def __init__ (self, top_block=None, gui=None, options=None, args=None, title="GNU Radio", nstatus=1, start=False, realtime=False): self.top_block = top_block self.gui = gui diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index e0bc4ca60..041a2a7a5 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -27,13 +27,13 @@ # # Oct 15, 2004 Gordon Williams (g_will@cyberus.ca) # - Imported modules given leading underscore to name. -# - Added Cursor Line Tracking and User Point Labels. +# - Added Cursor Line Tracking and User Point Labels. # - Demo for Cursor Line Tracking and Point Labels. # - Size of plot preview frame adjusted to show page better. # - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas. # - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve) # can be in either user coords or screen coords. -# +# # May 27, 2007 Johnathan Corgan (jcorgan@corganenterprises.com) # - Converted from numarray to numpy # @@ -45,8 +45,8 @@ This is a simple light weight plotting module that can be used with Boa or easily integrated into your own wxPython application. The emphasis is on small size and fast plotting for large data sets. It has a reasonable number of features to do line and scatter graphs -easily as well as simple bar graphs. It is not as sophisticated or -as powerful as SciPy Plt or Chaco. Both of these are great packages +easily as well as simple bar graphs. It is not as sophisticated or +as powerful as SciPy Plt or Chaco. Both of these are great packages but consume huge amounts of computer resources for simple plots. They can be found at http://scipy.com @@ -69,7 +69,7 @@ Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca) -Doc strings and lots of comments -Optimizations for large number of points -Legends - + Did a lot of work here to speed markers up. Only a factor of 4 improvement though. Lines are much faster than markers, especially filled markers. Stay away from circles and triangles unless you @@ -128,11 +128,11 @@ class PolyPoints: self.scaled = self.points self.attributes = {} self.attributes.update(self._attributes) - for name, value in attr.items(): + for name, value in attr.items(): if name not in self._attributes.keys(): raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys() self.attributes[name] = value - + def boundingBox(self): if len(self.points) == 0: # no curves to draw @@ -154,7 +154,7 @@ class PolyPoints: self.currentScale= scale self.currentShift= shift # else unchanged use the current scaling - + def getLegend(self): return self.attributes['legend'] @@ -177,13 +177,13 @@ class PolyPoints: pntIndex = _numpy.argmin(d) dist = d[pntIndex] return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist] - - + + class PolyLine(PolyPoints): """Class to define line type and style - All methods except __init__ are private. """ - + _attributes = {'colour': 'black', 'width': 1, 'style': wx.SOLID, @@ -224,7 +224,7 @@ class PolyMarker(PolyPoints): """Class to define marker type and style - All methods except __init__ are private. """ - + _attributes = {'colour': 'black', 'width': 1, 'size': 2, @@ -245,7 +245,7 @@ class PolyMarker(PolyPoints): 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill) 'marker'= 'circle' - Marker shape 'legend'= '' - Marker Legend to display - + Marker Shapes: - 'circle' - 'dot' @@ -255,7 +255,7 @@ class PolyMarker(PolyPoints): - 'cross' - 'plus' """ - + PolyPoints.__init__(self, points, attr) def draw(self, dc, printerScale, coord= None): @@ -315,7 +315,7 @@ class PolyMarker(PolyPoints): poly.shape= (len(coords),3,2) poly += shape dc.DrawPolygonList(poly.astype(_numpy.int32)) - + def _cross(self, dc, coords, size=1): fact= 2.5*size for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]: @@ -370,7 +370,7 @@ class PlotGraphics: def setYLabel(self, yLabel= ''): """Set the Y axis label on the graph""" self.yLabel= yLabel - + def setTitle(self, title= ''): """Set the title at the top of graph""" self.title= title @@ -401,14 +401,14 @@ class PlotGraphics: oSymExt = o.getSymExtent(printerScale) symExt = _numpy.maximum(symExt, oSymExt) return symExt - + def getLegendNames(self): """Returns list of legend names""" lst = [None]*len(self) for i in range(len(self)): lst[i]= self.objects[i].getLegend() return lst - + def __len__(self): return len(self.objects) @@ -432,12 +432,12 @@ class PlotCanvas(wx.Window): self.decim_counter=0 """Constucts a window, which can be a child of a frame, dialog or any other non-control window""" - + wx.Window.__init__(self, parent, id, pos, size, style, name) self.border = (1,1) self.SetBackgroundColour("white") - + # Create some mouse events for zooming self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp) @@ -466,7 +466,7 @@ class PlotCanvas(wx.Window): self._zoomCorner2= _numpy.array([0.0, 0.0]) # left mouse up corner self._zoomEnabled= False self._hasDragged= False - + # Drawing Variables self.last_draw = None self._pointScale= 1 @@ -476,7 +476,7 @@ class PlotCanvas(wx.Window): self._gridEnabled= False self._legendEnabled= False self._xUseScopeTicks= False - + # Fonts self._fontCache = {} self._fontSizeAxis= 10 @@ -504,13 +504,13 @@ class PlotCanvas(wx.Window): def set_persist_alpha(self, persist_alpha): self.alpha = persist_alpha - + # SaveFile def SaveFile(self, fileName= ''): """Saves the file to the type specified in the extension. If no file name is specified a dialog box is provided. Returns True if sucessful, otherwise False. - + .bmp Save a Windows bitmap file. .xbm Save an X bitmap file. .xpm Save an XPM bitmap file. @@ -519,7 +519,7 @@ class PlotCanvas(wx.Window): """ if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']: dlg1 = wx.FileDialog( - self, + self, "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "", "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg", wx.SAVE|wx.OVERWRITE_PROMPT @@ -576,7 +576,7 @@ class PlotCanvas(wx.Window): self.print_data=data.GetPrintData() # updates print_data finally: dlg.Destroy() - + def Printout(self, paper=None): """Print current plot.""" if paper != None: @@ -614,11 +614,11 @@ class PlotCanvas(wx.Window): def SetFontSizeAxis(self, point= 10): """Set the tick and axis label font size (default is 10 point)""" self._fontSizeAxis= point - + def GetFontSizeAxis(self): """Get current tick and axis label font size in points""" return self._fontSizeAxis - + def SetFontSizeTitle(self, point= 15): """Set Title font size (default is 15 point)""" self._fontSizeTitle= point @@ -626,11 +626,11 @@ class PlotCanvas(wx.Window): def GetFontSizeTitle(self): """Get current Title font size in points""" return self._fontSizeTitle - + def SetFontSizeLegend(self, point= 7): """Set Legend font size (default is 7 point)""" self._fontSizeLegend= point - + def GetFontSizeLegend(self): """Get current Legend font size in points""" return self._fontSizeLegend @@ -660,7 +660,7 @@ class PlotCanvas(wx.Window): """Set True to enable legend.""" if value not in [True,False]: raise TypeError, "Value should be True or False" - self._legendEnabled= value + self._legendEnabled= value self.Redraw() def GetEnableLegend(self): @@ -671,7 +671,7 @@ class PlotCanvas(wx.Window): """Set True to enable pointLabel.""" if value not in [True,False]: raise TypeError, "Value should be True or False" - self._pointLabelEnabled= value + self._pointLabelEnabled= value self.Redraw() #will erase existing pointLabel if present self.last_PointLabel = None @@ -694,8 +694,8 @@ class PlotCanvas(wx.Window): self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: self.Draw(self.last_draw[0]) - - def ScrollRight(self, units): + + def ScrollRight(self, units): """Move view right number of axis units.""" self.last_PointLabel = None #reset pointLabel if self.last_draw is not None: @@ -710,7 +710,7 @@ class PlotCanvas(wx.Window): graphics, xAxis, yAxis= self.last_draw yAxis= (yAxis[0]+units, yAxis[1]+units) self.Draw(graphics,xAxis,yAxis) - + def GetXY(self,event): """Takes a mouse event and returns the XY user axis values.""" x,y= self.PositionScreenToUser(event.GetPosition()) @@ -721,13 +721,13 @@ class PlotCanvas(wx.Window): userPos= _numpy.array(pntXY) x,y= userPos * self._pointScale + self._pointShift return x,y - + def PositionScreenToUser(self, pntXY): """Converts Screen position to User Coordinates""" screenPos= _numpy.array(pntXY) x,y= (screenPos-self._pointShift)/self._pointScale return x,y - + def SetXSpec(self, type= 'auto'): """xSpec- defines x axis type. Can be 'none', 'min' or 'auto' where: @@ -736,7 +736,7 @@ class PlotCanvas(wx.Window): 'auto' - rounds axis range to sensible values """ self._xSpec= type - + def SetYSpec(self, type= 'auto'): """ySpec- defines x axis type. Can be 'none', 'min' or 'auto' where: @@ -749,11 +749,11 @@ class PlotCanvas(wx.Window): def GetXSpec(self): """Returns current XSpec for axis""" return self._xSpec - + def GetYSpec(self): """Returns current YSpec for axis""" return self._ySpec - + def GetXMaxRange(self): """Returns (minX, maxX) x-axis range for displayed graph""" graphics= self.last_draw[0] @@ -771,15 +771,15 @@ class PlotCanvas(wx.Window): def GetXCurrentRange(self): """Returns (minX, maxX) x-axis for currently displayed portion of graph""" return self.last_draw[1] - + def GetYCurrentRange(self): """Returns (minY, maxY) y-axis for currently displayed portion of graph""" return self.last_draw[2] - + def SetXUseScopeTicks(self, v=False): """Always 10 divisions, no labels""" self._xUseScopeTicks = v - + def GetXUseScopeTicks(self): return self._xUseScopeTicks @@ -788,7 +788,7 @@ class PlotCanvas(wx.Window): graphics- instance of PlotGraphics with list of PolyXXX objects xAxis - tuple with (min, max) axis range to view yAxis - same as xAxis - dc - drawing context - doesn't have to be specified. + dc - drawing context - doesn't have to be specified. If it's not, the offscreen buffer is used """ # check Axis is either tuple or none @@ -796,7 +796,7 @@ class PlotCanvas(wx.Window): raise TypeError, "xAxis should be None or (minX,maxX)" if type(yAxis) not in [type(None),tuple]: raise TypeError, "yAxis should be None or (minY,maxY)" - + # check case for axis = (a,b) where a==b caused by improper zooms if xAxis != None: if xAxis[0] == xAxis[1]: @@ -804,22 +804,22 @@ class PlotCanvas(wx.Window): if yAxis != None: if yAxis[0] == yAxis[1]: return - + if dc == None: - # sets new dc and clears it + # sets new dc and clears it if self.use_persistence: dc = wx.MemoryDC() dc.SelectObject(self._Buffer) dc.Clear() else: dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.Clear() - + dc.Clear() + dc.BeginDrawing() # dc.Clear() - - + + # set font size for every thing but title and legend dc.SetFont(self._getFont(self._fontSizeAxis)) @@ -847,7 +847,7 @@ class PlotCanvas(wx.Window): dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) #wx.SOLID wx.TRANSPARENT ) ) #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) - #dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) ) + #dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) ) #dc.SetLogicalFunction(wx.COPY) # Get ticks and textExtents for axis if required @@ -908,13 +908,13 @@ class PlotCanvas(wx.Window): self._pointScale= scale # make available for mouse events self._pointShift= shift - #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT + #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) - #dc.SetLogicalFunction(wx.COPY) - + #dc.SetLogicalFunction(wx.COPY) + graphics.scaleAndShift(scale, shift) graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing - + # set clipping area so drawing does not occur outside axis box ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight) @@ -946,7 +946,7 @@ class PlotCanvas(wx.Window): self._Buffer2array +=self._Bufferarray.astype(_numpy.uint32)*alpha_int self._Buffer2array /=256 - ##copy back to image buffer + ##copy back to image buffer self._Buffer2.CopyFromBuffer(self._Buffer2array.astype(_numpy.uint8)) #, format=wx.BitmapBufferFormat_RGB, stride=-1) #draw to the screen @@ -958,7 +958,7 @@ class PlotCanvas(wx.Window): dc2.DrawBitmap(self._Buffer2, 0, 0, False) #dc2.DrawBitmap(self._Buffer, 0, 0, False) dc2.EndDrawing() - + def Redraw(self, dc= None): """Redraw the existing plot.""" if self.last_draw is not None: @@ -986,13 +986,13 @@ class PlotCanvas(wx.Window): xAxis = ( x - w/2, x + w/2 ) yAxis = ( y - h/2, y + h/2 ) self.Draw(graphics, xAxis, yAxis) - + def GetClosestPoints(self, pntXY, pointScaled= True): """Returns list with [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] list for each curve. Returns [] if no curves are being plotted. - + x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords @@ -1016,7 +1016,7 @@ class PlotCanvas(wx.Window): [curveNumber, legend, index of closest point, pointXY, scaledXY, distance] list for only the closest curve. Returns [] if no curves are being plotted. - + x, y in user coords if pointScaled == True based on screen coords if pointScaled == False based on user coords @@ -1042,7 +1042,7 @@ class PlotCanvas(wx.Window): you specify. This function can be called from parent window with onClick, - onMotion events etc. + onMotion events etc. """ if self.last_PointLabel != None: #compare pointXY @@ -1088,7 +1088,7 @@ class PlotCanvas(wx.Window): def OnMouseDoubleClick(self,event): if self._zoomEnabled: self.Reset() - + def OnMouseRightDown(self,event): if self._zoomEnabled: X,Y = self.GetXY(event) @@ -1113,13 +1113,13 @@ class PlotCanvas(wx.Window): # a file, or whatever. self._Buffer = wx.EmptyBitmap(Size[0],Size[1],24) - + if True: #self.use_persistence: #self._Bufferarray = _numpy.zeros((Size[0], Size[1],3), dtype=_numpy.uint8) self._Bufferarray = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint8) # Make new second offscreen bitmap: this bitmap will always have the - # last drawing in it, so it can be used to do display time dependent processing + # last drawing in it, so it can be used to do display time dependent processing # like averaging (IIR) or show differences between updates self._Buffer2 = wx.EmptyBitmap(Size[0],Size[1],24) # now the extra buffers for the IIR processing @@ -1144,24 +1144,24 @@ class PlotCanvas(wx.Window): self._drawPointLabel(self.last_PointLabel) #erase old self.last_PointLabel = None - + # Private Methods ************************************************** def _setSize(self, width=None, height=None): """DC width and height.""" if width == None: (self.width,self.height) = self.GetClientSize() else: - self.width, self.height= width,height + self.width, self.height= width,height self.plotbox_size = 0.97*_numpy.array([self.width, self.height]) xo = 0.5*(self.width-self.plotbox_size[0]) yo = self.height-0.5*(self.height-self.plotbox_size[1]) self.plotbox_origin = _numpy.array([xo, yo]) - + def _setPrinterScale(self, scale): """Used to thicken lines and increase marker size for print out.""" # line thickness on printer is very thin at 600 dot/in. Markers small self.printerScale= scale - + def _printDraw(self, printDC): """Used for printing.""" if self.last_draw != None: @@ -1183,7 +1183,7 @@ class PlotCanvas(wx.Window): dc = wx.ClientDC( self ) #this will erase if called twice dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst - + def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt): """Draws legend symbols and text""" @@ -1222,7 +1222,7 @@ class PlotCanvas(wx.Window): xLabelWH= dc.GetTextExtent(xLabel) yLabelWH= dc.GetTextExtent(yLabel) return titleWH, xLabelWH, yLabelWH - + def _legendWH(self, dc, graphics): """Returns the size in screen units for legend box""" if self._legendEnabled != True: @@ -1236,7 +1236,7 @@ class PlotCanvas(wx.Window): txtExt= dc.GetTextExtent(txtList[0]) for txt in graphics.getLegendNames()[1:]: txtExt= _numpy.maximum(txtExt,dc.GetTextExtent(txt)) - maxW= symExt[0]+txtExt[0] + maxW= symExt[0]+txtExt[0] maxH= max(symExt[1],txtExt[1]) # padding .1 for lhs of legend box and space between lines maxW= maxW* 1.1 @@ -1250,7 +1250,7 @@ class PlotCanvas(wx.Window): ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2) # draw rectangle dc = wx.ClientDC( self ) - dc.BeginDrawing() + dc.BeginDrawing() dc.SetPen(wx.Pen(wx.BLACK)) dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) ) dc.SetLogicalFunction(wx.INVERT) @@ -1285,8 +1285,8 @@ class PlotCanvas(wx.Window): plr= _numpy.maximum(pt1,pt2) # Lower right corner rectWidth, rectHeight= plr-pul ptx,pty= pul - return ptx, pty, rectWidth, rectHeight - + return ptx, pty, rectWidth, rectHeight + def _axisInterval(self, spec, lower, upper): """Returns sensible axis range for given spec""" if spec == 'none' or spec == 'min': @@ -1320,10 +1320,10 @@ class PlotCanvas(wx.Window): raise ValueError, str(spec) + ': illegal axis specification' def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks): - + penWidth= self.printerScale # increases thickness for printing only dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth)) - + # set length of tick marks--long ones make grid if self._gridEnabled: x,y,width,height= self._point2ClientCoord(p1,p2) @@ -1332,7 +1332,7 @@ class PlotCanvas(wx.Window): else: yTickLength= 3 * self.printerScale # lengthens lines for printing xTickLength= 3 * self.printerScale - + if self._xSpec is not 'none': lower, upper = p1[0],p2[0] text = 1 @@ -1377,7 +1377,7 @@ class PlotCanvas(wx.Window): factor = f grid = factor * 10.**power if power > 4 or power < -4: - format = '%+7.1e' + format = '%+7.1e' elif power >= 0: digits = max(1, int(power)) format = '%' + `digits`+'.0f' @@ -1441,14 +1441,14 @@ class PlotPrintout(wx.Printout): dcSize= dc.GetSize() # DC size pageSize= self.GetPageSizePixels() # page size in terms of pixcels clientDcSize= self.graph.GetClientSize() - + # find what the margins are (mm) margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft() margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight() # calculate offset and scale for dc pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in) - pixRight= margRightSize*PPIPrinter[0]/25.4 + pixRight= margRightSize*PPIPrinter[0]/25.4 pixTop= margTopSize*PPIPrinter[1]/25.4 pixBottom= margBottomSize*PPIPrinter[1]/25.4 @@ -1463,10 +1463,10 @@ class PlotPrintout(wx.Printout): pixTop *= ratioH plotAreaW *= ratioW plotAreaH *= ratioH - + # rescale plot to page or preview plot area self.graph._setSize(plotAreaW,plotAreaH) - + # Set offset and scale dc.SetDeviceOrigin(pixLeft,pixTop) @@ -1511,7 +1511,7 @@ def _draw1Objects(): markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), (3.*pi/4., -1)], legend='Cross Legend', colour='blue', marker='cross') - + return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis") def _draw2Objects(): @@ -1593,16 +1593,16 @@ class TestFrame(wx.Frame): menu = wx.Menu() menu.Append(200, 'Page Setup...', 'Setup the printer page') self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200) - + menu.Append(201, 'Print Preview...', 'Show the current plot on page') self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201) - + menu.Append(202, 'Print...', 'Print the current plot') self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202) - + menu.Append(203, 'Save Plot...', 'Save current plot') self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203) - + menu.Append(205, 'E&xit', 'Enough of this already!') self.Bind(wx.EVT_MENU, self.OnFileExit, id=205) self.mainmenu.Append(menu, '&File') @@ -1620,25 +1620,25 @@ class TestFrame(wx.Frame): self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210) menu.Append(260, 'Draw6', 'Draw plots6') self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260) - + menu.Append(211, '&Redraw', 'Redraw plots') self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211) menu.Append(212, '&Clear', 'Clear canvas') self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212) menu.Append(213, '&Scale', 'Scale canvas') - self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213) + self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213) menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214) + self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214) menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK) self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215) menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK) self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220) menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK) self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222) - + menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit') - self.Bind(wx.EVT_MENU,self.OnScrUp, id=225) + self.Bind(wx.EVT_MENU,self.OnScrUp, id=225) menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units') self.Bind(wx.EVT_MENU,self.OnScrRt, id=230) menu.Append(235, '&Plot Reset', 'Reset to original plot') @@ -1655,7 +1655,7 @@ class TestFrame(wx.Frame): # A status bar to tell people what's happening self.CreateStatusBar(1) - + self.client = PlotCanvas(self) #define the function for drawing pointLabels self.client.SetPointLabelFunc(self.DrawPointLabel) @@ -1678,7 +1678,7 @@ class TestFrame(wx.Frame): # ---------- dc.SetPen(wx.Pen(wx.BLACK)) dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) - + sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point px,py = mDataDict["pointXY"] @@ -1703,7 +1703,7 @@ class TestFrame(wx.Frame): dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True) if dlst != []: #returns [] if none curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst - #make up dictionary to pass to my user function (see DrawPointLabel) + #make up dictionary to pass to my user function (see DrawPointLabel) mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\ "pointXY":pointXY, "scaledXY":scaledXY} #pass dict to update the pointLabel @@ -1712,13 +1712,13 @@ class TestFrame(wx.Frame): def OnFilePageSetup(self, event): self.client.PageSetup() - + def OnFilePrintPreview(self, event): self.client.PrintPreview() - + def OnFilePrint(self, event): self.client.Printout() - + def OnSaveFile(self, event): self.client.SaveFile() @@ -1728,11 +1728,11 @@ class TestFrame(wx.Frame): def OnPlotDraw1(self, event): self.resetDefaults() self.client.Draw(_draw1Objects()) - + def OnPlotDraw2(self, event): self.resetDefaults() self.client.Draw(_draw2Objects()) - + def OnPlotDraw3(self, event): self.resetDefaults() self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL)) @@ -1747,7 +1747,7 @@ class TestFrame(wx.Frame): drawObj= _draw4Objects() self.client.Draw(drawObj) ## # profile -## start = _time.clock() +## start = _time.clock() ## for x in range(10): ## self.client.Draw(drawObj) ## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start) @@ -1775,7 +1775,7 @@ class TestFrame(wx.Frame): def OnPlotClear(self,event): self.client.Clear() - + def OnPlotScale(self, event): if self.client.last_draw != None: graphics, xAxis, yAxis= self.client.last_draw @@ -1783,10 +1783,10 @@ class TestFrame(wx.Frame): def OnEnableZoom(self, event): self.client.SetEnableZoom(event.IsChecked()) - + def OnEnableGrid(self, event): self.client.SetEnableGrid(event.IsChecked()) - + def OnEnableLegend(self, event): self.client.SetEnableLegend(event.IsChecked()) @@ -1795,7 +1795,7 @@ class TestFrame(wx.Frame): def OnScrUp(self, event): self.client.ScrollUp(1) - + def OnScrRt(self,event): self.client.ScrollRight(2) @@ -1814,7 +1814,7 @@ class TestFrame(wx.Frame): self.client.SetFontSizeLegend(7) self.client.SetXSpec('auto') self.client.SetYSpec('auto') - + def __test(): diff --git a/gr-wxgui/src/python/plotter/common.py b/gr-wxgui/src/python/plotter/common.py index 4c50cd787..6775b7057 100644 --- a/gr-wxgui/src/python/plotter/common.py +++ b/gr-wxgui/src/python/plotter/common.py @@ -115,7 +115,7 @@ class point_label_thread(threading.Thread, mutex): def run(self): last_ts = time.time() last_coor = coor = None - try: + try: while True: time.sleep(1.0/30.0) self.lock() diff --git a/gr-wxgui/src/python/plotter/gltext.py b/gr-wxgui/src/python/plotter/gltext.py index 1b3c047dc..0b6e3f55b 100644 --- a/gr-wxgui/src/python/plotter/gltext.py +++ b/gr-wxgui/src/python/plotter/gltext.py @@ -1,503 +1,503 @@ -#!/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.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 +#!/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.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/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py index a9bd02731..5eaa76bc0 100644 --- a/gr-wxgui/src/python/plotter/grid_plotter_base.py +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -78,7 +78,7 @@ class grid_plotter_base(plotter_base): def set_point_label_coordinate(self, coor): """ Set the point label coordinate. - @param coor the coordinate x, y tuple or None + @param coor the coordinate x, y tuple or None """ self.lock() self._point_label_coordinate = coor diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index b856215e9..5af580339 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -101,7 +101,7 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) def set_use_persistence(self,enable): - self.use_persistence=enable + self.use_persistence=enable self.clear_accum=True def set_persist_alpha(self,analog_alpha): diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 0af64b826..f2456241c 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -204,7 +204,7 @@ class waterfall_plotter(grid_plotter_base): Create the texture to fit the fft_size X num_lines. @param flag the set/unset or update flag """ - if flag is not None: + if flag is not None: self._resize_texture_flag = flag return if not self._resize_texture_flag: return diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py index 7f54dff33..7c324c5d9 100644 --- a/gr-wxgui/src/python/powermate.py +++ b/gr-wxgui/src/python/powermate.py @@ -1,24 +1,24 @@ #!/usr/bin/env python # # Copyright 2005 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. -# +# """ Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs @@ -184,7 +184,7 @@ class powermate(threading.Thread): self.setDaemon (1) self.keep_running = True self.start () - + def __del__(self): self.keep_running = False if self.handle >= 0: @@ -227,7 +227,7 @@ class powermate(threading.Thread): except exceptions.OSError: return False - + def set_event_receiver(self, obj): self.event_receiver = obj @@ -239,7 +239,7 @@ class powermate(threading.Thread): """ if self.id != ID_POWERMATE: return False - + static_brightness &= 0xff; if pulse_speed < 0: pulse_speed = 0 @@ -269,14 +269,14 @@ class powermate(threading.Thread): raw_input_event = struct.unpack(input_event_struct,s) sec, usec, type, code, val = self.mapper(raw_input_event) - + if self.event_receiver is None: continue - + if type == IET_SYN: # ignore pass elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness) - pass + pass elif type == IET_REL and code == IEC_REL_DIAL: #print "Dial: %d" % (val,) wx.PostEvent(self.event_receiver, PMRotateEvent(val)) @@ -346,13 +346,13 @@ class _contour_remapper(object): if type == IET_KEY: # remap keys so that all 3 gadgets have buttons 0 to 4 in common - return (sec, usec, type, + return (sec, usec, type, (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8, IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4, IEC_BTN_9, IEC_BTN_10, IEC_BTN_11, IEC_BTN_12, IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val) - + return event # ------------------------------------------------------------------------ @@ -374,9 +374,9 @@ class PMButtonEvent(wx.PyEvent): self.button = button self.value = value - def Clone (self): + def Clone (self): self.__class__(self.GetId()) - + class PMRotateEvent(wx.PyEvent): def __init__(self, delta): @@ -384,7 +384,7 @@ class PMRotateEvent(wx.PyEvent): self.SetEventType (grEVT_POWERMATE_ROTATE) self.delta = delta - def Clone (self): + def Clone (self): self.__class__(self.GetId()) @@ -394,9 +394,9 @@ class PMShuttleEvent(wx.PyEvent): self.SetEventType (grEVT_POWERMATE_SHUTTLE) self.position = position - def Clone (self): + def Clone (self): self.__class__(self.GetId()) - + # ------------------------------------------------------------------------ # Example usage # ------------------------------------------------------------------------ @@ -411,14 +411,14 @@ if __name__ == '__main__': EVT_POWERMATE_SHUTTLE(self, self.on_shuttle) self.brightness = 128 self.pulse_speed = 0 - + try: self.pm = powermate(self) except: sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") sys.exit(1) - self.pm.set_led_state(self.brightness, self.pulse_speed) + self.pm.set_led_state(self.brightness, self.pulse_speed) def on_button(self, evt): @@ -431,11 +431,11 @@ if __name__ == '__main__': new = max(0, min(255, self.brightness + evt.delta)) if new != self.brightness: self.brightness = new - self.pm.set_led_state(self.brightness, self.pulse_speed) - + self.pm.set_led_state(self.brightness, self.pulse_speed) + def on_shuttle(self, evt): print "Shuttle %d" % (evt.position,) - + class App(wx.App): def OnInit(self): title='PowerMate Demo' diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 89a808cec..dc90a6045 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -120,7 +120,7 @@ class control_panel(wx.Panel): parent.subscribe(USE_PERSISTENCE_KEY, widget.ShowItems) #allways show initially, so room is reserved for them widget.ShowItems(True) # (parent[USE_PERSISTENCE_KEY]) - + parent.subscribe(USE_PERSISTENCE_KEY, self._update_layout) ################################################## @@ -406,7 +406,7 @@ class control_panel(wx.Panel): # Just ignore the key value we get # we only need to now that the visability or size of something has changed self.parent.Layout() - #self.parent.Fit() + #self.parent.Fit() ################################################## # Scope window with plotter and control panel @@ -477,12 +477,12 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_LEVEL_KEY] = 0 self[TRIGGER_CHANNEL_KEY] = 0 self[TRIGGER_MODE_KEY] = trig_mode - + self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 self[USE_PERSISTENCE_KEY] = use_persistence self[PERSIST_ALPHA_KEY] = persist_alpha - + if self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_STRIPCHART: self[T_FRAC_OFF_KEY] = 0.0 diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 5ae897400..e6ff532e7 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -84,7 +84,7 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): **kwargs #do not end with a comma ): #ensure analog alpha - if persist_alpha is None: + if persist_alpha is None: actual_frame_rate=float(frame_rate) analog_cutoff_freq=0.5 # Hertz #calculate alpha from wanted cutoff freq @@ -157,11 +157,11 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): ) else: for i in range(num_inputs): - c2f = gr.complex_to_float() + c2f = gr.complex_to_float() self.wxgui_connect((self, i), c2f) for j in range(2): self.connect( - (c2f, j), + (c2f, j), ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), SAMPLE_RATE_KEY), (scope, 2*i+j), ) @@ -187,7 +187,7 @@ class test_top_block (stdgui2.std_top_block): default_input_rate = 1e6 if len(argv) > 1: - input_rate = int(argv[1]) + input_rate = int(argv[1]) else: input_rate = default_input_rate @@ -202,12 +202,12 @@ class test_top_block (stdgui2.std_top_block): t_scale = .00003*default_input_rate/input_rate # old behavior print "input rate %s v_scale %s t_scale %s" % (input_rate,v_scale,t_scale) - + # Generate a complex sinusoid ampl=1.0e3 self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3*input_rate/default_input_rate, ampl) - self.noise =gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 11.1*25.1e3*input_rate/default_input_rate, ampl/10) + self.noise =gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 11.1*25.1e3*input_rate/default_input_rate, ampl/10) #self.noise =gr.noise_source_c(gr.GR_GAUSSIAN, ampl/10) self.combine=gr.add_cc() @@ -223,7 +223,7 @@ class test_top_block (stdgui2.std_top_block): # self.connect("src0 throttle scope") self.connect(self.src0,(self.combine,0)) self.connect(self.noise,(self.combine,1)) - self.connect(self.combine, self.thr, scope) + self.connect(self.combine, self.thr, scope) def main (): app = stdgui2.stdapp (test_top_block, "O'Scope Test App") diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index bf2c50917..d45e79906 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -1,24 +1,24 @@ #!/usr/bin/env python # # Copyright 2003,2004,2006,2007 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 gnuradio import gr, gru, eng_notation from gnuradio.wxgui import stdgui2 @@ -42,7 +42,7 @@ class scope_sink_f(gr.hier_block2): msgq = gr.msg_queue(2) # message queue that holds at most 2 messages self.guts = gr.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): + for i in range(num_inputs): self.connect((self, i), (self.guts, i)) self.win = scope_window(win_info (msgq, sample_rate, frame_decim, @@ -63,16 +63,16 @@ class scope_sink_c(gr.hier_block2): msgq = gr.msg_queue(2) # message queue that holds at most 2 messages self.guts = gr.oscope_sink_f(sample_rate, msgq) - for i in range(num_inputs): - c2f = gr.complex_to_float() + for i in range(num_inputs): + c2f = gr.complex_to_float() self.connect((self, i), c2f) self.connect((c2f, 0), (self.guts, 2*i+0)) self.connect((c2f, 1), (self.guts, 2*i+1)) - + self.win = scope_window(win_info(msgq, sample_rate, frame_decim, v_scale, t_scale, self.guts, title), parent) self.win.info.xy = xy_mode - + def set_sample_rate(self, sample_rate): self.guts.set_sample_rate(sample_rate) self.win.info.set_sample_rate(sample_rate) @@ -132,7 +132,7 @@ v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER 1.0e+4 # 10000 /div, USRP full scale is -/+ 32767 ] - + wxDATA_EVENT = wx.NewEventType() def EVT_DATA_EVENT(win, func): @@ -144,12 +144,12 @@ class DataEvent(wx.PyEvent): self.SetEventType (wxDATA_EVENT) self.data = data - def Clone (self): + def Clone (self): self.__class__ (self.GetId()) class win_info (object): - __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', + __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', 'scopesink', 'title', 'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy', 'autorange', 'running'] @@ -178,7 +178,7 @@ class win_info (object): def set_sample_rate(self, sample_rate): self.sample_rate = sample_rate - + def get_sample_rate (self): return self.sample_rate @@ -202,31 +202,31 @@ class input_watcher (gru.msgq_runner): def handle_msg(self, msg): if self.iscan == 0: # only display at frame_decim self.iscan = self.frame_decim - + 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 - + bytes_per_chan = nsamples * gr.sizeof_float - + records = [] for ch in range (nchan): - + start = ch * bytes_per_chan chan_data = s[start:start+bytes_per_chan] rec = numpy.fromstring (chan_data, numpy.float32) records.append (rec) - + # print "nrecords = %d, reclen = %d" % (len (records),nsamples) - + de = DataEvent (records) wx.PostEvent (self.event_receiver, de) records = [] del de self.iscan -= 1 - + class scope_window (wx.Panel): @@ -248,7 +248,7 @@ class scope_window (wx.Panel): self.SetAutoLayout (True) self.sizer.Fit (self) self.set_autorange(self.info.autorange) - + # second row of control buttons etc. appears BELOW control_box def make_control2_box (self): @@ -339,14 +339,14 @@ class scope_window (wx.Panel): ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER) return ctrlbox - + _marker_choices = ['line', 'plus', 'dot'] def update_timebase_label (self): time_per_div = self.info.get_time_per_div () s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div' self.time_base_label.SetLabel (s) - + def decr_timebase (self, evt): self.info.time_scale_cursor.prev () self.update_timebase_label () @@ -359,7 +359,7 @@ class scope_window (wx.Panel): volts_per_div = self.info.get_volts_per_div () s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div self.v_scale_label.SetLabel (s) - + def decr_v_scale (self, evt): self.info.v_scale_cursor.prev () self.update_v_scale_label () @@ -367,7 +367,7 @@ class scope_window (wx.Panel): def incr_v_scale (self, evt): self.info.v_scale_cursor.next () self.update_v_scale_label () - + def marker_choice_event (self, evt): s = evt.GetString () self.set_marker (s) @@ -388,13 +388,13 @@ class scope_window (wx.Panel): self.autorange_checkbox.SetValue(False) self.inc_v_button.Enable(True) self.dec_v_button.Enable(True) - + def autorange_checkbox_event(self, evt): if evt.Checked(): self.set_autorange(True) else: self.set_autorange(False) - + def set_marker (self, s): self.info.set_marker (s) # set info for drawing routines i = self.marker_choice.FindString (s) @@ -409,7 +409,7 @@ class scope_window (wx.Panel): def set_format_plus (self): self.set_marker ('plus') - + def xy_choice_event (self, evt): s = evt.GetString () self.info.xy = s == 'X:Y' @@ -430,19 +430,19 @@ class scope_window (wx.Panel): sink.set_trigger_mode (gr.gr_TRIG_MODE_FREE) else: assert 0, "Bad trig_mode_choice string" - + def set_trig_level50 (self, evt): self.info.scopesink.set_trigger_level_auto () def run_stop (self, evt): self.info.running = not self.info.running - + class graph_window (plot.PlotCanvas): channel_colors = ['BLUE', 'RED', 'CYAN', 'MAGENTA', 'GREEN', 'YELLOW'] - + def __init__ (self, info, parent, id = -1, pos = wx.DefaultPosition, size = (640, 240), style = wx.DEFAULT_FRAME_STYLE, name = ""): @@ -453,7 +453,7 @@ class graph_window (plot.PlotCanvas): self.SetEnableZoom (True) self.SetEnableLegend(True) # self.SetBackgroundColour ('black') - + self.info = info; self.y_range = None self.x_range = None @@ -468,11 +468,11 @@ class graph_window (plot.PlotCanvas): def channel_color (self, ch): return self.channel_colors[ch % len(self.channel_colors)] - + def format_data (self, evt): if not self.info.running: return - + if self.info.xy: self.format_xy_data (evt) return @@ -545,7 +545,7 @@ class graph_window (plot.PlotCanvas): points = numpy.zeros ((len(records[0]), 2), numpy.float32) points[:,0] = records[0] points[:,1] = records[1] - + self.SetXUseScopeTicks (False) m = info.get_marker () @@ -607,7 +607,7 @@ class test_top_block (stdgui2.std_top_block): stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) if len(argv) > 1: - frame_decim = int(argv[1]) + frame_decim = int(argv[1]) else: frame_decim = 1 @@ -622,7 +622,7 @@ class test_top_block (stdgui2.std_top_block): t_scale = None # old behavior print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) - + input_rate = 1e6 # Generate a complex sinusoid @@ -639,7 +639,7 @@ class test_top_block (stdgui2.std_top_block): # Ultimately this will be # self.connect("src0 throttle scope") - self.connect(self.src0, self.thr, scope) + self.connect(self.src0, self.thr, scope) def main (): app = stdgui2.stdapp (test_top_block, "O'Scope Test App") diff --git a/gr-wxgui/src/python/stdgui2.py b/gr-wxgui/src/python/stdgui2.py index f397fd01e..71436d72c 100644 --- a/gr-wxgui/src/python/stdgui2.py +++ b/gr-wxgui/src/python/stdgui2.py @@ -1,23 +1,23 @@ # # Copyright 2004 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. -# +# '''A simple wx gui for GNU Radio applications''' @@ -74,7 +74,7 @@ class stdframe (wx.Frame): def top_block (self): return self.panel.top_block - + class stdpanel (wx.Panel): def __init__ (self, parent, frame, top_block_maker, max_nouts=None): @@ -91,7 +91,7 @@ class stdpanel (wx.Panel): if(max_nouts is not None): self.top_block.start (max_nouts) else: - self.top_block.start () + self.top_block.start () class std_top_block (gr.top_block): def __init__ (self, parent, panel, vbox, argv): diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index 6536ada10..c78244f04 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -159,7 +159,7 @@ class control_panel(wx.Panel): self.parent[FRAME_RATE_KEY] *= 0.75 if self.parent[FRAME_RATE_KEY] < 1.0: self.parent[FRAME_RATE_KEY] = 1.0 - + if self.parent[FRAME_RATE_KEY] == old_rate: self.parent[DECIMATION_KEY] += 1 def _on_decr_time_scale(self, event): @@ -268,7 +268,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub): #plot the fft self.plotter.set_samples( samples=samples, - minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY], + minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY], maximum=self[REF_LEVEL_KEY], ) #update the plotter diff --git a/gr-wxgui/src/python/waterfallsink_nongl.py b/gr-wxgui/src/python/waterfallsink_nongl.py index 3c25a9d9f..213415c82 100644 --- a/gr-wxgui/src/python/waterfallsink_nongl.py +++ b/gr-wxgui/src/python/waterfallsink_nongl.py @@ -1,24 +1,24 @@ #!/usr/bin/env python # # Copyright 2003,2004,2005,2007,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 gnuradio import gr, gru, window from gnuradio.wxgui import stdgui2 @@ -26,7 +26,7 @@ import wx import gnuradio.wxgui.plot as plot import numpy import os -import math +import math default_fftsink_size = (640,240) default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15) @@ -70,7 +70,7 @@ class waterfall_sink_base(object): def _set_n(self): self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): def __init__(self, parent, baseband_freq=0, y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, @@ -85,11 +85,11 @@ class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title) - + self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + mywindow = window.blackmanharris(self.fft_size) self.fft = gr.fft_vfc(self.fft_size, True, mywindow) self.c2mag = gr.complex_to_mag(self.fft_size) @@ -105,7 +105,7 @@ class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): def __init__(self, parent, baseband_freq=0, y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, - fft_rate=default_fft_rate, average=False, avg_alpha=None, + fft_rate=default_fft_rate, average=False, avg_alpha=None, title='', size=default_fftsink_size, **kwargs): gr.hier_block2.__init__(self, "waterfall_sink_f", @@ -120,7 +120,7 @@ class waterfall_sink_c(gr.hier_block2, waterfall_sink_base): self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) - + mywindow = window.blackmanharris(self.fft_size) self.fft = gr.fft_vcc(self.fft_size, True, mywindow) self.c2mag = gr.complex_to_mag(self.fft_size) @@ -145,9 +145,9 @@ class DataEvent(wx.PyEvent): self.SetEventType (myDATA_EVENT) self.data = data - def Clone (self): + def Clone (self): self.__class__ (self.GetId()) - + class input_watcher (gru.msgq_runner): def __init__ (self, msgq, fft_size, event_receiver, **kwds): self.fft_size = fft_size @@ -181,7 +181,7 @@ class waterfall_window (wx.Panel): self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1) self.scale_factor = 5.0 # FIXME should autoscale, or set this - + dc1 = wx.MemoryDC() dc1.SelectObject(self.bm) dc1.Clear() @@ -191,9 +191,9 @@ class waterfall_window (wx.Panel): wx.EVT_PAINT( self, self.OnPaint ) wx.EVT_CLOSE (self, self.on_close_window) EVT_DATA_EVENT (self, self.set_data) - + self.build_popup_menu() - + wx.EVT_CLOSE (self, self.on_close_window) self.Bind(wx.EVT_RIGHT_UP, self.on_right_click) @@ -213,14 +213,14 @@ class waterfall_window (wx.Panel): r.extend(range(0,255,4)) r.extend(self.const_list(255,64)) r.extend(range(255,128,-4)) - + g = [] g.extend(self.const_list(0,32)) g.extend(range(0,255,4)) g.extend(self.const_list(255,64)) g.extend(range(255,0,-4)) g.extend(self.const_list(0,32)) - + b = range(128,255,4) b.extend(self.const_list(255,64)) b.extend(range(255,0,-4)) @@ -234,7 +234,7 @@ class waterfall_window (wx.Panel): colour = wx.Colour(r[i], g[i], b[i]) pens.append( wx.Pen(colour, 2, wx.SOLID)) return pens - + def OnPaint(self, event): dc = wx.PaintDC(self) self.DoDrawing(dc) @@ -243,7 +243,7 @@ class waterfall_window (wx.Panel): if dc is None: dc = wx.ClientDC(self) dc.DrawBitmap(self.bm, 0, 0, False ) - + def const_list(self,const,len): a = [const] @@ -285,18 +285,18 @@ class waterfall_window (wx.Panel): value = int(dB[x_pos] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) else: # complex fft for x_pos in range(0, d_max): # positive freqs value = int(dB[x_pos] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) + dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) for x_pos in range(0 , d_max): # negative freqs value = int(dB[x_pos+d_max] * scale_factor) value = min(255, max(0, value)) dc1.SetPen(self.pens[value]) - dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) + dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) del dc1 self.DoDrawing (None) -- cgit From ed838eb9be22d769247a9c63f23423c8c2f81f72 Mon Sep 17 00:00:00 2001 From: Marcus Leech Date: Mon, 21 May 2012 14:35:33 -0400 Subject: wxgui: allows LEFT click to set a flow-graph variable to the adjusted X value on either the FFT or waterfall display. --- gr-wxgui/src/python/fft_window.py | 2 ++ gr-wxgui/src/python/fftsink_gl.py | 3 +++ gr-wxgui/src/python/plotter/channel_plotter.py | 8 ++++++++ gr-wxgui/src/python/plotter/common.py | 1 + gr-wxgui/src/python/plotter/grid_plotter_base.py | 14 +++++++++++++- gr-wxgui/src/python/plotter/plotter_base.py | 5 ++++- gr-wxgui/src/python/plotter/waterfall_plotter.py | 8 ++++++++ gr-wxgui/src/python/waterfall_window.py | 3 +++ gr-wxgui/src/python/waterfallsink_gl.py | 3 +++ 9 files changed, 45 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 99c1cf6e1..fac83a4a3 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -300,6 +300,8 @@ class fft_window(wx.Panel, pubsub.pubsub): #initial update self.update_grid() + def set_callback(self,callb): + self.plotter.set_callback(callb) def autoscale(self, *args): """ diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 6b268f6cb..dc31e84a1 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -127,6 +127,9 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS #connect self.wxgui_connect(self, fft, sink) + + def set_callback(self,callb): + self.win.set_callback(callb) class fft_sink_f(_fft_sink_base): _fft_chain = blks2.logpwrfft_f diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index a3a2b6451..4bcc36fd4 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -56,6 +56,7 @@ class channel_plotter(grid_plotter_base): self._channels = dict() #init channel plotter self.register_init(self._init_channel_plotter) + self.callback = None def _init_channel_plotter(self): """ @@ -150,6 +151,13 @@ class channel_plotter(grid_plotter_base): 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. diff --git a/gr-wxgui/src/python/plotter/common.py b/gr-wxgui/src/python/plotter/common.py index 6775b7057..88215e039 100644 --- a/gr-wxgui/src/python/plotter/common.py +++ b/gr-wxgui/src/python/plotter/common.py @@ -103,6 +103,7 @@ class point_label_thread(threading.Thread, mutex): 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() diff --git a/gr-wxgui/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py index 5eaa76bc0..f1bc8f546 100644 --- a/gr-wxgui/src/python/plotter/grid_plotter_base.py +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -85,7 +85,19 @@ class grid_plotter_base(plotter_base): self._point_label_cache.changed(True) self.update() self.unlock() - + + def call_freq_callback(self, coor): + 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 + self._call_callback(x_val, y_val) + def enable_grid_aspect_ratio(self, enable=None): """ Enable/disable the grid aspect ratio. diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 5af580339..2fdd0f20a 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -189,7 +189,10 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): if self.use_persistence: if self.clear_accum: #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) - GL.glAccum(GL.GL_LOAD, 1.0) + try: + GL.glAccum(GL.GL_LOAD, 1.0) + except: + pass self.clear_accum=False GL.glAccum(GL.GL_MULT, 1.0-self.persist_alpha) diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index f2456241c..6a6bf6330 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -110,6 +110,7 @@ class waterfall_plotter(grid_plotter_base): self._counter = 0 self.set_num_lines(0) self.set_color_mode(COLORS.keys()[0]) + self.callback = None def _init_waterfall(self): """ @@ -172,6 +173,13 @@ class waterfall_plotter(grid_plotter_base): """ 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. diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index c78244f04..cd60104d7 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -238,6 +238,9 @@ class waterfall_window(wx.Panel, pubsub.pubsub): #initial update self.update_grid() + def set_callback(self,callb): + self.plotter.set_callback(callb) + def autoscale(self, *args): """ Autoscale the waterfall plot to the last frame. diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index c2c4e8df7..b69c5dda0 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -113,6 +113,9 @@ class _waterfall_sink_base(gr.hier_block2, common.wxgui_hb): #connect self.wxgui_connect(self, fft, sink) + def set_callback(self,callb): + self.win.set_callback(callb) + class waterfall_sink_f(_waterfall_sink_base): _fft_chain = blks2.logpwrfft_f _item_size = gr.sizeof_float -- cgit From db627dafee41444f42c2c44b0aa971f41883a44f Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Tue, 15 Jan 2013 05:16:39 -0800 Subject: wxgui: dead code removal and formatting cleanup --- gr-wxgui/src/python/plotter/plotter_base.py | 67 +++++++++++++---------------- 1 file changed, 31 insertions(+), 36 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 2fdd0f20a..41c94e5e0 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -88,9 +88,9 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList); - self.use_persistence=False - self.persist_alpha=2.0/15 - self.clear_accum=True + 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() @@ -100,12 +100,12 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): 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_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 set_persist_alpha(self,analog_alpha): + self.persist_alpha=analog_alpha def new_gl_cache(self, draw_fcn, draw_pri=50): """ @@ -141,7 +141,7 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ self.lock() self._resized_flag = True - self.clear_accum=True + self.clear_accum=True self.unlock() def _on_paint(self, event): @@ -153,12 +153,14 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ self.lock() self.SetCurrent() - #check if gl was initialized + + # 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 + + # check for a change in window size if self._resized_flag: self.width, self.height = self.GetSize() GL.glMatrixMode(GL.GL_PROJECTION) @@ -169,35 +171,28 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): GL.glViewport(0, 0, self.width, self.height) for cache in self._gl_caches: cache.changed(True) self._resized_flag = False - #clear, draw functions, swap - GL.glClear(GL.GL_COLOR_BUFFER_BIT) - - if False: - GL.glEnable (GL.GL_LINE_SMOOTH) - GL.glEnable (GL.GL_POLYGON_SMOOTH) - GL.glEnable (GL.GL_BLEND) - GL.glBlendFunc (GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) - GL.glHint (GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST) #GL.GL_DONT_CARE) - GL.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST) - #GL.glLineWidth (1.5) - - GL.glEnable(GL.GL_MULTISAMPLE) #Enable Multisampling anti-aliasing + # clear buffer + GL.glClear(GL.GL_COLOR_BUFFER_BIT) + # draw functions for fcn in self._draw_fcns: fcn[1]() - if self.use_persistence: - if self.clear_accum: - #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) - try: - GL.glAccum(GL.GL_LOAD, 1.0) - except: - pass - self.clear_accum=False - - GL.glAccum(GL.GL_MULT, 1.0-self.persist_alpha) - GL.glAccum(GL.GL_ACCUM, self.persist_alpha) - GL.glAccum(GL.GL_RETURN, 1.0) + # apply persistence + if self.use_persistence: + if self.clear_accum: + #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) + try: + GL.glAccum(GL.GL_LOAD, 1.0) + except: + pass + self.clear_accum=False + + GL.glAccum(GL.GL_MULT, 1.0-self.persist_alpha) + GL.glAccum(GL.GL_ACCUM, self.persist_alpha) + GL.glAccum(GL.GL_RETURN, 1.0) + + # show result self.SwapBuffers() self.unlock() -- cgit From c33c33c5d8a0286ce058a0f1a0400d9072eeb27e Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Tue, 15 Jan 2013 05:51:53 -0800 Subject: wxgui: implement persistence without using glAccum --- gr-wxgui/src/python/plotter/plotter_base.py | 35 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 41c94e5e0..25811f09b 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -172,26 +172,29 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): for cache in self._gl_caches: cache.changed(True) self._resized_flag = False - # clear buffer - GL.glClear(GL.GL_COLOR_BUFFER_BIT) + # 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]() - # apply persistence - if self.use_persistence: - if self.clear_accum: - #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) - try: - GL.glAccum(GL.GL_LOAD, 1.0) - except: - pass - self.clear_accum=False - - GL.glAccum(GL.GL_MULT, 1.0-self.persist_alpha) - GL.glAccum(GL.GL_ACCUM, self.persist_alpha) - GL.glAccum(GL.GL_RETURN, 1.0) - # show result self.SwapBuffers() self.unlock() -- cgit From 6b9b1f762eacd6ad53727cf116d12a76d6e8b6b8 Mon Sep 17 00:00:00 2001 From: Balint Seeber Date: Fri, 18 Jan 2013 16:28:10 -0800 Subject: Added PaintDC to 'plotter_base' so WX GL sinks will work under Windows. Changed 'notebook' SetSelection (deprecated) to ChangeSelection. --- gr-wxgui/src/python/forms/forms.py | 3 ++- gr-wxgui/src/python/plotter/plotter_base.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/forms/forms.py b/gr-wxgui/src/python/forms/forms.py index f1d0038ab..cabc5860b 100644 --- a/gr-wxgui/src/python/forms/forms.py +++ b/gr-wxgui/src/python/forms/forms.py @@ -500,7 +500,8 @@ class notebook(_chooser_base): self._add_widget(self._notebook) def _handle(self, event): self[INT_KEY] = self._notebook.GetSelection() - def _update(self, i): self._notebook.SetSelection(i) + # SetSelection triggers a page change event (deprecated, breaks on Windows) and ChangeSelection does not + def _update(self, i): self._notebook.ChangeSelection(i) # ---------------------------------------------------------------- # Stand-alone test application diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 41c94e5e0..f1cbaf3c7 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -151,6 +151,8 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): Resize the view port if the width or height changed. Redraw the screen, calling the draw functions. """ + # create device context (needed on Windows, noop on X) + dc = wx.PaintDC(self) self.lock() self.SetCurrent() -- cgit From 02484c4d8880b317e92077748e48989e0c0c0b34 Mon Sep 17 00:00:00 2001 From: Tom Rondeau Date: Wed, 20 Mar 2013 09:55:25 -0400 Subject: wxgui: fix scaling issue of nongl FFT plot (issue #523). wxgui: add some noise to the fftsink_nongl test case. --- gr-wxgui/src/python/fftsink_nongl.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index c756d8a3b..c38abbb22 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -132,7 +132,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins + -20*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale @@ -177,7 +177,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): # FIXME We need to add 3dB to all bins but the DC bin self.log = gr.nlog10_ff(20, self.fft_size, - -10*math.log10(self.fft_size) # Adjust for number of bins + -20*math.log10(self.fft_size) # Adjust for number of bins -10*math.log10(power/self.fft_size) # Adjust for windowing loss -20*math.log10(ref_scale/2)) # Adjust for reference scale @@ -606,6 +606,8 @@ class test_app_block (stdgui2.std_top_block): # Generate a complex sinusoid #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) + noise1 = analog.noise_source_c(analog.GR_UNIFORM, 1.0/10) + add1 = blocks.add_cc() # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. @@ -616,17 +618,24 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect(src1, thr1, sink1) + self.connect(src1, (add1,0)) + self.connect(noise1, (add1,1)) + self.connect(add1, thr1, sink1) #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) + noise2 = analog.noise_source_f(analog.GR_UNIFORM, 1.0/10) + add2 = blocks.add_ff() + thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect(src2, thr2, sink2) + self.connect(src2, (add2,0)) + self.connect(noise2, (add2,1)) + self.connect(add2, thr2, sink2) def main (): app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") -- cgit From 5f0aaf3d5397675d6f87acd7ab20526ac1fb0d4e Mon Sep 17 00:00:00 2001 From: Balint Seeber Date: Thu, 11 Apr 2013 19:49:46 -0700 Subject: wxgui: Fixes to solve issues using GL sinks on OS X plotter/plotter_base.py: Create explicit OpenGL context, check if window is shown before realising context & only create PaintDC is paint triggered by WM message (OS paint event) Added SetSizeHints to WX sink windows so sizing occurs correctly --- gr-wxgui/src/python/const_window.py | 1 + gr-wxgui/src/python/fft_window.py | 1 + gr-wxgui/src/python/histo_window.py | 1 + gr-wxgui/src/python/plotter/plotter_base.py | 11 ++++++++--- gr-wxgui/src/python/scope_window.py | 1 + gr-wxgui/src/python/waterfall_window.py | 1 + 6 files changed, 13 insertions(+), 3 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index 2ad89b2a3..a7ff2e5f3 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -150,6 +150,7 @@ class const_window(wx.Panel, pubsub.pubsub): wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) + self.plotter.SetSizeHints(*size) self.plotter.set_title(title) self.plotter.set_x_label('Inphase') self.plotter.set_y_label('Quadrature') diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index fac83a4a3..cf21b893a 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -274,6 +274,7 @@ class fft_window(wx.Panel, pubsub.pubsub): wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) + self.plotter.SetSizeHints(*size) self.plotter.set_title(title) self.plotter.enable_legend(True) self.plotter.enable_point_label(True) diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py index a1b520f9c..e87e97825 100644 --- a/gr-wxgui/src/python/histo_window.py +++ b/gr-wxgui/src/python/histo_window.py @@ -117,6 +117,7 @@ class histo_window(wx.Panel, pubsub.pubsub): wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.bar_plotter(self) self.plotter.SetSize(wx.Size(*size)) + self.plotter.SetSizeHints(*size) self.plotter.set_title(title) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(False) diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index b8a2ce709..6d9463458 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -87,7 +87,8 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): @param parent the parent widgit """ attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) - wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList); + wx.glcanvas.GLCanvas.__init__(self, parent, wx.ID_ANY, 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 @@ -151,10 +152,14 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): 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 = wx.PaintDC(self) + 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.SetCurrent(self._gl_ctx) # Real the explicit GL context # check if gl was initialized if not self._gl_init_flag: diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index dc90a6045..fa79a986e 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -492,6 +492,7 @@ class scope_window(wx.Panel, pubsub.pubsub): wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) + self.plotter.SetSizeHints(*size) self.plotter.set_title(title) self.plotter.enable_legend(True) self.plotter.enable_point_label(True) diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index cd60104d7..a190899c3 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -207,6 +207,7 @@ class waterfall_window(wx.Panel, pubsub.pubsub): wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.waterfall_plotter(self) self.plotter.SetSize(wx.Size(*size)) + self.plotter.SetSizeHints(*size) self.plotter.set_title(title) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(False) -- cgit From 97dc187c612d8aa126215db708f265566fce0fda Mon Sep 17 00:00:00 2001 From: Marcus Leech Date: Tue, 19 Mar 2013 14:29:17 -0400 Subject: wxgui: make dummy set_callback stub in nongl sinks. Doesn't work, but also doesn't break the code. --- gr-wxgui/src/python/fftsink_nongl.py | 4 +++- gr-wxgui/src/python/waterfallsink_nongl.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'gr-wxgui/src/python') diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index c38abbb22..473cc424c 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2006,2007,2009,2010 Free Software Foundation, Inc. +# Copyright 2003-2007,2009,2010 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -99,6 +99,8 @@ class fft_sink_base(object): def _set_n(self): self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + def set_callback(self, callb): + return class fft_sink_f(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, ref_scale=2.0, diff --git a/gr-wxgui/src/python/waterfallsink_nongl.py b/gr-wxgui/src/python/waterfallsink_nongl.py index 213415c82..bf77b4b13 100644 --- a/gr-wxgui/src/python/waterfallsink_nongl.py +++ b/gr-wxgui/src/python/waterfallsink_nongl.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2003,2004,2005,2007,2008 Free Software Foundation, Inc. +# Copyright 2003-2005,2007,2008 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -71,6 +71,9 @@ class waterfall_sink_base(object): def _set_n(self): self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate))) + def set_callback(self, callb): + return + class waterfall_sink_f(gr.hier_block2, waterfall_sink_base): def __init__(self, parent, baseband_freq=0, y_per_div=10, ref_level=50, sample_rate=1, fft_size=512, -- cgit