? peakfind.py Index: Makefile.am =================================================================== RCS file: /cvsroot/gnuradio/gr-wxgui/src/python/gnuradio/wxgui/Makefile.am,v retrieving revision 1.2 diff -u -r1.2 Makefile.am --- Makefile.am 19 Sep 2004 21:43:54 -0000 1.2 +++ Makefile.am 17 Jan 2005 04:45:56 -0000 @@ -32,4 +32,5 @@ __init__.py \ fftsink.py \ scopesink.py \ - stdgui.py + stdgui.py \ + peakfind.py Index: fftsink.py =================================================================== RCS file: /cvsroot/gnuradio/gr-wxgui/src/python/gnuradio/wxgui/fftsink.py,v retrieving revision 1.2 diff -u -r1.2 fftsink.py --- fftsink.py 19 Sep 2004 01:24:39 -0000 1.2 +++ fftsink.py 17 Jan 2005 04:45:56 -0000 @@ -21,12 +21,13 @@ # from gnuradio import gr -from gnuradio.wxgui import stdgui +from gnuradio.wxgui import stdgui,peakfind import wx import wx.lib.plot as plot import Numeric import os import threading + @@ -37,13 +38,25 @@ # block requires a single input stream of float # win is a subclass of wxWindow -def make_fft_sink_f (fg, parent, label, fft_size, input_rate): +def make_fft_sink_f (fg, parent, label, fft_size, input_rate,show_peak_info=False,show_peak_markers=False,navg_frames=100,peak_searchwidth=-1): + #show_peak_info (default=False): if set to true the fft_window will show the frequency and level of every big peak found in the spectrum + #show_peak_markers (default=False): if set to true the fft_window will show a marker at every big peak found in the spectrum + #navg_frames (default=100): number of fft frames to average before showing it. If set to 100 or higher you get a much quiter spectrum (less noisy and not changing very much every frame) + #peak_search_width(-1 gets you the default of 10 for a fft_size of 1024): number of neigbours the peakfinding algorithm will look at for determing if something is a peak. + # A higher value means you get less peaks detected + #Default the peakfinding algorithm determines a minimum signalstrength threshold below which no peaks are detected + # you can override this with set_autopeakthreshold(False) and set_peakthreshold(mythreshold) to use your own threshold (r_fd, w_fd) = os.pipe () fft_rate = 20 - + if(navg_frames>1): + fft_rate = fft_rate*navg_frames s2p = gr.serial_to_parallel (gr.sizeof_float, fft_size) + decimate=int (input_rate / fft_size / fft_rate) + if(decimate<1): + decimate=1 + #fft_rate=input_rate/fft_size/decimate one_in_n = gr.keep_one_in_n (gr.sizeof_float * fft_size, - int (input_rate / fft_size / fft_rate)) + decimate) fft = gr.fft_vfc (fft_size, True, True) dst = gr.file_descriptor_sink (gr.sizeof_gr_complex * fft_size, w_fd) @@ -53,7 +66,7 @@ block = s2p # head of pipeline - win = fft_window (fft_info (r_fd, fft_size, input_rate, True, label), parent) + win = fft_window (fft_info (r_fd, fft_size, input_rate, True, label,show_peak_info,show_peak_markers,navg_frames,peak_searchwidth), parent) return (block, win) @@ -62,13 +75,17 @@ # block requires a single input stream of gr_complex # win is a subclass of wxWindow -def make_fft_sink_c (fg, parent, label, fft_size, input_rate): +def make_fft_sink_c (fg, parent, label, fft_size, input_rate,show_peak_info=False,show_peak_markers=False,navg_frames=100,peak_searchwidth=-1): (r_fd, w_fd) = os.pipe () fft_rate = 20 - + if(navg_frames>1): + fft_rate = fft_rate*navg_frames + decimate=int (input_rate / fft_size / fft_rate) + if(decimate<1): + decimate=1 s2p = gr.serial_to_parallel (gr.sizeof_gr_complex, fft_size) - one_in_n = gr.keep_one_in_n (gr.sizeof_gr_complex * fft_size, - int (input_rate / fft_size / fft_rate)) + one_in_n = gr.keep_one_in_n (gr.sizeof_gr_complex * fft_size, + decimate) fft = gr.fft_vcc (fft_size, True, True) dst = gr.file_descriptor_sink (gr.sizeof_gr_complex * fft_size, w_fd) @@ -78,7 +95,7 @@ block = s2p # head of pipeline - win = fft_window (fft_info (r_fd, fft_size, input_rate, False, label), parent) + win = fft_window (fft_info (r_fd, fft_size, input_rate, False, label,show_peak_info,show_peak_markers,navg_frames,peak_searchwidth), parent) return (block, win) @@ -99,21 +116,26 @@ class fft_info: - def __init__ (self, file_descriptor, fft_size, sample_rate, input_is_real, title = "fft"): + def __init__ (self, file_descriptor, fft_size, sample_rate, input_is_real, title = "fft",show_peak_info=False,show_peak_markers=False,navg_frames=100,peak_searchwidth=-1): self.file_descriptor = file_descriptor self.fft_size = fft_size self.sample_rate = sample_rate self.input_is_real = input_is_real self.title = title; + self.show_peak_info=show_peak_info + self.show_peak_markers=show_peak_markers + self.navg_frames = navg_frames + self.peak_searchwidth = peak_searchwidth class input_watcher (threading.Thread): - def __init__ (self, file_descriptor, fft_size, event_receiver, **kwds): + def __init__ (self, file_descriptor, fft_size, navg_frames,event_receiver, **kwds): threading.Thread.__init__ (self, **kwds) self.setDaemon (1) self.file_descriptor = file_descriptor self.fft_size = fft_size self.event_receiver = event_receiver self.keep_running = True + self.navg_frames = navg_frames self.start () def run (self): @@ -124,8 +146,16 @@ self.keep_running = False break - complex_data = Numeric.fromstring (s, Numeric.Complex32) - de = DataEvent (complex_data) + mag_data = abs(Numeric.fromstring (s, Numeric.Complex32)) + if(self.navg_frames>1): + for i in range(1,self.navg_frames): + s = os.read (self.file_descriptor, gr.sizeof_gr_complex * self.fft_size) + if not s: + self.keep_running = False + break + mag_data = mag_data + abs(Numeric.fromstring (s, Numeric.Complex32)) + + de = DataEvent (mag_data) wx.PostEvent (self.event_receiver, de) # print "run: len(complex_data) = ", len(complex_data) del de @@ -145,14 +175,44 @@ self.y_range = None self.avg_y_min = None self.avg_y_max = None - + self.navg_frames=info.navg_frames #number of fft's to average over EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) self.input_watcher = input_watcher (info.file_descriptor, - info.fft_size, + info.fft_size , self.navg_frames, self) + self.peakfinder=peakfind.SimplePeakFinder() + self.peakthreshold=0.0 + self.peak_searchwidth=info.peak_searchwidth #searchspace in x-direction of frequencies that will be checked to be lower than the peak. Can be 1 to fftsize + if(self.peak_searchwidth<=0): + self.peak_searchwidth=10*1024/info.fft_size #default to inspect 10 neigbours at fft_size 1024, scale accordingly if other fft_size is used + info.peak_searchwidth=self.peak_searchwidth + + self.show_peak_markers=info.show_peak_markers + self.show_peak_info=info.show_peak_info + self.autopeakthreshold=True #default if finds its own lower threshold for peaks + #self.SetFontSizeLegend(1) + def set_peak_searchwidth(peak_searchwidth): + self.peak_searchwidth=peak_searchwidth + + def get_peak_searchwidth(): + return self.peak_searchwidth + + def set_autopeakthreshold(autopeakthreshold): + self.autopeakthreshold=autopeakthreshold + + def get_autopeakthreshold(): + return self.autopeakthreshold + + def set_peakthreshold(peakthreshold): + self.autopeakthreshold=False + self.peakthreshold=peakthreshold + + def get_peakthreshold(): + return peakthreshold + def on_close_window (self, event): print "fft_window:on_close_window" self.keep_running = False @@ -160,38 +220,55 @@ def set_data (self, evt): data = evt.data - dB = 20 * Numeric.log10 (abs(data) + 1e-8) + #dB = 20 * Numeric.log10 (abs(data) + 1e-8) + dB = 20 * Numeric.log10 (data + 1e-8) - 20*Numeric.log10(self.navg_frames) l = len (dB) - if self.info.sample_rate >= 1e6: sf = 1e-6 units = "MHz" else: sf = 1e-3 units = "kHz" - + plot_objects=[] if self.info.input_is_real: # only plot 1/2 the points x_vals = Numeric.arrayrange (l/2) * (self.info.sample_rate * sf / l) - points = zip (x_vals, dB[0:l/2]) + y_vals = dB[0:l/2] else: # the "negative freqs" are in the second half of the array x_vals = Numeric.arrayrange (-l/2+1, l/2) * (self.info.sample_rate * sf / l) - points = zip (x_vals, Numeric.concatenate ((dB[l/2:], dB[0:l/2]))) - - + y_vals = Numeric.concatenate ((dB[l/2:], dB[0:l/2])) + points = zip (x_vals, y_vals) lines = plot.PolyLine (points, colour='LIME GREEN') - - graphics = plot.PlotGraphics ([lines], + self.current_lines=lines + plot_objects.append(lines) + if(self.show_peak_info | self.show_peak_markers): + peaks = self.peakfinder.getPeaks(self.peak_searchwidth,y_vals,self.peakthreshold,False) + peak_points=[] + for i in peaks: + peak_points.append([x_vals[i],y_vals[i]]) + peaktext_x_offset=0 + if(self.show_peak_markers): + plot_objects.append(plot.PolyMarker(peak_points,legend='peak')) + peaktext_x_offset=10 + graphics = plot.PlotGraphics (plot_objects, title=self.info.title, xLabel = units, yLabel = "dB") self.Draw (graphics, xAxis=None, yAxis=self.y_range) + mydc=wx.ClientDC(self) + h = mydc.GetCharHeight() + if(self.show_peak_info): + for pt in peak_points: + label='%5.4f ' % pt[0] + units + ' %4.1f dB' % pt[1] + ptscaled=Numeric.array(pt)*self._pointScale+self._pointShift + mydc.DrawText(label,ptscaled[0]+peaktext_x_offset,ptscaled[1]-0.5*h) self.update_y_range () def update_y_range (self): alpha = 1.0/25 graphics = self.last_draw[0] - p1, p2 = graphics.boundingBox () # min, max points of graphics + #p1, p2 = graphics.boundingBox () # min, max points of graphics + p1,p2 = self.current_lines.boundingBox() # min, max points of graphics if self.avg_y_min: self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha) @@ -199,7 +276,8 @@ else: self.avg_y_min = p1[1] self.avg_y_max = p2[1] - + if(self.autopeakthreshold): + self.peakthreshold=(8*self.avg_y_min+2*self.avg_y_max)/10 self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max) @@ -215,7 +293,7 @@ input_rate = 20.0001234e6 src = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e6, 10e3) - block, fft_win = make_fft_sink_c (self, panel, "Secret Data", 512, input_rate) + block, fft_win = make_fft_sink_c (self, panel, "Secret Data", 512, input_rate,True,False,100) self.connect (src, block) vbox.Add (fft_win, 1, wx.EXPAND)