config properties and rates for gl sinks
[debian/gnuradio] / gr-wxgui / src / python / waterfall_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_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
43 COLOR_MODES = (
44         ('RGB1', 'rgb1'),
45         ('RGB2', 'rgb2'),
46         ('RGB3', 'rgb3'),
47         ('Gray', 'gray'),
48 )
49
50 ##################################################
51 # Waterfall window control panel
52 ##################################################
53 class control_panel(wx.Panel):
54         """
55         A control panel with wx widgits to control the plotter and fft block chain.
56         """
57
58         def __init__(self, parent):
59                 """
60                 Create a new control panel.
61                 @param parent the wx parent window
62                 """
63                 self.parent = parent
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)
68                 #color mode
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)
72                 #average
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(
78                         self, 'Avg Alpha',
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,
82                 )
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)
91                 #ref lvl buttons
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)
97                 #num lines buttons
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)
103                 #autoscale
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)
108                 #clear
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)
112                 #run/stop
113                 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
114                 control_box.Add(self.run_button, 0, wx.EXPAND)
115                 #set sizer
116                 self.SetSizerAndFit(control_box)
117
118         ##################################################
119         # Event handlers
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
145
146 ##################################################
147 # Waterfall window with plotter and control panel
148 ##################################################
149 class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter):
150         def __init__(
151                 self,
152                 parent,
153                 controller,
154                 size,
155                 title,
156                 real,
157                 fft_size,
158                 num_lines,
159                 decimation_key,
160                 baseband_freq,
161                 sample_rate_key,
162                 frame_rate_key,
163                 dynamic_range,
164                 ref_level,
165                 average_key,
166                 avg_alpha_key,
167                 msg_key,
168         ):
169                 pubsub.pubsub.__init__(self)
170                 #setup
171                 self.ext_controller = controller
172                 self.real = real
173                 self.fft_size = fft_size
174                 self.decimation_key = decimation_key
175                 self.sample_rate_key = sample_rate_key
176                 self.frame_rate_key = frame_rate_key
177                 self.average_key = average_key
178                 self.avg_alpha_key = avg_alpha_key
179                 #init panel and plot
180                 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
181                 self.plotter = plotter.waterfall_plotter(self)
182                 self.plotter.SetSize(wx.Size(*size))
183                 self.plotter.set_title(title)
184                 self.plotter.enable_point_label(True)
185                 #setup the box with plot and controls
186                 self.control_panel = control_panel(self)
187                 main_box = wx.BoxSizer(wx.HORIZONTAL)
188                 main_box.Add(self.plotter, 1, wx.EXPAND)
189                 main_box.Add(self.control_panel, 0, wx.EXPAND)
190                 self.SetSizerAndFit(main_box)
191                 #plotter listeners
192                 self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode)
193                 self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines)
194                 #initial setup
195                 self.ext_controller[self.average_key] = self.ext_controller[self.average_key]
196                 self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key]
197                 self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range)
198                 self._register_set_prop(self, NUM_LINES_KEY, num_lines)
199                 self._register_set_prop(self, Y_DIVS_KEY, 8)
200                 self._register_set_prop(self, X_DIVS_KEY, 8) #approximate
201                 self._register_set_prop(self, REF_LEVEL_KEY, ref_level)
202                 self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq)
203                 self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1])
204                 self._register_set_prop(self, RUNNING_KEY, True)
205                 #register events
206                 self.ext_controller.subscribe(msg_key, self.handle_msg)
207                 self.ext_controller.subscribe(self.decimation_key, self.update_grid)
208                 self.ext_controller.subscribe(self.sample_rate_key, self.update_grid)
209                 self.ext_controller.subscribe(self.frame_rate_key, self.update_grid)
210                 self.subscribe(BASEBAND_FREQ_KEY, self.update_grid)
211                 self.subscribe(NUM_LINES_KEY, self.update_grid)
212                 self.subscribe(Y_DIVS_KEY, self.update_grid)
213                 self.subscribe(X_DIVS_KEY, self.update_grid)
214                 #initial update
215                 self.update_grid()
216
217         def autoscale(self, *args):
218                 """
219                 Autoscale the waterfall plot to the last frame.
220                 Set the dynamic range and reference level.
221                 Does not affect the current data in the waterfall.
222                 """
223                 #get the peak level (max of the samples)
224                 peak_level = numpy.max(self.samples)
225                 #get the noise floor (averge the smallest samples)
226                 noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4])
227                 #padding
228                 noise_floor -= abs(noise_floor)*.5
229                 peak_level += abs(peak_level)*.1
230                 #set the range and level
231                 self.set_ref_level(peak_level)
232                 self.set_dynamic_range(peak_level - noise_floor)
233
234         def handle_msg(self, msg):
235                 """
236                 Handle the message from the fft sink message queue.
237                 If complex, reorder the fft samples so the negative bins come first.
238                 If real, keep take only the positive bins.
239                 Send the data to the plotter.
240                 @param msg the fft array as a character array
241                 """
242                 if not self[RUNNING_KEY]: return
243                 #convert to floating point numbers
244                 self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame
245                 num_samps = len(samples)
246                 #reorder fft
247                 if self.real: samples = samples[:num_samps/2]
248                 else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2]))
249                 #plot the fft
250                 self.plotter.set_samples(
251                         samples=samples,
252                         minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY], 
253                         maximum=self[REF_LEVEL_KEY],
254                 )
255                 #update the plotter
256                 self.plotter.update()
257
258         def update_grid(self, *args):
259                 """
260                 Update the plotter grid.
261                 This update method is dependent on the variables below.
262                 Determine the x and y axis grid parameters.
263                 The x axis depends on sample rate, baseband freq, and x divs.
264                 The y axis depends on y per div, y divs, and ref level.
265                 """
266                 #grid parameters
267                 sample_rate = self.ext_controller[self.sample_rate_key]
268                 frame_rate = self.ext_controller[self.frame_rate_key]
269                 baseband_freq = self[BASEBAND_FREQ_KEY]
270                 num_lines = self[NUM_LINES_KEY]
271                 y_divs = self[Y_DIVS_KEY]
272                 x_divs = self[X_DIVS_KEY]
273                 #determine best fitting x_per_div
274                 if self.real: x_width = sample_rate/2.0
275                 else: x_width = sample_rate/1.0
276                 x_per_div = common.get_clean_num(x_width/x_divs)
277                 coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0))
278                 #update the x grid
279                 if self.real:
280                         self.plotter.set_x_grid(
281                                 baseband_freq,
282                                 baseband_freq + sample_rate/2.0,
283                                 x_per_div,
284                                 10**(-exp),
285                         )
286                 else:
287                         self.plotter.set_x_grid(
288                                 baseband_freq - sample_rate/2.0,
289                                 baseband_freq + sample_rate/2.0,
290                                 x_per_div,
291                                 10**(-exp),
292                         )
293                 #update x units
294                 self.plotter.set_x_label('Frequency', prefix+'Hz')
295                 #update y grid
296                 duration = float(num_lines)/frame_rate
297                 y_per_div = common.get_clean_num(duration/y_divs)
298                 self.plotter.set_y_grid(0, duration, y_per_div)
299                 #update y units
300                 self.plotter.set_y_label('Time', 's')
301                 #update plotter
302                 self.plotter.update()