gr-wxgui: adds stripchart trigger mode to graphics sinks
[debian/gnuradio] / gr-wxgui / src / python / waterfall_window.py
index 51bd01881b23072095a9b681b1e86d8c4ea5e498..6536ada10100287b3348c8e8b6bea7e0227a9c14 100644 (file)
@@ -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.
 #
 
 ##################################################
@@ -29,16 +29,20 @@ import numpy
 import math
 import pubsub
 from constants import *
+from gnuradio import gr #for gr.prefs
+import forms
 
 ##################################################
 # 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_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
+DYNAMIC_RANGE_STEP = 10.
 COLOR_MODES = (
        ('RGB1', 'rgb1'),
        ('RGB2', 'rgb2'),
@@ -60,57 +64,80 @@ 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)
+               parent[SHOW_CONTROL_PANEL_KEY] = True
+               parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show)
                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)
+               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()
-               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,
+               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.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)
+               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)
-               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)
+               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()
-               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)
+               forms.single_button(
+                       parent=self, sizer=axes_box, label='Autoscale',
+                       callback=self.parent.autoscale,
+               )
                #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)
+               control_box.AddStretchSpacer()
+               forms.single_button(
+                       parent=self, sizer=control_box, label='Clear',
+                       callback=self._on_clear_button,
+               )
                #run/stop
-               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)
 
@@ -118,34 +145,33 @@ 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(MAX_DYNAMIC_RANGE, common.get_clean_incr(self.parent[DYNAMIC_RANGE_KEY]))
        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(MIN_DYNAMIC_RANGE, common.get_clean_decr(self.parent[DYNAMIC_RANGE_KEY]))
        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]/DYNAMIC_RANGE_STEP
        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]/DYNAMIC_RANGE_STEP
        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] < 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):
-               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,
@@ -167,49 +193,48 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter):
        ):
                pubsub.pubsub.__init__(self)
                #setup
-               self.ext_controller = controller
+               self.samples = list()
                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)
+               #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[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[COLOR_MODE_KEY] = DEFAULT_COLOR_MODE
+               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)
-               #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)
+               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()
 
@@ -219,16 +244,11 @@ 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.
                """
-               #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
+               if not len(self.samples): return
+               min_level, max_level = common.get_min_max_fft(self.samples)
                #set the range and level
-               self.set_ref_level(peak_level)
-               self.set_dynamic_range(peak_level - noise_floor)
+               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):
                """
@@ -243,8 +263,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter):
                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,
@@ -263,8 +283,10 @@ 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]
+               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]
@@ -273,28 +295,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