config properties and rates for gl sinks
[debian/gnuradio] / gr-wxgui / src / python / fft_window.py
1 #
2 # Copyright 2008 Free Software Foundation, Inc.
3 #
4 # This file is part of GNU Radio
5 #
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
9 # any later version.
10 #
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING.  If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
20 #
21
22 ##################################################
23 # Imports
24 ##################################################
25 import plotter
26 import common
27 import wx
28 import numpy
29 import math
30 import pubsub
31 from constants import *
32 from gnuradio import gr #for gr.prefs
33
34 ##################################################
35 # Constants
36 ##################################################
37 SLIDER_STEPS = 100
38 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0
39 DEFAULT_WIN_SIZE = (600, 300)
40 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30)
41 DIV_LEVELS = (1, 2, 5, 10, 20)
42 FFT_PLOT_COLOR_SPEC = (0, 0, 1)
43 PEAK_VALS_COLOR_SPEC = (0, 1, 0)
44 NO_PEAK_VALS = list()
45
46 ##################################################
47 # FFT window control panel
48 ##################################################
49 class control_panel(wx.Panel):
50         """
51         A control panel with wx widgits to control the plotter and fft block chain.
52         """
53
54         def __init__(self, parent):
55                 """
56                 Create a new control panel.
57                 @param parent the wx parent window
58                 """
59                 self.parent = parent
60                 wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
61                 control_box = wx.BoxSizer(wx.VERTICAL)
62                 #checkboxes for average and peak hold
63                 control_box.AddStretchSpacer()
64                 control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER)
65                 self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key)
66                 control_box.Add(self.average_check_box, 0, wx.EXPAND)
67                 self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY)
68                 control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND)
69                 control_box.AddSpacer(2)
70                 self.avg_alpha_slider = common.LogSliderController(
71                         self, 'Avg Alpha',
72                         AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS,
73                         parent.ext_controller, parent.avg_alpha_key,
74                         formatter=lambda x: ': %.4f'%x,
75                 )
76                 parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable)
77                 control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND)
78                 #radio buttons for div size
79                 control_box.AddStretchSpacer()
80                 control_box.Add(common.LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER)
81                 radio_box = wx.BoxSizer(wx.VERTICAL)
82                 self.radio_buttons = list()
83                 for y_per_div in DIV_LEVELS:
84                         radio_button = wx.RadioButton(self, -1, "%d dB/div"%y_per_div)
85                         radio_button.Bind(wx.EVT_RADIOBUTTON, self._on_y_per_div)
86                         self.radio_buttons.append(radio_button)
87                         radio_box.Add(radio_button, 0, wx.ALIGN_LEFT)
88                 parent.subscribe(Y_PER_DIV_KEY, self._on_set_y_per_div)
89                 control_box.Add(radio_box, 0, wx.EXPAND)
90                 #ref lvl buttons
91                 control_box.AddStretchSpacer()
92                 control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER)
93                 control_box.AddSpacer(2)
94                 self._ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level)
95                 control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER)
96                 #autoscale
97                 control_box.AddStretchSpacer()
98                 self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT)
99                 self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale)
100                 control_box.Add(self.autoscale_button, 0, wx.EXPAND)
101                 #run/stop
102                 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
103                 control_box.Add(self.run_button, 0, wx.EXPAND)
104                 #set sizer
105                 self.SetSizerAndFit(control_box)
106
107         ##################################################
108         # Event handlers
109         ##################################################
110         def _on_set_y_per_div(self, y_per_div):
111                 try:
112                         index = list(DIV_LEVELS).index(y_per_div)
113                         self.radio_buttons[index].SetValue(True)
114                 except: pass
115         def _on_y_per_div(self, event):
116                 selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0]
117                 index = self.radio_buttons.index(selected_radio_button)
118                 self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index]
119         def _on_incr_ref_level(self, event):
120                 self.parent.set_ref_level(
121                         self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY])
122         def _on_decr_ref_level(self, event):
123                 self.parent.set_ref_level(
124                         self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY])
125
126 ##################################################
127 # FFT window with plotter and control panel
128 ##################################################
129 class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter):
130         def __init__(
131                 self,
132                 parent,
133                 controller,
134                 size,
135                 title,
136                 real,
137                 fft_size,
138                 baseband_freq,
139                 sample_rate_key,
140                 y_per_div,
141                 y_divs,
142                 ref_level,
143                 average_key,
144                 avg_alpha_key,
145                 peak_hold,
146                 msg_key,
147         ):
148                 pubsub.pubsub.__init__(self)
149                 #ensure y_per_div
150                 if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0]
151                 #setup
152                 self.ext_controller = controller
153                 self.real = real
154                 self.fft_size = fft_size
155                 self.sample_rate_key = sample_rate_key
156                 self.average_key = average_key
157                 self.avg_alpha_key = avg_alpha_key
158                 self._reset_peak_vals()
159                 #init panel and plot
160                 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
161                 self.plotter = plotter.channel_plotter(self)
162                 self.plotter.SetSize(wx.Size(*size))
163                 self.plotter.set_title(title)
164                 self.plotter.enable_point_label(True)
165                 #setup the box with plot and controls
166                 self.control_panel = control_panel(self)
167                 main_box = wx.BoxSizer(wx.HORIZONTAL)
168                 main_box.Add(self.plotter, 1, wx.EXPAND)
169                 main_box.Add(self.control_panel, 0, wx.EXPAND)
170                 self.SetSizerAndFit(main_box)
171                 #initial setup
172                 self.ext_controller[self.average_key] = self.ext_controller[self.average_key]
173                 self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key]
174                 self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold)
175                 self._register_set_prop(self, Y_PER_DIV_KEY, y_per_div)
176                 self._register_set_prop(self, Y_DIVS_KEY, y_divs)
177                 self._register_set_prop(self, X_DIVS_KEY, 8) #approximate
178                 self._register_set_prop(self, REF_LEVEL_KEY, ref_level)
179                 self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq)
180                 self._register_set_prop(self, RUNNING_KEY, True)
181                 #register events
182                 self.subscribe(PEAK_HOLD_KEY, self.plotter.enable_legend)
183                 self.ext_controller.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals())
184                 self.ext_controller.subscribe(msg_key, self.handle_msg)
185                 self.ext_controller.subscribe(self.sample_rate_key, self.update_grid)
186                 for key in (
187                         BASEBAND_FREQ_KEY,
188                         Y_PER_DIV_KEY, X_DIVS_KEY,
189                         Y_DIVS_KEY, REF_LEVEL_KEY,
190                 ): self.subscribe(key, self.update_grid)
191                 #initial update
192                 self.plotter.enable_legend(self[PEAK_HOLD_KEY])
193                 self.update_grid()
194
195         def autoscale(self, *args):
196                 """
197                 Autoscale the fft plot to the last frame.
198                 Set the dynamic range and reference level.
199                 """
200                 #get the peak level (max of the samples)
201                 peak_level = numpy.max(self.samples)
202                 #get the noise floor (averge the smallest samples)
203                 noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4])
204                 #padding
205                 noise_floor -= abs(noise_floor)*.5
206                 peak_level += abs(peak_level)*.1
207                 #set the reference level to a multiple of y divs
208                 self.set_ref_level(self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY]))
209                 #set the range to a clean number of the dynamic range
210                 self.set_y_per_div(common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY]))
211
212         def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS
213
214         def handle_msg(self, msg):
215                 """
216                 Handle the message from the fft sink message queue.
217                 If complex, reorder the fft samples so the negative bins come first.
218                 If real, keep take only the positive bins.
219                 Plot the samples onto the grid as channel 1.
220                 If peak hold is enabled, plot peak vals as channel 2.
221                 @param msg the fft array as a character array
222                 """
223                 if not self[RUNNING_KEY]: return
224                 #convert to floating point numbers
225                 samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame
226                 num_samps = len(samples)
227                 #reorder fft
228                 if self.real: samples = samples[:num_samps/2]
229                 else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2]))
230                 self.samples = samples
231                 #peak hold calculation
232                 if self[PEAK_HOLD_KEY]:
233                         if len(self.peak_vals) != len(samples): self.peak_vals = samples
234                         self.peak_vals = numpy.maximum(samples, self.peak_vals)
235                 else: self._reset_peak_vals()
236                 #plot the fft
237                 self.plotter.set_waveform(
238                         channel='FFT',
239                         samples=samples,
240                         color_spec=FFT_PLOT_COLOR_SPEC,
241                 )
242                 #plot the peak hold
243                 self.plotter.set_waveform(
244                         channel='Peak',
245                         samples=self.peak_vals,
246                         color_spec=PEAK_VALS_COLOR_SPEC,
247                 )
248                 #update the plotter
249                 self.plotter.update()
250
251         def update_grid(self, *args):
252                 """
253                 Update the plotter grid.
254                 This update method is dependent on the variables below.
255                 Determine the x and y axis grid parameters.
256                 The x axis depends on sample rate, baseband freq, and x divs.
257                 The y axis depends on y per div, y divs, and ref level.
258                 """
259                 #grid parameters
260                 sample_rate = self.ext_controller[self.sample_rate_key]
261                 baseband_freq = self[BASEBAND_FREQ_KEY]
262                 y_per_div = self[Y_PER_DIV_KEY]
263                 y_divs = self[Y_DIVS_KEY]
264                 x_divs = self[X_DIVS_KEY]
265                 ref_level = self[REF_LEVEL_KEY]
266                 #determine best fitting x_per_div
267                 if self.real: x_width = sample_rate/2.0
268                 else: x_width = sample_rate/1.0
269                 x_per_div = common.get_clean_num(x_width/x_divs)
270                 coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0))
271                 #update the x grid
272                 if self.real:
273                         self.plotter.set_x_grid(
274                                 baseband_freq,
275                                 baseband_freq + sample_rate/2.0,
276                                 x_per_div,
277                                 10**(-exp),
278                         )
279                 else:
280                         self.plotter.set_x_grid(
281                                 baseband_freq - sample_rate/2.0,
282                                 baseband_freq + sample_rate/2.0,
283                                 x_per_div,
284                                 10**(-exp),
285                         )
286                 #update x units
287                 self.plotter.set_x_label('Frequency', prefix+'Hz')
288                 #update y grid
289                 self.plotter.set_y_grid(ref_level-y_per_div*y_divs, ref_level, y_per_div)
290                 #update y units
291                 self.plotter.set_y_label('Amplitude', 'dB')
292                 #update plotter
293                 self.plotter.update()