Imported Upstream version 3.2.2
[debian/gnuradio] / gr-wxgui / src / python / waterfallsink_nongl.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2005,2007,2008 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 os
29 import math    
30
31 default_fftsink_size = (640,240)
32 default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15)
33
34 class waterfall_sink_base(object):
35     def __init__(self, input_is_real=False, baseband_freq=0,
36                  sample_rate=1, fft_size=512,
37                  fft_rate=default_fft_rate,
38                  average=False, avg_alpha=None, title=''):
39
40         # initialize common attributes
41         self.baseband_freq = baseband_freq
42         self.sample_rate = sample_rate
43         self.fft_size = fft_size
44         self.fft_rate = fft_rate
45         self.average = average
46         if avg_alpha is None:
47             self.avg_alpha = 2.0 / fft_rate
48         else:
49             self.avg_alpha = avg_alpha
50         self.title = title
51         self.input_is_real = input_is_real
52         self.msgq = gr.msg_queue(2)         # queue up to 2 messages
53
54     def set_average(self, average):
55         self.average = average
56         if average:
57             self.avg.set_taps(self.avg_alpha)
58         else:
59             self.avg.set_taps(1.0)
60
61     def set_avg_alpha(self, avg_alpha):
62         self.avg_alpha = avg_alpha
63
64     def set_baseband_freq(self, baseband_freq):
65         self.baseband_freq = baseband_freq
66
67     def set_sample_rate(self, sample_rate):
68         self.sample_rate = sample_rate
69         self._set_n()
70
71     def _set_n(self):
72         self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
73         
74 class waterfall_sink_f(gr.hier_block2, waterfall_sink_base):
75     def __init__(self, parent, baseband_freq=0,
76                  y_per_div=10, ref_level=50, sample_rate=1, fft_size=512,
77                  fft_rate=default_fft_rate, average=False, avg_alpha=None,
78                  title='', size=default_fftsink_size):
79
80         gr.hier_block2.__init__(self, "waterfall_sink_f",
81                                 gr.io_signature(1, 1, gr.sizeof_float),
82                                 gr.io_signature(0,0,0))
83
84         waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq,
85                                sample_rate=sample_rate, fft_size=fft_size,
86                                fft_rate=fft_rate,
87                                average=average, avg_alpha=avg_alpha, title=title)
88                                
89         self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size)
90         self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size,
91                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
92         
93         mywindow = window.blackmanharris(self.fft_size)
94         self.fft = gr.fft_vfc(self.fft_size, True, mywindow)
95         self.c2mag = gr.complex_to_mag(self.fft_size)
96         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
97         self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
98         self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
99         self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
100
101         self.win = waterfall_window(self, parent, size=size)
102         self.set_average(self.average)
103
104
105 class waterfall_sink_c(gr.hier_block2, waterfall_sink_base):
106     def __init__(self, parent, baseband_freq=0,
107                  y_per_div=10, ref_level=50, sample_rate=1, fft_size=512,
108                  fft_rate=default_fft_rate, average=False, avg_alpha=None, 
109                  title='', size=default_fftsink_size):
110
111         gr.hier_block2.__init__(self, "waterfall_sink_f",
112                                 gr.io_signature(1, 1, gr.sizeof_gr_complex),
113                                 gr.io_signature(0,0,0))
114
115         waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq,
116                                      sample_rate=sample_rate, fft_size=fft_size,
117                                      fft_rate=fft_rate,
118                                      average=average, avg_alpha=avg_alpha, title=title)
119
120         self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size)
121         self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size,
122                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
123         
124         mywindow = window.blackmanharris(self.fft_size)
125         self.fft = gr.fft_vcc(self.fft_size, True, mywindow)
126         self.c2mag = gr.complex_to_mag(self.fft_size)
127         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
128         self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
129         self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
130         self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
131
132         self.win = waterfall_window(self, parent, size=size)
133         self.set_average(self.average)
134
135
136 # ------------------------------------------------------------------------
137
138 myDATA_EVENT = wx.NewEventType()
139 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
140
141
142 class DataEvent(wx.PyEvent):
143     def __init__(self, data):
144         wx.PyEvent.__init__(self)
145         self.SetEventType (myDATA_EVENT)
146         self.data = data
147
148     def Clone (self): 
149         self.__class__ (self.GetId())
150     
151 class input_watcher (gru.msgq_runner):
152     def __init__ (self, msgq, fft_size, event_receiver, **kwds):
153         self.fft_size = fft_size
154         self.event_receiver = event_receiver
155         gru.msgq_runner.__init__(self, msgq, self.handle_msg)
156
157     def handle_msg(self, msg):
158         itemsize = int(msg.arg1())
159         nitems = int(msg.arg2())
160
161         s = msg.to_string() # get the body of the msg as a string
162
163         # There may be more than one FFT frame in the message.
164         # If so, we take only the last one
165         if nitems > 1:
166             start = itemsize * (nitems - 1)
167             s = s[start:start+itemsize]
168
169         complex_data = numpy.fromstring (s, numpy.float32)
170         de = DataEvent (complex_data)
171         wx.PostEvent (self.event_receiver, de)
172         del de
173
174 class waterfall_window (wx.Panel):
175     def __init__ (self, fftsink, parent, id = -1,
176                   pos = wx.DefaultPosition, size = wx.DefaultSize,
177                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
178         wx.Panel.__init__(self, parent, id, pos, size, style, name)
179         self.set_baseband_freq = fftsink.set_baseband_freq
180         self.fftsink = fftsink
181         self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1)
182
183         self.scale_factor = 5.0           # FIXME should autoscale, or set this
184         
185         dc1 = wx.MemoryDC()
186         dc1.SelectObject(self.bm)
187         dc1.Clear()
188
189         self.pens = self.make_pens()
190
191         wx.EVT_PAINT( self, self.OnPaint )
192         wx.EVT_CLOSE (self, self.on_close_window)
193         EVT_DATA_EVENT (self, self.set_data)
194         
195         self.build_popup_menu()
196         
197         wx.EVT_CLOSE (self, self.on_close_window)
198         self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
199
200         self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self)
201
202
203     def on_close_window (self, event):
204         print "waterfall_window: on_close_window"
205         self.keep_running = False
206
207     def const_list(self,const,len):
208         return [const] * len
209
210     def make_colormap(self):
211         r = []
212         r.extend(self.const_list(0,96))
213         r.extend(range(0,255,4))
214         r.extend(self.const_list(255,64))
215         r.extend(range(255,128,-4))
216         
217         g = []
218         g.extend(self.const_list(0,32))
219         g.extend(range(0,255,4))
220         g.extend(self.const_list(255,64))
221         g.extend(range(255,0,-4))
222         g.extend(self.const_list(0,32))
223         
224         b = range(128,255,4)
225         b.extend(self.const_list(255,64))
226         b.extend(range(255,0,-4))
227         b.extend(self.const_list(0,96))
228         return (r,g,b)
229
230     def make_pens(self):
231         (r,g,b) = self.make_colormap()
232         pens = []
233         for i in range(0,256):
234             colour = wx.Colour(r[i], g[i], b[i])
235             pens.append( wx.Pen(colour, 2, wx.SOLID))
236         return pens
237         
238     def OnPaint(self, event):
239         dc = wx.PaintDC(self)
240         self.DoDrawing(dc)
241
242     def DoDrawing(self, dc=None):
243         if dc is None:
244             dc = wx.ClientDC(self)
245         dc.DrawBitmap(self.bm, 0, 0, False )
246     
247
248     def const_list(self,const,len):
249         a = [const]
250         for i in range(1,len):
251             a.append(const)
252         return a
253
254
255     def set_data (self, evt):
256         dB = evt.data
257         L = len (dB)
258
259         dc1 = wx.MemoryDC()
260         dc1.SelectObject(self.bm)
261         dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1)
262
263         x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq))
264         if x >= 1e9:
265             sf = 1e-9
266             units = "GHz"
267         elif x >= 1e6:
268             sf = 1e-6
269             units = "MHz"
270         else:
271             sf = 1e-3
272             units = "kHz"
273
274
275         if self.fftsink.input_is_real:     # only plot 1/2 the points
276             d_max = L/2
277             p_width = 2
278         else:
279             d_max = L/2
280             p_width = 1
281
282         scale_factor = self.scale_factor
283         if self.fftsink.input_is_real:     # real fft
284            for x_pos in range(0, d_max):
285                value = int(dB[x_pos] * scale_factor)
286                value = min(255, max(0, value))
287                dc1.SetPen(self.pens[value])
288                dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) 
289         else:                               # complex fft
290            for x_pos in range(0, d_max):    # positive freqs
291                value = int(dB[x_pos] * scale_factor)
292                value = min(255, max(0, value))
293                dc1.SetPen(self.pens[value])
294                dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2) 
295            for x_pos in range(0 , d_max):   # negative freqs
296                value = int(dB[x_pos+d_max] * scale_factor)
297                value = min(255, max(0, value))
298                dc1.SetPen(self.pens[value])
299                dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2) 
300
301         del dc1
302         self.DoDrawing (None)
303
304     def on_average(self, evt):
305         # print "on_average"
306         self.fftsink.set_average(evt.IsChecked())
307
308     def on_right_click(self, event):
309         menu = self.popup_menu
310         for id, pred in self.checkmarks.items():
311             item = menu.FindItemById(id)
312             item.Check(pred())
313         self.PopupMenu(menu, event.GetPosition())
314
315
316     def build_popup_menu(self):
317         self.id_incr_ref_level = wx.NewId()
318         self.id_decr_ref_level = wx.NewId()
319         self.id_incr_y_per_div = wx.NewId()
320         self.id_decr_y_per_div = wx.NewId()
321         self.id_y_per_div_1 = wx.NewId()
322         self.id_y_per_div_2 = wx.NewId()
323         self.id_y_per_div_5 = wx.NewId()
324         self.id_y_per_div_10 = wx.NewId()
325         self.id_y_per_div_20 = wx.NewId()
326         self.id_average = wx.NewId()
327
328         self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
329         #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
330         #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
331         #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
332         #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
333         #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
334         #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
335         #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
336         #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
337         #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
338
339
340         # make a menu
341         menu = wx.Menu()
342         self.popup_menu = menu
343         menu.AppendCheckItem(self.id_average, "Average")
344         # menu.Append(self.id_incr_ref_level, "Incr Ref Level")
345         # menu.Append(self.id_decr_ref_level, "Decr Ref Level")
346         # menu.Append(self.id_incr_y_per_div, "Incr dB/div")
347         # menu.Append(self.id_decr_y_per_div, "Decr dB/div")
348         # menu.AppendSeparator()
349         # we'd use RadioItems for these, but they're not supported on Mac
350         #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div")
351         #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div")
352         #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div")
353         #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div")
354         #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div")
355
356         self.checkmarks = {
357             self.id_average : lambda : self.fftsink.average
358             #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1,
359             #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2,
360             #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5,
361             #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10,
362             #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20,
363             }
364
365
366 def next_up(v, seq):
367     """
368     Return the first item in seq that is > v.
369     """
370     for s in seq:
371         if s > v:
372             return s
373     return v
374
375 def next_down(v, seq):
376     """
377     Return the last item in seq that is < v.
378     """
379     rseq = list(seq[:])
380     rseq.reverse()
381
382     for s in rseq:
383         if s < v:
384             return s
385     return v
386
387
388 # ----------------------------------------------------------------
389 # Standalone test app
390 # ----------------------------------------------------------------
391
392 class test_top_block (stdgui2.std_top_block):
393     def __init__(self, frame, panel, vbox, argv):
394         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
395
396         fft_size = 512
397
398         # build our flow graph
399         input_rate = 20.000e3
400
401         # Generate a complex sinusoid
402         self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
403         #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
404
405         # We add these throttle blocks so that this demo doesn't
406         # suck down all the CPU available.  Normally you wouldn't use these.
407         self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
408
409         sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size,
410                                   sample_rate=input_rate, baseband_freq=100e3)
411         self.connect(self.src1, self.thr1, sink1)
412         vbox.Add (sink1.win, 1, wx.EXPAND)
413
414         # generate a real sinusoid
415         self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
416         self.thr2 = gr.throttle(gr.sizeof_float, input_rate)
417         sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size,
418                                   sample_rate=input_rate, baseband_freq=100e3)
419         self.connect(self.src2, self.thr2, sink2)
420         vbox.Add (sink2.win, 1, wx.EXPAND)
421
422
423 def main ():
424     app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App")
425     app.MainLoop ()
426
427 if __name__ == '__main__':
428     main ()