From af6398eb6e3273c005312939fb6d965f8a50baed Mon Sep 17 00:00:00 2001 From: FOSSEE Date: Tue, 10 Mar 2015 17:11:34 +0530 Subject: Plot-sink added --- gr-input/grc/CMakeLists.txt | 6 + gr-input/grc/plot_sink.xml | 112 +++++++++++++++ gr-input/python/CMakeLists.txt | 16 +++ gr-input/python/matplotsink.py | 312 +++++++++++++++++++++++++++++++++++++++++ gr-input/python/plot_sink.py | 57 ++++++++ 5 files changed, 503 insertions(+) create mode 100755 gr-input/grc/plot_sink.xml create mode 100755 gr-input/python/matplotsink.py create mode 100755 gr-input/python/plot_sink.py diff --git a/gr-input/grc/CMakeLists.txt b/gr-input/grc/CMakeLists.txt index 1b604feb2..61b317f77 100644 --- a/gr-input/grc/CMakeLists.txt +++ b/gr-input/grc/CMakeLists.txt @@ -69,3 +69,9 @@ install(FILES ) +install(FILES + plot_sink.xml + DESTINATION ${GRC_BLOCKS_DIR} + COMPONENT "input_python" +) + diff --git a/gr-input/grc/plot_sink.xml b/gr-input/grc/plot_sink.xml new file mode 100755 index 000000000..07022ea07 --- /dev/null +++ b/gr-input/grc/plot_sink.xml @@ -0,0 +1,112 @@ + + + + Plot Sink + plot_sink + Single Board Heater System + import gnuradio.input.plot_sink + #set $parent = $notebook() and 'self.%s.GetPage(%s)'%$notebook() or 'self' +gnuradio.input.plot_sink.plot_sink_$(type.fcn)( + $(parent).GetWin(), + title=$title, + vlen=$vlen, + decim=$decim, + gsz=$gsz, + zoom=$zoom, +) +#if not $grid_pos() +$(parent).Add(self.$(id).win) +#else +$(parent).GridAdd(self.$(id).win, $(', '.join(map(str, $grid_pos())))) +#end if + set_decim($decim) + + Type + type + enum + + + + + + + + Title + title + Scope Plot + string + + + Decimation + decim + 1 + int + + + Vec Length + vlen + 1 + int + + + + Graph size + gsz + 50 + int + + + History Required + zoom + 0 + int + + + + Grid Position + grid_pos + + grid_pos + + + Notebook + notebook + + notebook + + + $vlen > 0 + + in + $type + $vlen + + +Read samples from the input stream and \ +plot one in every decimation samples to the plot sink. + + diff --git a/gr-input/python/CMakeLists.txt b/gr-input/python/CMakeLists.txt index 77ac986ac..7c73fc59f 100644 --- a/gr-input/python/CMakeLists.txt +++ b/gr-input/python/CMakeLists.txt @@ -92,6 +92,20 @@ GR_PYTHON_INSTALL( COMPONENT "input_python" ) +GR_PYTHON_INSTALL( + FILES + plot_sink.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/input + COMPONENT "input_python" +) + +GR_PYTHON_INSTALL( + FILES + matplotsink.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/input + COMPONENT "input_python" +) + ######################################################################## # Handle the unit tests @@ -104,6 +118,8 @@ list(APPEND GR_TEST_PYTHON_DIRS list(APPEND GR_TEST_TARGET_DEPS gnuradio-input) include(GrTest) + + file(GLOB py_qa_test_files "qa_*.py") foreach(py_qa_test_file ${py_qa_test_files}) get_filename_component(py_qa_test_name ${py_qa_test_file} NAME_WE) diff --git a/gr-input/python/matplotsink.py b/gr-input/python/matplotsink.py new file mode 100755 index 000000000..105009b4d --- /dev/null +++ b/gr-input/python/matplotsink.py @@ -0,0 +1,312 @@ +""" +$$ + +Modified for working as a GNU Radio block +Rakesh Peter (rakesh.peter@gmail.com) +Last modified: 07.05.2010 + +$$ + +This demo demonstrates how to draw a dynamic mpl (matplotlib) +plot in a wxPython application. + +It allows "live" plotting as well as manual zooming to specific +regions. + +Both X and Y axes allow "auto" or "manual" settings. For Y, auto +mode sets the scaling of the graph to see all the data points. +For X, auto mode makes the graph "follow" the data. Set it X min +to manual 0 to always see the whole data from the beginning. + +Note: press Enter in the 'manual' text box to make a new value +affect the plot. + +Eli Bendersky (eliben@gmail.com) +License: this code is in the public domain +Last modified: 31.07.2008 +""" + +import os +import pprint +import random +import sys +import wx +import gnuradio.grc.gui +from gnuradio.grc.gui import Actions,ActionHandler +# The recommended way to use wx with mpl is with the WXAgg +# backend. +# +import matplotlib +import matplotlib.pyplot as plt +import matplotlib.animation as animation +#matplotlib.use('WXAgg') +from matplotlib.figure import Figure +from matplotlib.backends.backend_wxagg import \ + FigureCanvasWxAgg as FigCanvas, \ + NavigationToolbar2WxAgg as NavigationToolbar +import numpy as np +import pylab + + +class DataGen(object): + """ A silly class that generates pseudo-random data for + display in the plot. + """ + def __init__(self, init=50): + self.data = self.init = init + + def next(self): + self._recalc_data() + #return self.data + return [0.0,1.1,2.2,3.3] + + def _recalc_data(self): + delta = random.uniform(-0.5, 0.5) + r = random.random() + + if r > 0.9: + self.data += delta * 15 + elif r > 0.8: + # attraction to the initial value + delta += (0.5 if self.init > self.data else -0.5) + self.data += delta + else: + self.data += delta + + + +class matplotsink(wx.Panel): + + def __init__(self, parent, title, queue,gsz,zoom): + wx.Panel.__init__(self, parent, wx.SIMPLE_BORDER) + + self.gsz = gsz + self.parent = parent + self.title = title + self.q = queue + self.zoom=zoom + self.paused = False + +# self.create_menu() +# self.create_status_bar() + self.create_main_panel() + + + def create_menu(self): + self.menubar = wx.MenuBar() + + menu_file = wx.Menu() + m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file") + self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt) + menu_file.AppendSeparator() + m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit") + self.Bind(wx.EVT_MENU, self.on_exit, m_exit) + self.menubar.Append(menu_file, "&File") + self.SetMenuBar(self.menubar) + + + def create_main_panel(self): + self.panel = self + + self.init_plot() + self.canvas = FigCanvas(self.panel, -1, self.fig) + self.scroll_range = 400 + self.canvas.SetScrollbar(wx.HORIZONTAL,0,5,self.scroll_range) + self.canvas.Bind(wx.EVT_SCROLLWIN,self.OnScrollEvt) + + + self.pause_button = wx.Button(self.panel, -1, "Pause") + self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button) + self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button) + + self.cb_grid = wx.CheckBox(self.panel, -1, + "Show Grid", + style=wx.ALIGN_RIGHT) + self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid) + self.cb_grid.SetValue(True) + + self.cb_xlab = wx.CheckBox(self.panel, -1, + "Show X labels", + style=wx.ALIGN_RIGHT) + self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab) + self.cb_xlab.SetValue(True) + + self.hbox1 = wx.BoxSizer(wx.HORIZONTAL) + self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) + self.hbox1.AddSpacer(20) + self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) + self.hbox1.AddSpacer(10) + self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) + + + self.vbox = wx.BoxSizer(wx.VERTICAL) + self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW) + self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP) + + self.panel.SetSizer(self.vbox) + self.vbox.Fit(self) + self.ani=animation.FuncAnimation(self.fig,self.draw_plot,interval=100) + + def OnScrollEvt(self,event): + self.i_start = event.GetPosition() + self.i_end = self.i_window + event.GetPosition() + self.draw_plot(0) + + def create_status_bar(self): + self.statusbar = self.CreateStatusBar() + + def draw_test(self,event): + self.xar=np.arange(len(self.q.queue)) + self.yar=np.array(self.q.queue) + self.axes.plot(self.xar,self.yar) + + def init_plot(self): + self.dpi = 100 + self.fig = Figure((3.0, 3.0), dpi=self.dpi) + self.fig.set_size_inches(7.0,4.0) + self.fig.set_dpi(self.dpi) + + self.axes = self.fig.add_subplot(111) + self.axes.set_axis_bgcolor('black') + self.axes.set_title(self.title, size=12) + + pylab.setp(self.axes.get_xticklabels(), fontsize=8) + pylab.setp(self.axes.get_yticklabels(), fontsize=8) + self.i_window = self.gsz + self.i_start = 0 + self.i_end = self.i_start + self.i_window + # plot the data as a line series, and save the reference + # to the plotted line series + # + self.plot_data = self.axes.plot( + [], + linewidth=1, + color=(1, 1, 0), + )[0] + + + def draw_plot(self,event): + """ Redraws the plot + """ + if len(list(self.q.queue))>1 and not self.paused: + + if self.zoom: + xmax = len(list(self.q.queue)) if len(list(self.q.queue)) > 50 else 50 + + xmin = xmax - 50 + # for ymin and ymax, find the minimal and maximal values + # in the data set and add a mininal margin. + # + # note that it's easy to change this scheme to the + # minimal/maximal value in the current display, and not + # the whole data set. + # + ymin = round(min(list(self.q.queue)), 0) - 1 + + ymax = round(max(list(self.q.queue)), 0) + 1 + + self.axes.set_xbound(lower=xmin, upper=xmax) + self.axes.set_ybound(lower=ymin, upper=ymax) + + # anecdote: axes.grid assumes b=True if any other flag is + # given even if b is set to False. + # so just passing the flag into the first statement won't + # work. + # + if self.cb_grid.IsChecked(): + self.axes.grid(True, color='gray') + else: + self.axes.grid(False) + + # Using setp here is convenient, because get_xticklabels + # returns a list over which one needs to explicitly + # iterate, and setp already handles this. + # + pylab.setp(self.axes.get_xticklabels(), + visible=self.cb_xlab.IsChecked()) + + + self.plot_data.set_xdata(np.arange(len(list(self.q.queue)))) + self.plot_data.set_ydata(np.array(list(self.q.queue))) + self.canvas.draw() + + else: + if self.cb_grid.IsChecked(): + self.axes.grid(True, color='gray') + else: + self.axes.grid(False) + + # Using setp here is convenient, because get_xticklabels + # returns a list over which one needs to explicitly + # iterate, and setp already handles this. + + pylab.setp(self.axes.get_xticklabels(), + visible=self.cb_xlab.IsChecked()) + + self.plot_data.set_xdata(np.arange(len(list(self.q.queue)))[self.i_start:self.i_end]) + self.plot_data.set_ydata(np.array(list(self.q.queue))[self.i_start:self.i_end]) + self.axes.set_xlim(min(np.arange(len(list(self.q.queue)))[self.i_start:self.i_end]),max(np.arange(len(list(self.q.queue)))[self.i_start:self.i_end])) + # if self.zoom: + self.axes.set_ylim(min(np.array(list(self.q.queue))),max(np.array(list(self.q.queue)))) + + self.canvas.draw() + + + + def on_pause_button(self, event): + self.paused = not self.paused + + def on_update_pause_button(self, event): + label = "Resume" if self.paused else "Pause" + self.pause_button.SetLabel(label) + + def on_cb_grid(self, event): + self.draw_plot(0) + + def on_cb_xlab(self, event): + self.draw_plot(0) + + def on_save_plot(self, event): + file_choices = "PNG (*.png)|*.png" + + dlg = wx.FileDialog( + self, + message="Save plot as...", + defaultDir=os.getcwd(), + defaultFile="plot.png", + wildcard=file_choices, + style=wx.SAVE) + + if dlg.ShowModal() == wx.ID_OK: + path = dlg.GetPath() + self.canvas.print_figure(path, dpi=self.dpi) + self.flash_status_message("Saved to %s" % path) + + def on_redraw_timer(self, event): + # if paused do not add data, but still redraw the plot + # (to respond to scale modifications, grid change, etc.) + # + if not self.paused: + self.data += self.datagen.next() + self.draw_plot(0) + + + def on_exit(self, event): + self.Destroy() + + def flash_status_message(self, msg, flash_len_ms=1500): + self.statusbar.SetStatusText(msg) + self.timeroff = wx.Timer(self) + self.Bind( + wx.EVT_TIMER, + self.on_flash_status_off, + self.timeroff) + self.timeroff.Start(flash_len_ms, oneShot=True) + + def on_flash_status_off(self, event): + self.statusbar.SetStatusText('') + + + + + diff --git a/gr-input/python/plot_sink.py b/gr-input/python/plot_sink.py new file mode 100755 index 000000000..1c1def3fc --- /dev/null +++ b/gr-input/python/plot_sink.py @@ -0,0 +1,57 @@ +# Hacked from blks2/variable_sink.py +# Requires modified Matplotsink code + +from gnuradio import gr +import threading +import numpy +import matplotsink +import Queue + +class _plot_sink_base(gr.hier_block2, threading.Thread): + """ + The thread polls the message queue for values and writes to matplotsink callback + """ + + def __init__(self, parent, title, vlen, decim,gsz,zoom): + self._vlen = vlen + self._parent = parent + self._title = title + print "Initing block: %s" % title + + self.plotQueue = Queue.Queue() + self.win = matplotsink.matplotsink(parent,title, self.plotQueue,gsz,zoom) + + self._item_size = self._size*self._vlen + #init hier block + gr.hier_block2.__init__( + self, 'plot_sink', + gr.io_signature(1, 1, self._item_size), + gr.io_signature(0, 0, 0), + ) + #create blocks + self._msgq = gr.msg_queue(2) + message_sink = gr.message_sink(self._item_size, self._msgq, False) + #connect + self.connect(self, message_sink) + #setup thread + threading.Thread.__init__(self) + self.setDaemon(True) + self.start() + + def set_decim(self, decim): self._decimator.set_n(decim) + + def run(self): + while True: #truncate to item size, convert to array, callback + msg = self._msgq.delete_head().to_string()[-self._item_size:] + arr = map(self._cast, numpy.fromstring(msg, self._numpy)) + print "Sending value:" , arr + self.plotQueue.put(self._vlen > 1 and arr or arr[0]) + + def print_callback(self, array): + print array + +class plot_sink_b(_plot_sink_base): _numpy, _size, _cast = numpy.int8, gr.sizeof_char, int +class plot_sink_s(_plot_sink_base): _numpy, _size, _cast = numpy.int16, gr.sizeof_short, int +class plot_sink_i(_plot_sink_base): _numpy, _size, _cast = numpy.int32, gr.sizeof_int, int +class plot_sink_f(_plot_sink_base): _numpy, _size, _cast = numpy.float32, gr.sizeof_float, float +class plot_sink_c(_plot_sink_base): _numpy, _size, _cast = numpy.complex64, gr.sizeof_gr_complex, complex -- cgit