2 # Copyright 2008 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
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)
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.
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.
22 ##################################################
24 ##################################################
31 from constants import *
32 from gnuradio import gr #for gr.prefs
34 ##################################################
36 ##################################################
38 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0
39 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30)
40 DEFAULT_WIN_SIZE = (600, 300)
41 DIV_LEVELS = (1, 2, 5, 10, 20)
42 MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200
50 ##################################################
51 # Waterfall window control panel
52 ##################################################
53 class control_panel(wx.Panel):
55 A control panel with wx widgits to control the plotter and fft block chain.
58 def __init__(self, parent):
60 Create a new control panel.
61 @param parent the wx parent window
64 wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
65 control_box = wx.BoxSizer(wx.VERTICAL)
66 control_box.AddStretchSpacer()
67 control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER)
69 control_box.AddStretchSpacer()
70 self.color_mode_chooser = common.DropDownController(self, 'Color', COLOR_MODES, parent, COLOR_MODE_KEY)
71 control_box.Add(self.color_mode_chooser, 0, wx.EXPAND)
73 control_box.AddStretchSpacer()
74 self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key)
75 control_box.Add(self.average_check_box, 0, wx.EXPAND)
76 control_box.AddSpacer(2)
77 self.avg_alpha_slider = common.LogSliderController(
79 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS,
80 parent.ext_controller, parent.avg_alpha_key,
81 formatter=lambda x: ': %.4f'%x,
83 parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable)
84 control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND)
85 #dyanmic range buttons
86 control_box.AddStretchSpacer()
87 control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER)
88 control_box.AddSpacer(2)
89 self._dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range)
90 control_box.Add(self._dynamic_range_buttons, 0, wx.ALIGN_CENTER)
92 control_box.AddStretchSpacer()
93 control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER)
94 control_box.AddSpacer(2)
95 self._ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level)
96 control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER)
98 control_box.AddStretchSpacer()
99 control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER)
100 control_box.AddSpacer(2)
101 self._time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale)
102 control_box.Add(self._time_scale_buttons, 0, wx.ALIGN_CENTER)
104 control_box.AddStretchSpacer()
105 self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT)
106 self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale)
107 control_box.Add(self.autoscale_button, 0, wx.EXPAND)
109 self.clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT)
110 self.clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button)
111 control_box.Add(self.clear_button, 0, wx.EXPAND)
113 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
114 control_box.Add(self.run_button, 0, wx.EXPAND)
116 self.SetSizerAndFit(control_box)
118 ##################################################
120 ##################################################
121 def _on_clear_button(self, event):
122 self.parent.set_num_lines(self.parent[NUM_LINES_KEY])
123 def _on_incr_dynamic_range(self, event):
124 self.parent.set_dynamic_range(
125 min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE))
126 def _on_decr_dynamic_range(self, event):
127 self.parent.set_dynamic_range(
128 max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE))
129 def _on_incr_ref_level(self, event):
130 self.parent.set_ref_level(
131 self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1)
132 def _on_decr_ref_level(self, event):
133 self.parent.set_ref_level(
134 self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1)
135 def _on_incr_time_scale(self, event):
136 old_rate = self.parent.ext_controller[self.parent.frame_rate_key]
137 self.parent.ext_controller[self.parent.frame_rate_key] *= 0.75
138 if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate:
139 self.parent.ext_controller[self.parent.decimation_key] += 1
140 def _on_decr_time_scale(self, event):
141 old_rate = self.parent.ext_controller[self.parent.frame_rate_key]
142 self.parent.ext_controller[self.parent.frame_rate_key] *= 1.25
143 if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate:
144 self.parent.ext_controller[self.parent.decimation_key] -= 1
146 ##################################################
147 # Waterfall window with plotter and control panel
148 ##################################################
149 class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter):
169 pubsub.pubsub.__init__(self)
171 self.samples = list()
172 self.ext_controller = controller
174 self.fft_size = fft_size
175 self.decimation_key = decimation_key
176 self.sample_rate_key = sample_rate_key
177 self.frame_rate_key = frame_rate_key
178 self.average_key = average_key
179 self.avg_alpha_key = avg_alpha_key
181 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
182 self.plotter = plotter.waterfall_plotter(self)
183 self.plotter.SetSize(wx.Size(*size))
184 self.plotter.set_title(title)
185 self.plotter.enable_point_label(True)
186 #setup the box with plot and controls
187 self.control_panel = control_panel(self)
188 main_box = wx.BoxSizer(wx.HORIZONTAL)
189 main_box.Add(self.plotter, 1, wx.EXPAND)
190 main_box.Add(self.control_panel, 0, wx.EXPAND)
191 self.SetSizerAndFit(main_box)
193 self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode)
194 self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines)
196 self.ext_controller[self.average_key] = self.ext_controller[self.average_key]
197 self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key]
198 self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range)
199 self._register_set_prop(self, NUM_LINES_KEY, num_lines)
200 self._register_set_prop(self, Y_DIVS_KEY, 8)
201 self._register_set_prop(self, X_DIVS_KEY, 8) #approximate
202 self._register_set_prop(self, REF_LEVEL_KEY, ref_level)
203 self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq)
204 self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1])
205 self._register_set_prop(self, RUNNING_KEY, True)
207 self.ext_controller.subscribe(msg_key, self.handle_msg)
208 self.ext_controller.subscribe(self.decimation_key, self.update_grid)
209 self.ext_controller.subscribe(self.sample_rate_key, self.update_grid)
210 self.ext_controller.subscribe(self.frame_rate_key, self.update_grid)
211 self.subscribe(BASEBAND_FREQ_KEY, self.update_grid)
212 self.subscribe(NUM_LINES_KEY, self.update_grid)
213 self.subscribe(Y_DIVS_KEY, self.update_grid)
214 self.subscribe(X_DIVS_KEY, self.update_grid)
218 def autoscale(self, *args):
220 Autoscale the waterfall plot to the last frame.
221 Set the dynamic range and reference level.
222 Does not affect the current data in the waterfall.
224 if not len(self.samples): return
225 #get the peak level (max of the samples)
226 peak_level = numpy.max(self.samples)
227 #get the noise floor (averge the smallest samples)
228 noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4])
230 noise_floor -= abs(noise_floor)*.5
231 peak_level += abs(peak_level)*.1
232 #set the range and level
233 self.set_ref_level(peak_level)
234 self.set_dynamic_range(peak_level - noise_floor)
236 def handle_msg(self, msg):
238 Handle the message from the fft sink message queue.
239 If complex, reorder the fft samples so the negative bins come first.
240 If real, keep take only the positive bins.
241 Send the data to the plotter.
242 @param msg the fft array as a character array
244 if not self[RUNNING_KEY]: return
245 #convert to floating point numbers
246 self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame
247 num_samps = len(samples)
249 if self.real: samples = samples[:num_samps/2]
250 else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2]))
252 self.plotter.set_samples(
254 minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY],
255 maximum=self[REF_LEVEL_KEY],
258 self.plotter.update()
260 def update_grid(self, *args):
262 Update the plotter grid.
263 This update method is dependent on the variables below.
264 Determine the x and y axis grid parameters.
265 The x axis depends on sample rate, baseband freq, and x divs.
266 The y axis depends on y per div, y divs, and ref level.
269 sample_rate = self.ext_controller[self.sample_rate_key]
270 frame_rate = self.ext_controller[self.frame_rate_key]
271 baseband_freq = self[BASEBAND_FREQ_KEY]
272 num_lines = self[NUM_LINES_KEY]
273 y_divs = self[Y_DIVS_KEY]
274 x_divs = self[X_DIVS_KEY]
275 #determine best fitting x_per_div
276 if self.real: x_width = sample_rate/2.0
277 else: x_width = sample_rate/1.0
278 x_per_div = common.get_clean_num(x_width/x_divs)
279 coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0))
282 self.plotter.set_x_grid(
284 baseband_freq + sample_rate/2.0,
289 self.plotter.set_x_grid(
290 baseband_freq - sample_rate/2.0,
291 baseband_freq + sample_rate/2.0,
296 self.plotter.set_x_label('Frequency', prefix+'Hz')
298 duration = float(num_lines)/frame_rate
299 y_per_div = common.get_clean_num(duration/y_divs)
300 self.plotter.set_y_grid(0, duration, y_per_div)
302 self.plotter.set_y_label('Time', 's')
304 self.plotter.update()