From 49fa13f9fce2037d176c86bf326a7e25a78b72a5 Mon Sep 17 00:00:00 2001 From: Martin Dudok van Heel Date: Mon, 26 Apr 2010 19:40:41 +0200 Subject: [PATCH] Add analog CRT screen afterglow emulation for gr-wxgui --- gr-wxgui/src/python/constants.py | 2 + gr-wxgui/src/python/fft_window.py | 51 ++++++++++ gr-wxgui/src/python/fftsink_gl.py | 37 ++++++- gr-wxgui/src/python/fftsink_nongl.py | 60 ++++++++++-- gr-wxgui/src/python/plot.py | 98 +++++++++++++++++-- .../src/python/plotter/channel_plotter.py | 1 + gr-wxgui/src/python/plotter/plotter_base.py | 36 ++++++- gr-wxgui/src/python/scope_window.py | 52 ++++++++++ gr-wxgui/src/python/scopesink_gl.py | 32 ++++-- 9 files changed, 340 insertions(+), 29 deletions(-) diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 8ff7fa8f..517a5528 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -27,6 +27,8 @@ ALPHA_KEY = 'alpha' AUTORANGE_KEY = 'autorange' AVERAGE_KEY = 'average' AVG_ALPHA_KEY = 'avg_alpha' +EMULATE_ANALOG_KEY = 'emulate_analog' +ANALOG_ALPHA_KEY = 'analog_alpha' BASEBAND_FREQ_KEY = 'baseband_freq' BETA_KEY = 'beta' COLOR_MODE_KEY = 'color_mode' diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index e025c28d..67bb65b2 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -37,6 +37,7 @@ import forms ################################################## SLIDER_STEPS = 100 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 +ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DB_DIV_MIN, DB_DIV_MAX = 1, 20 @@ -95,7 +96,38 @@ class control_panel(wx.Panel): for widget in (avg_alpha_text, avg_alpha_slider): parent.subscribe(AVERAGE_KEY, widget.Enable) widget.Enable(parent[AVERAGE_KEY]) + parent.subscribe(AVERAGE_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[AVERAGE_KEY]) + + parent.subscribe(AVERAGE_KEY, self._update_layout) + + forms.check_box( + sizer=options_box, parent=self, label='Emulate Analog', + ps=parent, key=EMULATE_ANALOG_KEY, + ) + #static text and slider for analog alpha + analog_alpha_text = forms.static_text( + sizer=options_box, parent=self, label='Analog Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ) + analog_alpha_slider = forms.log_slider( + sizer=options_box, parent=self, + min_exp=ANALOG_ALPHA_MIN_EXP, + max_exp=ANALOG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ANALOG_ALPHA_KEY, + ) + for widget in (analog_alpha_text, analog_alpha_slider): + parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) + widget.Enable(parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + #trace menu for trace in TRACES: trace_box = wx.BoxSizer(wx.HORIZONTAL) @@ -142,6 +174,7 @@ class control_panel(wx.Panel): ) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event def on_mouse_wheel(event): if event.GetWheelRotation() < 0: self._on_incr_ref_level(event) @@ -159,6 +192,14 @@ class control_panel(wx.Panel): self.parent[Y_PER_DIV_KEY] = min(DB_DIV_MAX, common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) def _on_decr_db_div(self, event): self.parent[Y_PER_DIV_KEY] = max(DB_DIV_MIN, common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) + ################################################## + # subscriber handlers + ################################################## + def _update_layout(self,key): + # Just ignore the key value we get + # we only need to now that the visability or size of something has changed + self.parent.Layout() + #self.parent.Fit() ################################################## # FFT window with plotter and control panel @@ -181,7 +222,10 @@ class fft_window(wx.Panel, pubsub.pubsub): avg_alpha_key, peak_hold, msg_key, + emulate_analog, + analog_alpha, ): + pubsub.pubsub.__init__(self) #setup self.samples = EMPTY_TRACE @@ -202,6 +246,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self[REF_LEVEL_KEY] = ref_level self[BASEBAND_FREQ_KEY] = baseband_freq self[RUNNING_KEY] = True + self[EMULATE_ANALOG_KEY] = emulate_analog + self[ANALOG_ALPHA_KEY] = analog_alpha for trace in TRACES: #a function that returns a function #so the function wont use local trace @@ -230,6 +276,8 @@ class fft_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) + self.plotter.set_emulate_analog(emulate_analog) + self.plotter.set_analog_alpha(analog_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -245,9 +293,12 @@ class fft_window(wx.Panel, pubsub.pubsub): Y_PER_DIV_KEY, X_DIVS_KEY, Y_DIVS_KEY, REF_LEVEL_KEY, ): self.subscribe(key, self.update_grid) + self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) + self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) #initial update self.update_grid() + def autoscale(self, *args): """ Autoscale the fft plot to the last frame. diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 9d683d69..56476448 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -27,6 +27,7 @@ import common from gnuradio import gr, blks2 from pubsub import pubsub from constants import * +import math ################################################## # FFT sink block (wrapper for old wxgui) @@ -52,9 +53,20 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): title='', size=fft_window.DEFAULT_WIN_SIZE, peak_hold=False, + emulate_analog=False, + analog_alpha=None, ): #ensure avg alpha if avg_alpha is None: avg_alpha = 2.0/fft_rate + #ensure analog alpha + if analog_alpha is None: + actual_fft_rate=float(sample_rate/fft_size)/float(max(1,int(float((sample_rate/fft_size)/fft_rate)))) + #print "requested_fft_rate ",fft_rate + #print "actual_fft_rate ",actual_fft_rate + analog_cutoff_freq=0.5 # Hertz + #calculate alpha from wanted cutoff freq + analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_fft_rate) + #init gr.hier_block2.__init__( self, @@ -73,6 +85,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): ) msgq = gr.msg_queue(2) sink = gr.message_sink(gr.sizeof_float*fft_size, msgq, True) + + #controller self.controller = pubsub() self.controller.subscribe(AVERAGE_KEY, fft.set_average) @@ -100,6 +114,8 @@ class _fft_sink_base(gr.hier_block2, common.wxgui_hb): avg_alpha_key=AVG_ALPHA_KEY, peak_hold=peak_hold, msg_key=MSG_KEY, + emulate_analog=emulate_analog, + analog_alpha=analog_alpha, ) common.register_access_methods(self, self.win) setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS @@ -131,11 +147,14 @@ class test_app_block (stdgui2.std_top_block): fft_size = 256 # build our flow graph - input_rate = 20.48e3 + input_rate = 2048.0e3 + + #Generate some noise + noise =gr.noise_source_c(gr.GR_UNIFORM, 1.0/10) # Generate a complex sinusoid #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 57.50e3, 1) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. @@ -146,17 +165,25 @@ class test_app_block (stdgui2.std_top_block): ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink1.win, 1, wx.EXPAND) - self.connect(src1, thr1, sink1) + combine1=gr.add_cc() + self.connect(src1, (combine1,0)) + self.connect(noise,(combine1,1)) + self.connect(combine1,thr1, sink1) #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 57.50e3, 1) thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, ref_level=0, y_per_div=20, y_divs=10) vbox.Add (sink2.win, 1, wx.EXPAND) - self.connect(src2, thr2, sink2) + combine2=gr.add_ff() + c2f2=gr.complex_to_float() + + self.connect(src2, (combine2,0)) + self.connect(noise,c2f2,(combine2,1)) + self.connect(combine2, thr2,sink2) def main (): app = stdgui2.stdapp (test_app_block, "FFT Sink Test App") diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index ca5e91fd..f1c1f439 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -37,7 +37,7 @@ class fft_sink_base(object): y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, - average=False, avg_alpha=None, title='', peak_hold=False): + average=False, avg_alpha=None, title='', peak_hold=False,emulate_analog=False,analog_alpha=0.2): # initialize common attributes self.baseband_freq = baseband_freq @@ -52,6 +52,9 @@ class fft_sink_base(object): self.avg_alpha = 2.0 / fft_rate else: self.avg_alpha = avg_alpha + self.emulate_analog = emulate_analog + self.analog_alpha = analog_alpha + self.title = title self.peak_hold = peak_hold self.input_is_real = input_is_real @@ -75,6 +78,14 @@ class fft_sink_base(object): self.peak_hold = enable self.win.set_peak_hold(enable) + def set_emulate_analog(self, enable): + self.emulate_analog = enable + self.win.set_emulate_analog(enable) + + def set_analog_alpha(self, analog_alpha): + self.analog_alpha = analog_alpha + self.win.set_analog_alpha(analog_alpha) + def set_avg_alpha(self, avg_alpha): self.avg_alpha = avg_alpha @@ -93,7 +104,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, ref_scale=2.0, y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2): gr.hier_block2.__init__(self, "fft_sink_f", gr.io_signature(1, 1, gr.sizeof_float), @@ -104,7 +115,7 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) + peak_hold=peak_hold,emulate_analog=emulate_analog,analog_alpha=analog_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size, @@ -131,12 +142,14 @@ class fft_sink_f(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) self.set_peak_hold(self.peak_hold) + self.set_emulate_analog(self.emulate_analog) + self.set_analog_alpha(self.analog_alpha) class fft_sink_c(gr.hier_block2, fft_sink_base): def __init__(self, parent, baseband_freq=0, ref_scale=2.0, y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512, fft_rate=default_fft_rate, average=False, avg_alpha=None, - title='', size=default_fftsink_size, peak_hold=False): + title='', size=default_fftsink_size, peak_hold=False, emulate_analog=False,analog_alpha=0.2): gr.hier_block2.__init__(self, "fft_sink_c", gr.io_signature(1, 1, gr.sizeof_gr_complex), @@ -147,7 +160,7 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): sample_rate=sample_rate, fft_size=fft_size, fft_rate=fft_rate, average=average, avg_alpha=avg_alpha, title=title, - peak_hold=peak_hold) + peak_hold=peak_hold, emulate_analog=emulate_analog,analog_alpha=analog_alpha) self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size, @@ -173,6 +186,8 @@ class fft_sink_c(gr.hier_block2, fft_sink_base): self.win = fft_window(self, parent, size=size) self.set_average(self.average) + self.set_emulate_analog(self.emulate_analog) + self.set_analog_alpha(self.analog_alpha) self.set_peak_hold(self.peak_hold) @@ -236,6 +251,9 @@ class control_panel(wx.Panel): self.average_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Average") self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average) control_box.Add(self.average_check_box, 0, wx.EXPAND) + self.emulate_analog_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Emulate Analog") + self.emulate_analog_check_box.Bind(wx.EVT_CHECKBOX, parent.on_emulate_analog) + control_box.Add(self.emulate_analog_check_box, 0, wx.EXPAND) self.peak_hold_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Peak Hold") self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) @@ -276,6 +294,7 @@ class control_panel(wx.Panel): """ #update checkboxes self.average_check_box.SetValue(self.parent.fftsink.average) + self.emulate_analog_check_box.SetValue(self.parent.fftsink.emulate_analog) self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold) #update radio buttons try: @@ -306,6 +325,10 @@ class fft_window (wx.Panel): self.peak_hold = False self.peak_vals = None + + self.emulate_analog=False + self.analog_alpha=0.2 + self.plot.SetEnableGrid (True) # self.SetEnableZoom (True) @@ -394,6 +417,14 @@ class fft_window (wx.Panel): y_range = ymin, ymax self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div) + def set_emulate_analog(self, enable): + self.emulate_analog = enable + self.plot.set_emulate_analog( enable) + + def set_analog_alpha(self, analog_alpha): + self.analog_alpha = analog_alpha + self.plot.set_analog_alpha(analog_alpha) + def set_peak_hold(self, enable): self.peak_hold = enable self.peak_vals = None @@ -403,6 +434,11 @@ class fft_window (wx.Panel): self.fftsink.set_average(evt.IsChecked()) self.control_panel.update() + def on_emulate_analog(self, evt): + # print "on_analog" + self.fftsink.set_emulate_analog(evt.IsChecked()) + self.control_panel.update() + def on_peak_hold(self, evt): # print "on_peak_hold" self.fftsink.set_peak_hold(evt.IsChecked()) @@ -486,9 +522,11 @@ class fft_window (wx.Panel): self.id_y_per_div_10 = wx.NewId() self.id_y_per_div_20 = wx.NewId() self.id_average = wx.NewId() + self.id_emulate_analog = wx.NewId() self.id_peak_hold = wx.NewId() self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average) + self.plot.Bind(wx.EVT_MENU, self.on_emulate_analog, id=self.id_emulate_analog) self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold) self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level) self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level) @@ -504,6 +542,7 @@ class fft_window (wx.Panel): menu = wx.Menu() self.popup_menu = menu menu.AppendCheckItem(self.id_average, "Average") + menu.AppendCheckItem(self.id_emulate_analog, "Emulate Analog") menu.AppendCheckItem(self.id_peak_hold, "Peak Hold") menu.Append(self.id_incr_ref_level, "Incr Ref Level") menu.Append(self.id_decr_ref_level, "Decr Ref Level") @@ -519,6 +558,7 @@ class fft_window (wx.Panel): self.checkmarks = { self.id_average : lambda : self.fftsink.average, + self.id_emulate_analog : lambda : self.fftsink.emulate_analog, self.id_peak_hold : lambda : self.fftsink.peak_hold, self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1, self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2, @@ -561,11 +601,11 @@ class test_app_block (stdgui2.std_top_block): fft_size = 256 # build our flow graph - input_rate = 20.48e3 + input_rate = 100*20.48e3 # Generate a complex sinusoid - #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) + src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) # We add these throttle blocks so that this demo doesn't # suck down all the CPU available. Normally you wouldn't use these. @@ -578,8 +618,8 @@ class test_app_block (stdgui2.std_top_block): self.connect(src1, thr1, sink1) - #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1) - src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1) + #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1) + src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1) thr2 = gr.throttle(gr.sizeof_float, input_rate) sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2, sample_rate=input_rate, baseband_freq=100e3, diff --git a/gr-wxgui/src/python/plot.py b/gr-wxgui/src/python/plot.py index c104b0ea..c5557cb6 100644 --- a/gr-wxgui/src/python/plot.py +++ b/gr-wxgui/src/python/plot.py @@ -36,6 +36,9 @@ # # May 27, 2007 Johnathan Corgan (jcorgan@corganenterprises.com) # - Converted from numarray to numpy +# +# Apr 23, 2010 Martin Dudok van Heel (http://www.olifantasia.com/gnuradio/contact_olifantasia.gif) +# - Added Emulate Analog option (emulate after glow of an analog CRT display using IIR) """ This is a simple light weight plotting module that can be used with @@ -422,6 +425,11 @@ class PlotCanvas(wx.Window): def __init__(self, parent, id = -1, pos=wx.DefaultPosition, size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""): + + self.emulate_analog=False + self.alpha=0.3 + self.decimation=10 + self.decim_counter=0 """Constucts a window, which can be a child of a frame, dialog or any other non-control window""" @@ -488,6 +496,14 @@ class PlotCanvas(wx.Window): # platforms at initialization, but little harm done. self.OnSize(None) # sets the initial size based on client size # UNCONDITIONAL, needed to create self._Buffer + + + def set_emulate_analog(self, enable): + self.emulate_analog = enable + + def set_analog_alpha(self, analog_alpha): + self.alpha = analog_alpha + # SaveFile def SaveFile(self, fileName= ''): @@ -791,12 +807,19 @@ class PlotCanvas(wx.Window): if dc == None: # sets new dc and clears it - dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) - dc.Clear() - + if self.emulate_analog: + dc = wx.MemoryDC() + dc.SelectObject(self._Buffer) + dc.Clear() + else: + dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer) + dc.Clear() + dc.BeginDrawing() # dc.Clear() - + + + # set font size for every thing but title and legend dc.SetFont(self._getFont(self._fontSizeAxis)) @@ -818,6 +841,15 @@ class PlotCanvas(wx.Window): self.last_draw = (graphics, xAxis, yAxis) # saves most recient values + if False: + ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2) + #dc.SetPen(wx.Pen(wx.BLACK)) + dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) ) #wx.SOLID wx.TRANSPARENT ) ) + #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT + dc.DrawRectangle( ptx,pty, rectWidth,rectHeight) + #dc.SetBrush(wx.Brush( wx.WHITE, wx.SOLID ) ) + #dc.SetLogicalFunction(wx.COPY) + # Get ticks and textExtents for axis if required if self._xSpec is not 'none': if self._xUseScopeTicks: @@ -874,8 +906,11 @@ class PlotCanvas(wx.Window): scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _numpy.array((1,-1)) shift = -p1*scale + self.plotbox_origin + textSize_shift * _numpy.array((1,-1)) self._pointScale= scale # make available for mouse events - self._pointShift= shift + self._pointShift= shift + + #dc.SetLogicalFunction(wx.INVERT) #wx.XOR wx.INVERT self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks) + #dc.SetLogicalFunction(wx.COPY) graphics.scaleAndShift(scale, shift) graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing @@ -885,11 +920,44 @@ class PlotCanvas(wx.Window): dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight) # Draw the lines and markers #start = _time.clock() + graphics.draw(dc) # print "entire graphics drawing took: %f second"%(_time.clock() - start) # remove the clipping region dc.DestroyClippingRegion() dc.EndDrawing() + + + if self.emulate_analog: + dc=None + self._Buffer.CopyToBuffer(self._Bufferarray) #, format=wx.BitmapBufferFormat_RGB, stride=-1) + ## do the IIR filter + alpha_int=int(float(self.alpha*256)) + if True: + _numpy.add(self._Bufferarray,0,self._Buffer3array) + _numpy.multiply(self._Buffer3array,alpha_int,self._Buffer3array) + _numpy.multiply(self._Buffer2array,(256-alpha_int),self._Buffer2array) + _numpy.add(self._Buffer3array,self._Buffer2array,self._Buffer2array) + _numpy.right_shift(self._Buffer2array,8,self._Buffer2array) + elif False: + self._Buffer2array=(self._Bufferarray.astype(_numpy.uint32) *alpha_int + self._Buffer2array*(256-alpha_int)).__rshift__(8) + elif False: + self._Buffer2array *=(256-alpha_int) + self._Buffer2array +=self._Bufferarray.astype(_numpy.uint32)*alpha_int + self._Buffer2array /=256 + + ##copy back to image buffer + self._Buffer2.CopyFromBuffer(self._Buffer2array.astype(_numpy.uint8)) #, format=wx.BitmapBufferFormat_RGB, stride=-1) + + #draw to the screen + #self.decim_counter=self.decim_counter+1 + if True: #self.decim_counter>self.decimation: + #self.decim_counter=0 + dc2 = wx.ClientDC( self ) + dc2.BeginDrawing() + dc2.DrawBitmap(self._Buffer2, 0, 0, False) + #dc2.DrawBitmap(self._Buffer, 0, 0, False) + dc2.EndDrawing() def Redraw(self, dc= None): """Redraw the existing plot.""" @@ -1031,6 +1099,8 @@ class PlotCanvas(wx.Window): if self.last_PointLabel != None: self._drawPointLabel(self.last_PointLabel) #erase old self.last_PointLabel = None + + #paint current buffer to screen dc = wx.BufferedPaintDC(self, self._Buffer) def OnSize(self,event): @@ -1041,7 +1111,23 @@ class PlotCanvas(wx.Window): # Make new offscreen bitmap: this bitmap will always have the # current drawing in it, so it can be used to save the image to # a file, or whatever. - self._Buffer = wx.EmptyBitmap(Size[0],Size[1]) + self._Buffer = wx.EmptyBitmap(Size[0],Size[1],24) + + + if True: #self.emulate_analog: + #self._Bufferarray = _numpy.zeros((Size[0], Size[1],3), dtype=_numpy.uint8) + self._Bufferarray = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint8) + + # Make new second offscreen bitmap: this bitmap will always have the + # last drawing in it, so it can be used to do display time dependent processing + # like averaging (IIR) or show differences between updates + self._Buffer2 = wx.EmptyBitmap(Size[0],Size[1],24) + # now the extra buffers for the IIR processing + # note the different datatype uint32 + self._Buffer2array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float + self._Buffer3array = _numpy.zeros((Size[0]* Size[1]*3), dtype=_numpy.uint32) #dtype=_numpy.float + # optional you can set the ufunct buffer size to improve speed + #_numpy.setbufsize(16*((Size[0]* Size[1]*3)/16 +1)) self._setSize() self.last_PointLabel = None #reset pointLabel diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index ff0a3a16..f046ae5a 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -47,6 +47,7 @@ class channel_plotter(grid_plotter_base): """ #init grid_plotter_base.__init__(self, parent, MIN_PADDING) + self.set_emulate_analog(False) #setup legend cache self._legend_cache = self.new_gl_cache(self._draw_legend, 50) self.enable_legend(False) diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index dede5a0a..2fbd5fb9 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -87,7 +87,10 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): @param parent the parent widgit """ attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) - wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) + wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList); + self.emulate_analog=False + self.analog_alpha=2.0/15 + self.clear_accum=True self._gl_init_flag = False self._resized_flag = True self._init_fcns = list() @@ -97,6 +100,13 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self.Bind(wx.EVT_SIZE, self._on_size) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) + def set_emulate_analog(self,enable): + self.emulate_analog=enable + self.clear_accum=True + + def set_analog_alpha(self,analog_alpha): + self.analog_alpha=analog_alpha + def new_gl_cache(self, draw_fcn, draw_pri=50): """ Create a new gl cache. @@ -131,6 +141,7 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ self.lock() self._resized_flag = True + self.clear_accum=True self.unlock() def _on_paint(self, event): @@ -160,7 +171,30 @@ class plotter_base(wx.glcanvas.GLCanvas, common.mutex): self._resized_flag = False #clear, draw functions, swap GL.glClear(GL.GL_COLOR_BUFFER_BIT) + + if False: + GL.glEnable (GL.GL_LINE_SMOOTH) + GL.glEnable (GL.GL_POLYGON_SMOOTH) + GL.glEnable (GL.GL_BLEND) + GL.glBlendFunc (GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) + GL.glHint (GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST) #GL.GL_DONT_CARE) + GL.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST) + #GL.glLineWidth (1.5) + + GL.glEnable(GL.GL_MULTISAMPLE) #Enable Multisampling anti-aliasing + + for fcn in self._draw_fcns: fcn[1]() + + if self.emulate_analog: + if self.clear_accum: + #GL.glClear(GL.GL_ACCUM_BUFFER_BIT) + GL.glAccum(GL.GL_LOAD, 1.0) + self.clear_accum=False + + GL.glAccum(GL.GL_MULT, 1.0-self.analog_alpha) + GL.glAccum(GL.GL_ACCUM, self.analog_alpha) + GL.glAccum(GL.GL_RETURN, 1.0) self.SwapBuffers() self.unlock() diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 44904640..a6c7bdb4 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -36,6 +36,8 @@ import forms # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) +ANALOG_ALPHA_MIN_EXP, ANALOG_ALPHA_MAX_EXP = -2, 0 +SLIDER_STEPS = 100 DEFAULT_WIN_SIZE = (600, 300) COUPLING_MODES = ( ('DC', False), @@ -83,6 +85,37 @@ class control_panel(wx.Panel): self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) + + ################################################## + # Emulate Analog + ################################################## + + forms.check_box( + sizer=control_box, parent=self, label='Emulate Analog', + ps=parent, key=EMULATE_ANALOG_KEY, + ) + #static text and slider for analog alpha + analog_alpha_text = forms.static_text( + sizer=control_box, parent=self, label='Analog Alpha', + converter=forms.float_converter(lambda x: '%.4f'%x), + ps=parent, key=ANALOG_ALPHA_KEY, width=50, + ) + analog_alpha_slider = forms.log_slider( + sizer=control_box, parent=self, + min_exp=ANALOG_ALPHA_MIN_EXP, + max_exp=ANALOG_ALPHA_MAX_EXP, + num_steps=SLIDER_STEPS, + ps=parent, key=ANALOG_ALPHA_KEY, + ) + for widget in (analog_alpha_text, analog_alpha_slider): + parent.subscribe(EMULATE_ANALOG_KEY, widget.Enable) + widget.Enable(parent[EMULATE_ANALOG_KEY]) + parent.subscribe(EMULATE_ANALOG_KEY, widget.ShowItems) + #allways show initially, so room is reserved for them + widget.ShowItems(True) # (parent[EMULATE_ANALOG_KEY]) + + parent.subscribe(EMULATE_ANALOG_KEY, self._update_layout) + ################################################## # Axes Options ################################################## @@ -359,6 +392,15 @@ class control_panel(wx.Panel): def _on_decr_y_off(self, event): self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY] + ################################################## + # subscriber handlers + ################################################## + def _update_layout(self,key): + # Just ignore the key value we get + # we only need to now that the visability or size of something has changed + self.parent.Layout() + #self.parent.Fit() + ################################################## # Scope window with plotter and control panel ################################################## @@ -382,6 +424,8 @@ class scope_window(wx.Panel, pubsub.pubsub): trigger_channel_key, decimation_key, msg_key, + emulate_analog, + analog_alpha, ): pubsub.pubsub.__init__(self) #check num inputs @@ -424,6 +468,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS self[T_FRAC_OFF_KEY] = 0.5 + self[EMULATE_ANALOG_KEY] = emulate_analog + self[ANALOG_ALPHA_KEY] = analog_alpha for i in range(num_inputs): self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot @@ -434,6 +480,8 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.enable_legend(True) self.plotter.enable_point_label(True) self.plotter.enable_grid_lines(True) + self.plotter.set_emulate_analog(emulate_analog) + self.plotter.set_analog_alpha(analog_alpha) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -451,6 +499,9 @@ class scope_window(wx.Panel, pubsub.pubsub): XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY, TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, ]: self.subscribe(key, self.update_grid) + #register events for plotter settings + self.subscribe(EMULATE_ANALOG_KEY, self.plotter.set_emulate_analog) + self.subscribe(ANALOG_ALPHA_KEY, self.plotter.set_analog_alpha) #initial update self.update_grid() @@ -615,3 +666,4 @@ class scope_window(wx.Panel, pubsub.pubsub): self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) #redraw current sample self.handle_samples() + diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 2882488e..204434ce 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -27,6 +27,7 @@ import common from gnuradio import gr from pubsub import pubsub from constants import * +import math class ac_couple_block(gr.hier_block2): """ @@ -75,8 +76,17 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): ac_couple=False, num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, + emulate_analog=False, + analog_alpha=None, **kwargs #do not end with a comma ): + #ensure analog alpha + if analog_alpha is None: + actual_frame_rate=float(frame_rate) + analog_cutoff_freq=0.5 # Hertz + #calculate alpha from wanted cutoff freq + analog_alpha = 1.0 - math.exp(-2.0*math.pi*analog_cutoff_freq/actual_frame_rate) + if not t_scale: t_scale = 10.0/sample_rate #init gr.hier_block2.__init__( @@ -127,6 +137,8 @@ class _scope_sink_base(gr.hier_block2, common.wxgui_hb): trigger_channel_key=TRIGGER_CHANNEL_KEY, decimation_key=DECIMATION_KEY, msg_key=MSG_KEY, + emulate_analog=emulate_analog, + analog_alpha=analog_alpha, ) common.register_access_methods(self, self.win) #connect @@ -167,10 +179,11 @@ class test_top_block (stdgui2.std_top_block): def __init__(self, frame, panel, vbox, argv): stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + default_input_rate = 1e6 if len(argv) > 1: - frame_decim = int(argv[1]) + input_rate = int(argv[1]) else: - frame_decim = 1 + input_rate = default_input_rate if len(argv) > 2: v_scale = float(argv[2]) # start up at this v_scale value @@ -180,14 +193,17 @@ class test_top_block (stdgui2.std_top_block): if len(argv) > 3: t_scale = float(argv[3]) # start up at this t_scale value else: - t_scale = .00003 # old behavior + t_scale = .00003*default_input_rate/input_rate # old behavior - print "frame decim %s v_scale %s t_scale %s" % (frame_decim,v_scale,t_scale) + print "input rate %s v_scale %s t_scale %s" % (input_rate,v_scale,t_scale) - input_rate = 1e6 # Generate a complex sinusoid - self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3) + ampl=1.0e3 + self.src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3*input_rate/default_input_rate, ampl) + self.noise =gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 11.1*25.1e3*input_rate/default_input_rate, ampl/10) + #self.noise =gr.noise_source_c(gr.GR_GAUSSIAN, ampl/10) + self.combine=gr.add_cc() # We add this throttle block so that this demo doesn't suck down # all the CPU available. You normally wouldn't use it... @@ -199,7 +215,9 @@ class test_top_block (stdgui2.std_top_block): # Ultimately this will be # self.connect("src0 throttle scope") - self.connect(self.src0, self.thr, scope) + self.connect(self.src0,(self.combine,0)) + self.connect(self.noise,(self.combine,1)) + self.connect(self.combine, self.thr, scope) def main (): app = stdgui2.stdapp (test_top_block, "O'Scope Test App") -- 2.30.2