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