aa2624b898f011e0ee74a8d8e8d198a7aea2706b
[debian/gnuradio] / gr-radio-astronomy / src / python / ra_fftsink.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2005,2007 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
10 # any later version.
11
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING.  If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
21
22
23 from gnuradio import gr, gru, window
24 from gnuradio.wxgui import stdgui2
25 import wx
26 import gnuradio.wxgui.plot as plot
27 import numpy
28 import threading
29 import math    
30 import random
31
32 default_ra_fftsink_size = (640,140)
33
34
35
36 class ra_fft_sink_base(object):
37     def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, sc_y_per_div=0.5, ref_level=50, sc_ref_level=20,
38                  sample_rate=1, fft_size=512, fft_rate=15,
39                  average=False, avg_alpha=None, title='', peak_hold=False, ofunc=None, xydfunc=None):
40
41         # initialize common attributes
42         self.baseband_freq = baseband_freq
43         self.y_divs = 8
44         self.y_per_div=y_per_div
45         self.sc_y_per_div=sc_y_per_div
46         self.ref_level = ref_level
47         self.autoscale = False
48         self.sc_ref_level = sc_ref_level
49         self.sample_rate = sample_rate
50         self.fft_size = fft_size
51         self.fft_rate = fft_rate
52         self.binwidth = float(sample_rate/fft_size)
53         self.average = average
54         self.ofunc = ofunc
55         self.xydfunc = xydfunc
56         self.ofunc = ofunc
57         if avg_alpha is None:
58             self.avg_alpha = 2.0 / fft_rate
59         else:
60             self.avg_alpha = avg_alpha
61         self.title = title
62         self.peak_hold = peak_hold
63         self.input_is_real = input_is_real
64         self.msgq = gr.msg_queue(2)         # queue that holds a maximum of 2 messages
65
66     def set_y_per_div(self, y_per_div):
67         self.y_per_div = y_per_div
68
69
70     def set_ref_level(self, ref_level):
71         self.ref_level = ref_level
72
73     def set_average(self, average):
74         self.average = average
75         if average:
76             self.avg.set_taps(self.avg_alpha)
77             self.set_peak_hold(False)
78         else:
79             self.avg.set_taps(1.0)
80
81     def set_peak_hold(self, enable):
82         self.peak_hold = enable
83         if enable:
84             self.set_average(False)
85         self.win.set_peak_hold(enable)
86
87     def set_autoscale(self, auto):
88         self.autoscale = auto
89
90     def set_avg_alpha(self, avg_alpha):
91         self.avg_alpha = avg_alpha
92
93     def set_baseband_freq(self, baseband_freq):
94         self.baseband_freq = baseband_freq
95         
96
97 class ra_fft_sink_f(gr.hier_block2, ra_fft_sink_base):
98     def __init__(self, parent, baseband_freq=0,
99                  y_per_div=10, sc_y_per_div=0.5, sc_ref_level=40, ref_level=50, sample_rate=1, fft_size=512,
100                  fft_rate=15, average=False, avg_alpha=None, title='',
101                  size=default_ra_fftsink_size, peak_hold=False, ofunc=None,
102                  xydfunc=None):
103         gr.hier_block2.__init__(self, "ra_fft_sink_f",
104                                 gr.io_signature(1, 1, gr.sizeof_float),
105                                 gr.io_signature(0, 0, 0))
106                                 
107         ra_fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq,
108                                y_per_div=y_per_div, sc_y_per_div=sc_y_per_div,
109                                sc_ref_level=sc_ref_level, ref_level=ref_level,
110                                sample_rate=sample_rate, fft_size=fft_size,
111                                fft_rate=fft_rate,
112                                average=average, avg_alpha=avg_alpha, title=title,
113                                peak_hold=peak_hold, ofunc=ofunc, 
114                                xydfunc=xydfunc)
115                                
116         self.binwidth = float(sample_rate/2.0)/float(fft_size)
117         s2p = gr.serial_to_parallel(gr.sizeof_float, fft_size)
118         one_in_n = gr.keep_one_in_n(gr.sizeof_float * fft_size,
119                                     max(1, int(sample_rate/fft_size/fft_rate)))
120         mywindow = window.blackmanharris(fft_size)
121         fft = gr.fft_vfc(fft_size, True, mywindow)
122         c2mag = gr.complex_to_mag(fft_size)
123         self.avg = gr.single_pole_iir_filter_ff(1.0, fft_size)
124         log = gr.nlog10_ff(20, fft_size, -20*math.log10(fft_size))
125         sink = gr.message_sink(gr.sizeof_float * fft_size, self.msgq, True)
126
127         self.connect (self, s2p, one_in_n, fft, c2mag, self.avg, log, sink)
128
129         self.win = fft_window(self, parent, size=size)
130         self.set_average(self.average)
131
132 class ra_fft_sink_c(gr.hier_block2, ra_fft_sink_base):
133     def __init__(self, parent, baseband_freq=0,
134                  y_per_div=10, sc_y_per_div=0.5, sc_ref_level=40,
135                  ref_level=50, sample_rate=1, fft_size=512,
136                  fft_rate=15, average=False, avg_alpha=None, title='',
137                  size=default_ra_fftsink_size, peak_hold=False, ofunc=None, xydfunc=None):
138
139         gr.hier_block2.__init__(self, "ra_fft_sink_c",
140                                 gr.io_signature(1, 1, gr.sizeof_gr_complex),
141                                 gr.io_signature(0, 0, 0))
142                                 
143
144         ra_fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq,
145                                y_per_div=y_per_div, sc_y_per_div=sc_y_per_div,
146                                sc_ref_level=sc_ref_level, ref_level=ref_level,
147                                sample_rate=sample_rate, fft_size=fft_size,
148                                fft_rate=fft_rate,
149                                average=average, avg_alpha=avg_alpha, 
150                                title=title,
151                                peak_hold=peak_hold, ofunc=ofunc, 
152                                xydfunc=xydfunc)
153
154         s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, fft_size)
155         one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * fft_size,
156                                     max(1, int(sample_rate/fft_size/fft_rate)))
157         mywindow = window.blackmanharris(fft_size)
158         fft = gr.fft_vcc(fft_size, True, mywindow)
159         c2mag = gr.complex_to_mag(fft_size)
160         self.avg = gr.single_pole_iir_filter_ff(1.0, fft_size)
161         log = gr.nlog10_ff(20, fft_size, -20*math.log10(fft_size))
162         sink = gr.message_sink(gr.sizeof_float * fft_size, self.msgq, True)
163
164         self.connect(self, s2p, one_in_n, fft, c2mag, self.avg, log, sink)
165
166         self.win = fft_window(self, parent, size=size)
167         self.set_average(self.average)
168
169
170 # ------------------------------------------------------------------------
171
172 myDATA_EVENT = wx.NewEventType()
173 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
174
175
176 class DataEvent(wx.PyEvent):
177     def __init__(self, data):
178         wx.PyEvent.__init__(self)
179         self.SetEventType (myDATA_EVENT)
180         self.data = data
181
182     def Clone (self): 
183         self.__class__ (self.GetId())
184
185
186 class input_watcher (threading.Thread):
187     def __init__ (self, msgq, fft_size, event_receiver, **kwds):
188         threading.Thread.__init__ (self, **kwds)
189         self.setDaemon (1)
190         self.msgq = msgq
191         self.fft_size = fft_size
192         self.event_receiver = event_receiver
193         self.keep_running = True
194         self.start ()
195
196     def run (self):
197         while (self.keep_running):
198             msg = self.msgq.delete_head()  # blocking read of message queue
199             itemsize = int(msg.arg1())
200             nitems = int(msg.arg2())
201
202             s = msg.to_string()            # get the body of the msg as a string
203
204             # There may be more than one FFT frame in the message.
205             # If so, we take only the last one
206             if nitems > 1:
207                 start = itemsize * (nitems - 1)
208                 s = s[start:start+itemsize]
209
210             complex_data = numpy.fromstring (s, numpy.float32)
211             de = DataEvent (complex_data)
212             wx.PostEvent (self.event_receiver, de)
213             del de
214     
215
216 class fft_window (plot.PlotCanvas):
217     def __init__ (self, ra_fftsink, parent, id = -1,
218                   pos = wx.DefaultPosition, size = wx.DefaultSize,
219                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
220         plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
221
222         self.y_range = None
223         self.ra_fftsink = ra_fftsink
224         self.peak_hold = False
225         self.peak_vals = None
226
227         self.SetEnableGrid (True)
228         # self.SetEnableZoom (True)
229         # self.SetBackgroundColour ('black')
230         
231         self.build_popup_menu()
232         
233         EVT_DATA_EVENT (self, self.set_data)
234         wx.EVT_CLOSE (self, self.on_close_window)
235         self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
236         self.Bind(wx.EVT_MOTION, self.on_motion)
237
238         self.input_watcher = input_watcher(ra_fftsink.msgq, ra_fftsink.fft_size, self)
239
240
241     def on_close_window (self, event):
242         print "fft_window:on_close_window"
243         self.keep_running = False
244
245             
246     def set_data (self, evt):
247         calc_min = 99e10
248         calc_max = -99e10
249         dB = evt.data
250         L = len (dB)
251
252         calc_min = min(dB)
253         calc_max = max(dB)
254
255         if (self.ra_fftsink.ofunc != None):
256             self.ra_fftsink.ofunc(evt.data,L)
257         
258         if self.peak_hold:
259             if self.peak_vals is None:
260                 self.peak_vals = dB
261             else:
262                 self.peak_vals = numpy.maximum(dB, self.peak_vals)
263                 dB = self.peak_vals
264
265         x = max(abs(self.ra_fftsink.sample_rate), abs(self.ra_fftsink.baseband_freq))
266         if x >= 1e9:
267             sf = 1e-9
268             units = "GHz"
269         elif x >= 1e6:
270             sf = 1e-6
271             units = "MHz"
272         elif x >= 1e3:
273             sf = 1e-3
274             units = "kHz"
275         else:
276             sf = 1.0
277             units = "Hz"
278
279         if self.ra_fftsink.input_is_real:     # only plot 1/2 the points
280             x_vals = ((numpy.arange (L/2)
281                        * (self.ra_fftsink.sample_rate * sf / L))
282                       + self.ra_fftsink.baseband_freq * sf)
283             points = numpy.zeros((len(x_vals), 2), numpy.float64)
284             points[:,0] = x_vals
285             points[:,1] = dB[0:L/2]
286         else:
287             # the "negative freqs" are in the second half of the array
288             x_vals = ((numpy.arange(-L/2, L/2)
289                        * (self.ra_fftsink.sample_rate * sf / L))
290                       + self.ra_fftsink.baseband_freq * sf)
291             points = numpy.zeros((len(x_vals), 2), numpy.float64)
292             points[:,0] = x_vals
293             points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2]))
294
295         lines = plot.PolyLine (points, colour='BLUE')
296         graphics = plot.PlotGraphics ([lines],
297                                       title=self.ra_fftsink.title,
298                                       xLabel = units, yLabel = "dB")
299
300         self.Draw (graphics, xAxis=None, yAxis=self.y_range)
301         d = calc_max - calc_min
302         d = d * 0.1
303         if self.ra_fftsink.autoscale == True:
304             self.y_range = self._axisInterval ('min', calc_min-d, calc_max+d)
305         else:
306             self.update_y_range ()
307
308     def set_peak_hold(self, enable):
309         self.peak_hold = enable
310         self.peak_vals = None
311
312     def update_y_range (self):
313         ymax = self.ra_fftsink.ref_level
314         ymin = self.ra_fftsink.ref_level - self.ra_fftsink.y_per_div * self.ra_fftsink.y_divs
315         self.y_range = self._axisInterval ('min', ymin, ymax)
316
317     def on_average(self, evt):
318         # print "on_average"
319         self.ra_fftsink.set_average(evt.IsChecked())
320
321     def on_peak_hold(self, evt):
322         # print "on_peak_hold"
323         self.ra_fftsink.set_peak_hold(evt.IsChecked())
324
325     def on_autoscale(self, evt):
326         self.ra_fftsink.set_autoscale(evt.IsChecked())
327
328     def on_incr_ref_level(self, evt):
329         # print "on_incr_ref_level"
330         self.ra_fftsink.set_ref_level(self.ra_fftsink.ref_level
331                                    + self.ra_fftsink.y_per_div)
332
333     def on_decr_ref_level(self, evt):
334         # print "on_decr_ref_level"
335         self.ra_fftsink.set_ref_level(self.ra_fftsink.ref_level
336                                    - self.ra_fftsink.y_per_div)
337
338     def on_incr_y_per_div(self, evt):
339         # print "on_incr_y_per_div"
340         self.ra_fftsink.set_y_per_div(next_up(self.ra_fftsink.y_per_div, (0.5,1,2,5,10)))
341
342     def on_decr_y_per_div(self, evt):
343         # print "on_decr_y_per_div"
344         self.ra_fftsink.set_y_per_div(next_down(self.ra_fftsink.y_per_div, (0.5,1,2,5,10)))
345
346     def on_y_per_div(self, evt):
347         # print "on_y_per_div"
348         Id = evt.GetId()
349         if Id == self.id_y_per_div_1:
350             self.ra_fftsink.set_y_per_div(0.5)
351         elif Id == self.id_y_per_div_2:
352             self.ra_fftsink.set_y_per_div(1.0)
353         elif Id == self.id_y_per_div_5:
354             self.ra_fftsink.set_y_per_div(2.0)
355         elif Id == self.id_y_per_div_10:
356             self.ra_fftsink.set_y_per_div(5.0)
357         elif Id == self.id_y_per_div_20:
358             self.ra_fftsink.set_y_per_div(10)
359
360         
361     def on_right_click(self, event):
362         menu = self.popup_menu
363         for id, pred in self.checkmarks.items():
364             item = menu.FindItemById(id)
365             item.Check(pred())
366         self.PopupMenu(menu, event.GetPosition())
367
368     def on_motion(self, event):
369         if not self.ra_fftsink.xydfunc == None:
370             xy = self.GetXY(event)
371             self.ra_fftsink.xydfunc (xy)
372
373     def build_popup_menu(self):
374         self.id_incr_ref_level = wx.NewId()
375         self.id_decr_ref_level = wx.NewId()
376         self.id_autoscale = wx.NewId()
377         self.id_incr_y_per_div = wx.NewId()
378         self.id_decr_y_per_div = wx.NewId()
379         self.id_y_per_div_1 = wx.NewId()
380         self.id_y_per_div_2 = wx.NewId()
381         self.id_y_per_div_5 = wx.NewId()
382         self.id_y_per_div_10 = wx.NewId()
383         self.id_y_per_div_20 = wx.NewId()
384         self.id_average = wx.NewId()
385         self.id_peak_hold = wx.NewId()
386
387         self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
388         self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold)
389         self.Bind(wx.EVT_MENU, self.on_autoscale, id=self.id_autoscale)
390         self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
391         self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
392         self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
393         self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
394         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
395         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
396         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
397         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
398         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
399
400
401         # make a menu
402         menu = wx.Menu()
403         self.popup_menu = menu
404         menu.AppendCheckItem(self.id_average, "Average")
405         menu.AppendCheckItem(self.id_peak_hold, "Peak Hold")
406         menu.Append(self.id_incr_ref_level, "Incr Ref Level")
407         menu.Append(self.id_decr_ref_level, "Decr Ref Level")
408         # menu.Append(self.id_incr_y_per_div, "Incr dB/div")
409         # menu.Append(self.id_decr_y_per_div, "Decr dB/div")
410         menu.AppendSeparator()
411         # we'd use RadioItems for these, but they're not supported on Mac
412         menu.AppendCheckItem(self.id_autoscale, "Autoscale")
413         menu.AppendCheckItem(self.id_y_per_div_1, "0.5 dB/div")
414         menu.AppendCheckItem(self.id_y_per_div_2, "1.0 dB/div")
415         menu.AppendCheckItem(self.id_y_per_div_5, "2.0 dB/div")
416         menu.AppendCheckItem(self.id_y_per_div_10, "5.0 dB/div")
417         menu.AppendCheckItem(self.id_y_per_div_20, "10.0 dB/div")
418
419         self.checkmarks = {
420             self.id_average : lambda : self.ra_fftsink.average,
421             self.id_peak_hold : lambda : self.ra_fftsink.peak_hold,
422             self.id_autoscale : lambda : self.ra_fftsink.autoscale,
423             self.id_y_per_div_1 : lambda : self.ra_fftsink.y_per_div == 0.5,
424             self.id_y_per_div_2 : lambda : self.ra_fftsink.y_per_div == 1.0,
425             self.id_y_per_div_5 : lambda : self.ra_fftsink.y_per_div == 2.0,
426             self.id_y_per_div_10 : lambda : self.ra_fftsink.y_per_div == 5.0,
427             self.id_y_per_div_20 : lambda : self.ra_fftsink.y_per_div == 10.0,
428             }
429
430
431 def next_up(v, seq):
432     """
433     Return the first item in seq that is > v.
434     """
435     for s in seq:
436         if s > v:
437             return s
438     return v
439
440 def next_down(v, seq):
441     """
442     Return the last item in seq that is < v.
443     """
444     rseq = list(seq[:])
445     rseq.reverse()
446
447     for s in rseq:
448         if s < v:
449             return s
450     return v
451
452
453 # ----------------------------------------------------------------
454 # Standalone test app
455 # ----------------------------------------------------------------
456
457 class test_app_flow_graph (stdgui2.std_top_block):
458     def __init__(self, frame, panel, vbox, argv):
459         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
460
461         fft_size = 256
462
463         # build our flow graph
464         input_rate = 20.000e3
465
466         # Generate a complex sinusoid
467         src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
468         #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
469
470         # We add these throttle blocks so that this demo doesn't
471         # suck down all the CPU available.  Normally you wouldn't use these.
472         thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
473
474         sink1 = ra_fft_sink_c (panel, title="Complex Data", fft_size=fft_size,
475                             sample_rate=input_rate, baseband_freq=100e3,
476                             ref_level=60, y_per_div=10)
477         vbox.Add (sink1.win, 1, wx.EXPAND)
478         self.connect (src1, thr1, sink1)
479
480         src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
481         #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
482         thr2 = gr.throttle(gr.sizeof_float, input_rate)
483         sink2 = ra_fft_sink_f (panel, title="Real Data", fft_size=fft_size*2,
484                             sample_rate=input_rate, baseband_freq=100e3,
485                             ref_level=60, y_per_div=10)
486         vbox.Add (sink2.win, 1, wx.EXPAND)
487         self.connect (src2, thr2, sink2)
488
489 def main ():
490     app = stdgui2.stdapp (test_app_flow_graph,
491                          "FFT Sink Test App")
492     app.MainLoop ()
493
494 if __name__ == '__main__':
495     main ()