Imported Upstream version 3.2.2
[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_LEFT_UP, self.on_left_click)
237         self.Bind(wx.EVT_MOTION, self.on_motion)
238
239         self.input_watcher = input_watcher(ra_fftsink.msgq, ra_fftsink.fft_size, self)
240
241
242     def on_close_window (self, event):
243         print "fft_window:on_close_window"
244         self.keep_running = False
245
246             
247     def set_data (self, evt):
248         calc_min = 99e10
249         calc_max = -99e10
250         dB = evt.data
251         L = len (dB)
252
253         calc_min = min(dB)
254         calc_max = max(dB)
255
256         if (self.ra_fftsink.ofunc != None):
257             self.ra_fftsink.ofunc(evt.data,L)
258         
259         if self.peak_hold:
260             if self.peak_vals is None:
261                 self.peak_vals = dB
262             else:
263                 self.peak_vals = numpy.maximum(dB, self.peak_vals)
264                 dB = self.peak_vals
265
266         x = max(abs(self.ra_fftsink.sample_rate), abs(self.ra_fftsink.baseband_freq))
267         if x >= 1e9:
268             sf = 1e-9
269             units = "GHz"
270         elif x >= 1e6:
271             sf = 1e-6
272             units = "MHz"
273         elif x >= 1e3:
274             sf = 1e-3
275             units = "kHz"
276         else:
277             sf = 1.0
278             units = "Hz"
279
280         if self.ra_fftsink.input_is_real:     # only plot 1/2 the points
281             x_vals = ((numpy.arange (L/2)
282                        * (self.ra_fftsink.sample_rate * sf / L))
283                       + self.ra_fftsink.baseband_freq * sf)
284             points = numpy.zeros((len(x_vals), 2), numpy.float64)
285             points[:,0] = x_vals
286             points[:,1] = dB[0:L/2]
287         else:
288             # the "negative freqs" are in the second half of the array
289             x_vals = ((numpy.arange(-L/2, L/2)
290                        * (self.ra_fftsink.sample_rate * sf / L))
291                       + self.ra_fftsink.baseband_freq * sf)
292             points = numpy.zeros((len(x_vals), 2), numpy.float64)
293             points[:,0] = x_vals
294             points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2]))
295
296         lines = plot.PolyLine (points, colour='BLUE')
297         graphics = plot.PlotGraphics ([lines],
298                                       title=self.ra_fftsink.title,
299                                       xLabel = units, yLabel = "dB")
300
301         self.Draw (graphics, xAxis=None, yAxis=self.y_range)
302         d = calc_max - calc_min
303         d = d * 0.1
304         if self.ra_fftsink.autoscale == True:
305             self.y_range = self._axisInterval ('min', calc_min-d, calc_max+d)
306         else:
307             self.update_y_range ()
308
309     def set_peak_hold(self, enable):
310         self.peak_hold = enable
311         self.peak_vals = None
312
313     def update_y_range (self):
314         ymax = self.ra_fftsink.ref_level
315         ymin = self.ra_fftsink.ref_level - self.ra_fftsink.y_per_div * self.ra_fftsink.y_divs
316         self.y_range = self._axisInterval ('min', ymin, ymax)
317
318     def on_average(self, evt):
319         # print "on_average"
320         self.ra_fftsink.set_average(evt.IsChecked())
321
322     def on_peak_hold(self, evt):
323         # print "on_peak_hold"
324         self.ra_fftsink.set_peak_hold(evt.IsChecked())
325
326     def on_autoscale(self, evt):
327         self.ra_fftsink.set_autoscale(evt.IsChecked())
328
329     def on_incr_ref_level(self, evt):
330         # print "on_incr_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_decr_ref_level(self, evt):
335         # print "on_decr_ref_level"
336         self.ra_fftsink.set_ref_level(self.ra_fftsink.ref_level
337                                    - self.ra_fftsink.y_per_div)
338
339     def on_incr_y_per_div(self, evt):
340         # print "on_incr_y_per_div"
341         self.ra_fftsink.set_y_per_div(next_up(self.ra_fftsink.y_per_div, (0.5,1,2,5,10)))
342
343     def on_decr_y_per_div(self, evt):
344         # print "on_decr_y_per_div"
345         self.ra_fftsink.set_y_per_div(next_down(self.ra_fftsink.y_per_div, (0.5,1,2,5,10)))
346
347     def on_y_per_div(self, evt):
348         # print "on_y_per_div"
349         Id = evt.GetId()
350         if Id == self.id_y_per_div_1:
351             self.ra_fftsink.set_y_per_div(0.5)
352         elif Id == self.id_y_per_div_2:
353             self.ra_fftsink.set_y_per_div(1.0)
354         elif Id == self.id_y_per_div_5:
355             self.ra_fftsink.set_y_per_div(2.0)
356         elif Id == self.id_y_per_div_10:
357             self.ra_fftsink.set_y_per_div(5.0)
358         elif Id == self.id_y_per_div_20:
359             self.ra_fftsink.set_y_per_div(10)
360
361         
362     def on_right_click(self, event):
363         menu = self.popup_menu
364         for id, pred in self.checkmarks.items():
365             item = menu.FindItemById(id)
366             item.Check(pred())
367         self.PopupMenu(menu, event.GetPosition())
368
369     def on_motion(self, event):
370         if not self.ra_fftsink.xydfunc == None:
371             xy = self.GetXY(event)
372             self.ra_fftsink.xydfunc (0,xy)
373         
374     def on_left_click(self,event):
375         if not self.ra_fftsink.xydfunc == None:
376             xy = self.GetXY(event)
377             self.ra_fftsink.xydfunc (1,xy)
378
379     def build_popup_menu(self):
380         self.id_incr_ref_level = wx.NewId()
381         self.id_decr_ref_level = wx.NewId()
382         self.id_autoscale = wx.NewId()
383         self.id_incr_y_per_div = wx.NewId()
384         self.id_decr_y_per_div = wx.NewId()
385         self.id_y_per_div_1 = wx.NewId()
386         self.id_y_per_div_2 = wx.NewId()
387         self.id_y_per_div_5 = wx.NewId()
388         self.id_y_per_div_10 = wx.NewId()
389         self.id_y_per_div_20 = wx.NewId()
390         self.id_average = wx.NewId()
391         self.id_peak_hold = wx.NewId()
392
393         self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
394         self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold)
395         self.Bind(wx.EVT_MENU, self.on_autoscale, id=self.id_autoscale)
396         self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
397         self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
398         self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
399         self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
400         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
401         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
402         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
403         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
404         self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
405
406
407         # make a menu
408         menu = wx.Menu()
409         self.popup_menu = menu
410         menu.AppendCheckItem(self.id_average, "Average")
411         menu.AppendCheckItem(self.id_peak_hold, "Peak Hold")
412         menu.Append(self.id_incr_ref_level, "Incr Ref Level")
413         menu.Append(self.id_decr_ref_level, "Decr Ref Level")
414         # menu.Append(self.id_incr_y_per_div, "Incr dB/div")
415         # menu.Append(self.id_decr_y_per_div, "Decr dB/div")
416         menu.AppendSeparator()
417         # we'd use RadioItems for these, but they're not supported on Mac
418         menu.AppendCheckItem(self.id_autoscale, "Autoscale")
419         menu.AppendCheckItem(self.id_y_per_div_1, "0.5 dB/div")
420         menu.AppendCheckItem(self.id_y_per_div_2, "1.0 dB/div")
421         menu.AppendCheckItem(self.id_y_per_div_5, "2.0 dB/div")
422         menu.AppendCheckItem(self.id_y_per_div_10, "5.0 dB/div")
423         menu.AppendCheckItem(self.id_y_per_div_20, "10.0 dB/div")
424
425         self.checkmarks = {
426             self.id_average : lambda : self.ra_fftsink.average,
427             self.id_peak_hold : lambda : self.ra_fftsink.peak_hold,
428             self.id_autoscale : lambda : self.ra_fftsink.autoscale,
429             self.id_y_per_div_1 : lambda : self.ra_fftsink.y_per_div == 0.5,
430             self.id_y_per_div_2 : lambda : self.ra_fftsink.y_per_div == 1.0,
431             self.id_y_per_div_5 : lambda : self.ra_fftsink.y_per_div == 2.0,
432             self.id_y_per_div_10 : lambda : self.ra_fftsink.y_per_div == 5.0,
433             self.id_y_per_div_20 : lambda : self.ra_fftsink.y_per_div == 10.0,
434             }
435
436
437 def next_up(v, seq):
438     """
439     Return the first item in seq that is > v.
440     """
441     for s in seq:
442         if s > v:
443             return s
444     return v
445
446 def next_down(v, seq):
447     """
448     Return the last item in seq that is < v.
449     """
450     rseq = list(seq[:])
451     rseq.reverse()
452
453     for s in rseq:
454         if s < v:
455             return s
456     return v
457
458
459 # ----------------------------------------------------------------
460 # Standalone test app
461 # ----------------------------------------------------------------
462
463 class test_app_flow_graph (stdgui2.std_top_block):
464     def __init__(self, frame, panel, vbox, argv):
465         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
466
467         fft_size = 256
468
469         # build our flow graph
470         input_rate = 20.000e3
471
472         # Generate a complex sinusoid
473         src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
474         #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
475
476         # We add these throttle blocks so that this demo doesn't
477         # suck down all the CPU available.  Normally you wouldn't use these.
478         thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
479
480         sink1 = ra_fft_sink_c (panel, title="Complex Data", fft_size=fft_size,
481                             sample_rate=input_rate, baseband_freq=100e3,
482                             ref_level=60, y_per_div=10)
483         vbox.Add (sink1.win, 1, wx.EXPAND)
484         self.connect (src1, thr1, sink1)
485
486         src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
487         #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
488         thr2 = gr.throttle(gr.sizeof_float, input_rate)
489         sink2 = ra_fft_sink_f (panel, title="Real Data", fft_size=fft_size*2,
490                             sample_rate=input_rate, baseband_freq=100e3,
491                             ref_level=60, y_per_div=10)
492         vbox.Add (sink2.win, 1, wx.EXPAND)
493         self.connect (src2, thr2, sink2)
494
495 def main ():
496     app = stdgui2.stdapp (test_app_flow_graph,
497                          "FFT Sink Test App")
498     app.MainLoop ()
499
500 if __name__ == '__main__':
501     main ()