diff options
author | jcorgan | 2008-08-14 18:43:15 +0000 |
---|---|---|
committer | jcorgan | 2008-08-14 18:43:15 +0000 |
commit | 36649d4e472172fe840444ac0268c7b6b4da94b4 (patch) | |
tree | a3f8b35980e18f7faee2e82e079746f71b0ee02c /gr-wxgui/src/python/common.py | |
parent | 5b09804605cd41bbc3fdcb917eda3f69a7598af9 (diff) | |
download | gnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.tar.gz gnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.tar.bz2 gnuradio-36649d4e472172fe840444ac0268c7b6b4da94b4.zip |
Merged changeset r9241:9289 from jblum/glwxgui into trunk. Adds OpenGL versions of fftsink, waterfallsink, and scopesink, and new constsink. See README.gl for use. (Josh Blum)
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@9290 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gr-wxgui/src/python/common.py')
-rw-r--r-- | gr-wxgui/src/python/common.py | 302 |
1 files changed, 302 insertions, 0 deletions
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) |