class VelocityPanel(wx.Panel): def _init_ctrls(self, prnt): # generated method, don't edit wx.Panel.__init__(self, id=wxID_RIGHTPANEL, name='VelocityPanel', parent=prnt, pos=wx.Point(8, 416), size=wx.Size(1000, 250), style=wx.TAB_TRAVERSAL) self.SetClientSize(wx.Size(1000, 250)) self.SetBackgroundColour(wx.Colour(0, 0, 255)) self.Bind(wx.EVT_PAINT, self.OnPolarPanelPaint) def __init__(self, parent, id, pos, size, style, name): self._init_ctrls(parent) self.Times = list() self.VelocitiesX = list() self.VelocitiesY = list() self.Velocities = list() self._initFigure() ##now finally create the actual plot self.x_axes = self.fig.add_subplot(131) ## the x velocity self.y_axes = self.fig.add_subplot(132) ## the y velocity self.m_axes = self.fig.add_subplot(133) ## the magnitude of the velocity #self.axes = self.fig.add_axes((0.1,0.1,0.85,0.8)) ##left,bottom,width,height self.x_plot = self.x_axes.plot([0,1],[0,1],'b', animated=True) self.y_plot = self.y_axes.plot([0,1],[0,1],'b', animated=True) self.m_plot = self.m_axes.plot([0,1],[0,1],'r', animated=True) self.x_axes.set_title('X', fontsize='10') self.y_axes.set_title('Y', fontsize='10') self.m_axes.set_title('M', fontsize='10') self.x_axes.set_ylabel('Velocity (cm/s)', fontsize='10') #self.axes.set_ylabel('x (cm)', fontsize='10') ##plot formatting self._formatAxes(self.x_axes) self._formatAxes(self.y_axes) self._formatAxes(self.m_axes) #self.axes.set_title('Velocity', fontsize='10') self.canvas.draw() self.canvas.gui_repaint() # save the clean slate background -- everything but the animated line # is drawn and saved in the pixel buffer background self.background = self.canvas.copy_from_bbox(self.fig.bbox) def _initFigure(self): ##Create a matplotlib figure/canvas in this panel ##the background colour will be the same as the panel ##the size will also be the same as the panel ##calculate size in inches pixels_width,pixels_height = self.GetSizeTuple() self.dpi = 96.0 inches_width = pixels_width/self.dpi inches_height = pixels_height/self.dpi ##calculate colour in RGB 0 to 1 colour = self.GetBackgroundColour() self.fig = Figure(figsize=(inches_width,inches_height), dpi = self.dpi\ ,facecolor=(colour.Red()/255.0, colour.Green()/255.0, colour.Blue()/255.0)\ ,edgecolor=(colour.Red()/255.0, colour.Green()/255.0, colour.Blue()/255.0)) self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.fig.subplots_adjust(left=0.05,right=0.95,wspace=0.08) ##now put everything in a sizer sizer = wx.BoxSizer(wx.VERTICAL) # This way of adding to sizer allows resizing sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW) self.SetSizer(sizer) self.Fit() def _formatAxes(self, axes): """ """ ticks = numpy.arange(-25, 25+5, 5) labels = [str(tick) for tick in ticks] # velocity axes.set_yticks(ticks) axes.set_yticklabels(labels, fontsize=8) ticks = numpy.arange(0, 10+1.0, 1.0) # time labels = [str(tick) for tick in ticks] axes.set_xticks(ticks) axes.set_xticklabels(labels,fontsize=8) #if axes == self.m_axes: # self.axes.set_xlabel('time (s)', fontsize='10') #self.axes.set_ylabel(' (mm)', fontsize='10') def updateData(self, velx, vely, vel): """updateData. Updates the data that this panel is displaying. """ self.VelocitiesX.append(velx) self.VelocitiesY.append(vely) self.Velocities.append(vel) timenow = time.time() self.Times.append(timenow) if timenow - self.Times[0] > 10: del self.Times[0] del self.VelocitiesX[0] del self.VelocitiesY[0] del self.Velocities[0] self.x_plot[0].set_data(numpy.array(self.Times) - self.Times[0], self.VelocitiesX) self.y_plot[0].set_data(numpy.array(self.Times) - self.Times[0], self.VelocitiesY) self.m_plot[0].set_data(numpy.array(self.Times) - self.Times[0], self.Velocities) # restore the clean slate background self.canvas.restore_region(self.background) # just draw the animated artist self.fig.draw_artist(self.x_plot[0]) self.fig.draw_artist(self.y_plot[0]) self.fig.draw_artist(self.m_plot[0]) # just redraw the axes rectangle self.canvas.blit(self.fig.bbox) def OnPolarPanelPaint(self, event): pass
class VelocityPanel(wx.Panel): def _init_ctrls(self, prnt): # generated method, don't edit wx.Panel.__init__(self, id=wxID_RIGHTPANEL, name='VelocityPanel', parent=prnt, pos=wx.Point(8, 416), size=wx.Size(1000, 250), style=wx.TAB_TRAVERSAL) self.SetClientSize(wx.Size(1000, 250)) self.SetBackgroundColour(wx.Colour(0, 0, 255)) self.Bind(wx.EVT_PAINT, self.OnPolarPanelPaint) def __init__(self, parent, id, pos, size, style, name): self._init_ctrls(parent) self.Times = list() self.VelocitiesX = list() self.VelocitiesY = list() self.Velocities = list() self._initFigure() ##now finally create the actual plot self.x_axes = self.fig.add_subplot(131) ## the x velocity self.y_axes = self.fig.add_subplot(132) ## the y velocity self.m_axes = self.fig.add_subplot( 133) ## the magnitude of the velocity #self.axes = self.fig.add_axes((0.1,0.1,0.85,0.8)) ##left,bottom,width,height self.x_plot = self.x_axes.plot([0, 1], [0, 1], 'b', animated=True) self.y_plot = self.y_axes.plot([0, 1], [0, 1], 'b', animated=True) self.m_plot = self.m_axes.plot([0, 1], [0, 1], 'r', animated=True) self.x_axes.set_title('X', fontsize='10') self.y_axes.set_title('Y', fontsize='10') self.m_axes.set_title('M', fontsize='10') self.x_axes.set_ylabel('Velocity (cm/s)', fontsize='10') #self.axes.set_ylabel('x (cm)', fontsize='10') ##plot formatting self._formatAxes(self.x_axes) self._formatAxes(self.y_axes) self._formatAxes(self.m_axes) #self.axes.set_title('Velocity', fontsize='10') self.canvas.draw() self.canvas.gui_repaint() # save the clean slate background -- everything but the animated line # is drawn and saved in the pixel buffer background self.background = self.canvas.copy_from_bbox(self.fig.bbox) def _initFigure(self): ##Create a matplotlib figure/canvas in this panel ##the background colour will be the same as the panel ##the size will also be the same as the panel ##calculate size in inches pixels_width, pixels_height = self.GetSizeTuple() self.dpi = 96.0 inches_width = pixels_width / self.dpi inches_height = pixels_height / self.dpi ##calculate colour in RGB 0 to 1 colour = self.GetBackgroundColour() self.fig = Figure(figsize=(inches_width,inches_height), dpi = self.dpi\ ,facecolor=(colour.Red()/255.0, colour.Green()/255.0, colour.Blue()/255.0)\ ,edgecolor=(colour.Red()/255.0, colour.Green()/255.0, colour.Blue()/255.0)) self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.fig.subplots_adjust(left=0.05, right=0.95, wspace=0.08) ##now put everything in a sizer sizer = wx.BoxSizer(wx.VERTICAL) # This way of adding to sizer allows resizing sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.SetSizer(sizer) self.Fit() def _formatAxes(self, axes): """ """ ticks = numpy.arange(-25, 25 + 5, 5) labels = [str(tick) for tick in ticks] # velocity axes.set_yticks(ticks) axes.set_yticklabels(labels, fontsize=8) ticks = numpy.arange(0, 10 + 1.0, 1.0) # time labels = [str(tick) for tick in ticks] axes.set_xticks(ticks) axes.set_xticklabels(labels, fontsize=8) #if axes == self.m_axes: # self.axes.set_xlabel('time (s)', fontsize='10') #self.axes.set_ylabel(' (mm)', fontsize='10') def updateData(self, velx, vely, vel): """updateData. Updates the data that this panel is displaying. """ self.VelocitiesX.append(velx) self.VelocitiesY.append(vely) self.Velocities.append(vel) timenow = time.time() self.Times.append(timenow) if timenow - self.Times[0] > 10: del self.Times[0] del self.VelocitiesX[0] del self.VelocitiesY[0] del self.Velocities[0] self.x_plot[0].set_data( numpy.array(self.Times) - self.Times[0], self.VelocitiesX) self.y_plot[0].set_data( numpy.array(self.Times) - self.Times[0], self.VelocitiesY) self.m_plot[0].set_data( numpy.array(self.Times) - self.Times[0], self.Velocities) # restore the clean slate background self.canvas.restore_region(self.background) # just draw the animated artist self.fig.draw_artist(self.x_plot[0]) self.fig.draw_artist(self.y_plot[0]) self.fig.draw_artist(self.m_plot[0]) # just redraw the axes rectangle self.canvas.blit(self.fig.bbox) def OnPolarPanelPaint(self, event): pass
class AppForm(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowTitle('Serial Port Real Time Plotter') self.ser = None self.create_main_frame() self.create_status_bar() self.serialport_connected = False self.textbox.setText('0.0') self.xdata = list(range(-N, 0, 1)) self.ydata_ch1 = [0 for i in range(N)] self.ydata_ch2 = [0 for i in range(N)] self.ydata_ch3 = [0 for i in range(N)] self.ydata_ch4 = [0 for i in range(N)] self.ydata_ch5 = [0 for i in range(N)] self.ydata_ch6 = [0 for i in range(N)] self.ydata_ch1_cont = [] self.ydata_ch2_cont = [] self.ydata_ch3_cont = [] self.ydata_ch4_cont = [] self.ydata_ch5_cont = [] self.ydata_ch6_cont = [] os.chdir("./outputs") self.setFileName() self.ydata_raw = [] self.axes.clear() self.axes.grid(True) self.axes.set_xlim([-N, 0]) self.axes.set_ylim([0, 1.25]) self.plot_refs = self.axes.plot(self.xdata, self.ydata_ch1, 'r', self.xdata, self.ydata_ch2, 'g', self.xdata, self.ydata_ch3, 'b', self.xdata, self.ydata_ch4, 'y', self.xdata, self.ydata_ch5, 'k', self.xdata, self.ydata_ch6, 'r') #self.axes.legend(['Channel 1','Channel 2','Channel 3','Channel 4'],loc='upper center', bbox_to_anchor=(0.5, -0.05), ncol=4) self.canvas.draw() self.background = self.fig.canvas.copy_from_bbox(self.axes.bbox) self.timer = QTimer() self.timer.setInterval(10) self.timer.timeout.connect(self.update_plot) self.timer_send = QTimer() self.timer_send.setInterval(100) self.timer_send.timeout.connect(self.send_data) def setFileName(self): self.files = [] for file in glob.glob("*.mat"): self.files.append(file) i = 0 while True: self.filename = "out_" + time.strftime( "%Y-%m-%d_%H-%M-%S") + "_" + str(i) + ".mat" if self.filename not in self.files: break i += 1 def closeEvent(self, event): reply = QMessageBox.question( self, 'Window Close', 'Are you sure you want to close the window?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: if self.ser is not None: self.ser.close() event.accept() else: event.ignore() def create_main_frame(self): self.main_frame = QWidget() self.dpi = 100 self.fig = Figure((10.0, 6.0), dpi=self.dpi, tight_layout=True) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self.main_frame) self.axes = self.fig.add_subplot(111) #self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame) self.textbox = QLineEdit() self.textbox.setMinimumWidth(100) self.set_button = QPushButton("&Set") self.set_button.clicked.connect(self.set_setpoint) self.show_ch1 = QCheckBox("Show &Channel1") self.show_ch1.setChecked(True) self.show_ch2 = QCheckBox("Show &Channel2") self.show_ch2.setChecked(True) self.show_ch3 = QCheckBox("Show &Channel3") self.show_ch3.setChecked(True) self.show_ch4 = QCheckBox("Show &Channel4") self.show_ch4.setChecked(True) self.show_ch5 = QCheckBox("Show &Channel4") self.show_ch5.setChecked(True) self.show_ch6 = QCheckBox("Show &Channel4") self.show_ch6.setChecked(True) slider_label = QLabel('Setpoint:') self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, 65535) self.slider.setValue(0) self.slider.setTracking(True) self.slider.setTickPosition(QSlider.TicksBothSides) hbox = QHBoxLayout() for w in [self.textbox, self.set_button, slider_label, self.slider]: hbox.addWidget(w) hbox.setAlignment(w, Qt.AlignVCenter) hbox2 = QHBoxLayout() for w in [self.show_ch1, self.show_ch2, self.show_ch3, self.show_ch4]: hbox2.addWidget(w) hbox2.setAlignment(w, Qt.AlignVCenter) vbox = QVBoxLayout() vbox.addWidget(self.canvas) #vbox.addWidget(self.mpl_toolbar) vbox.addLayout(hbox2) vbox.addLayout(hbox) hbox3 = QHBoxLayout() self.save_mat_button = QPushButton("&Save .mat") self.save_mat_button.clicked.connect(self.save_mat) self.start_button = QPushButton("&Start") self.start_button.clicked.connect(self.connect) hbox3.addWidget(self.start_button) hbox3.addWidget(self.save_mat_button) hbox3.setAlignment(w, Qt.AlignVCenter) vbox.addLayout(hbox3) hbox4 = QHBoxLayout() self.combo_box_ports = QComboBox() self.button_update_ports = QPushButton("&Update Ports") self.button_update_ports.clicked.connect(self.update_ports) hbox4.addWidget(self.combo_box_ports) hbox4.addWidget(self.button_update_ports) vbox.addLayout(hbox4) self.main_frame.setLayout(vbox) self.setCentralWidget(self.main_frame) self.info_message('Press Start to start plotting') def update_ports(self): self.info_message('Updating Ports...') ports = serial.tools.list_ports.comports() self.combo_box_ports.clear() for port, desc, hwid in sorted(ports): self.combo_box_ports.addItem(str(port) + ": " + str(desc)) self.success_message('Updated Ports') def set_setpoint(self): try: self.slider.setValue(int(float(self.textbox.text()) * 65565)) except: self.error_message('Parsing error for set value') def create_status_bar(self): self.status_text = QLabel("") self.statusBar().addWidget(self.status_text, 1) def add_actions(self, target, actions): for action in actions: if action is None: target.addSeparator() else: target.addAction(action) def create_action(self, text, slot=None, shortcut=None, icon=None, tip=None, checkable=False): action = QAction(text, self) if icon is not None: action.setIcon(QIcon(":/%s.png" % icon)) if shortcut is not None: action.setShortcut(shortcut) if tip is not None: action.setToolTip(tip) action.setStatusTip(tip) if slot is not None: action.triggered.connect(slot) if checkable: action.setCheckable(True) return action def connect(self): if 'COM' in str(self.combo_box_ports.currentText()): com_port = str(self.combo_box_ports.currentText()).split(':')[0] else: self.error_message('Select COM Port') return if self.serialport_connected is False: try: self.ser = serial.Serial(com_port, 115200, timeout=0.2) if self.ser.isOpen() == False: self.ser.open() self.success_message('Connected to serial port') self.start_button.setText('&Stop') self.serialport_connected = True self.ydata_ch1 = [-1 for i in range(N)] self.ydata_ch2 = [-1 for i in range(N)] self.ydata_ch3 = [-1 for i in range(N)] self.ydata_ch4 = [-1 for i in range(N)] self.ydata_ch5 = [-1 for i in range(N)] self.ydata_ch6 = [-1 for i in range(N)] self.timer.start() self.timer_send.start() except: self.error_message('Connecting to serial port') self.setFileName() else: try: if self.ser.isOpen(): self.ser.close() self.info_message('Disconnected from serial port') self.start_button.setText('&Start') self.timer.stop() self.timer_send.stop() self.serialport_connected = False except: self.error_message('Disconnecting from serial port') def save_mat(self): try: mat_ch1 = np.array(self.ydata_ch1_cont) mat_ch2 = np.array(self.ydata_ch2_cont) mat_ch3 = np.array(self.ydata_ch3_cont) mat_ch4 = np.array(self.ydata_ch4_cont) mat_ch5 = np.array(self.ydata_ch5_cont) mat_ch6 = np.array(self.ydata_ch6_cont) mat_ch3_raw = np.array(self.ydata_raw) print(os.getcwd() + '\\' + self.filename) scipy.io.savemat( os.getcwd() + '\\' + self.filename, { 'channel1': mat_ch1, 'channel2': mat_ch2, 'channel3': mat_ch3, 'channel4': mat_ch4, 'channel5': mat_ch5, 'channel6': mat_ch6 }) self.success_message('Saved data to out.mat') except Exception as e: self.error_message('Saving Data ' + str(e)) def error_message(self, message): self.statusBar().showMessage('ERROR: ' + str(message)) self.statusBar().setStyleSheet( 'QStatusBar { background-color : red; }') def info_message(self, message): self.statusBar().showMessage('INFO: ' + str(message)) self.statusBar().setStyleSheet( 'QStatusBar { background-color : yellow; }') def success_message(self, message): self.statusBar().showMessage('SUCCESS: ' + str(message)) self.statusBar().setStyleSheet( 'QStatusBar { background-color : green; }') def update_plot(self): #ser.flushInput() #print('new plot') if self.ser.isOpen() == False: return rawData = self.ser.read(15) if len(rawData) != 15: return # print(time.time()) start = rawData[0] channel1 = int.from_bytes([rawData[1], rawData[2]], byteorder='little', signed=False) * 1.25 / 65536 channel2 = int.from_bytes([rawData[3], rawData[4]], byteorder='little', signed=False) * 1.25 / 65536 channel3 = int.from_bytes([rawData[5], rawData[6]], byteorder='little', signed=False) * 1.25 / 65536 channel4 = int.from_bytes([rawData[7], rawData[8]], byteorder='little', signed=False) * 150 / 65536 channel5 = int.from_bytes([rawData[9], rawData[10]], byteorder='little', signed=False) * 150 / 65536 channel6 = int.from_bytes([rawData[11], rawData[12]], byteorder='little', signed=False) * 150 / 65536 channel3_raw = int.from_bytes([rawData[5], rawData[6]], byteorder='little', signed=False) self.ydata_raw.append(channel3_raw) end = rawData[13] if start != 2 and end != 3: print('ERROR: Wrong frame') return print( int.from_bytes( [rawData[1], rawData[2]], byteorder='little', signed=False) * 1.25 / 65536) print( int.from_bytes( [rawData[3], rawData[4]], byteorder='little', signed=False) * 1.25 / 65536) print( int.from_bytes( [rawData[5], rawData[6]], byteorder='little', signed=False) * 1.25 / 65536) print( int.from_bytes( [rawData[7], rawData[8]], byteorder='little', signed=False) * 150 / 65536) print( int.from_bytes( [rawData[9], rawData[10]], byteorder='little', signed=False) * 150 / 65536) print( int.from_bytes( [rawData[11], rawData[12]], byteorder='little', signed=False) * 150 / 65536) # print(time.time()) print('----') # Drop off the first y element, append a new one. self.ydata_ch1 = self.ydata_ch1[1:] + [channel1] self.ydata_ch2 = self.ydata_ch2[1:] + [channel2] self.ydata_ch3 = self.ydata_ch3[1:] + [channel3] self.ydata_ch4 = self.ydata_ch4[1:] + [channel4] self.ydata_ch5 = self.ydata_ch5[1:] + [channel5] self.ydata_ch6 = self.ydata_ch6[1:] + [channel6] self.ydata_ch1_cont = self.ydata_ch1_cont + [channel1] self.ydata_ch2_cont = self.ydata_ch2_cont + [channel2] self.ydata_ch3_cont = self.ydata_ch3_cont + [channel3] self.ydata_ch4_cont = self.ydata_ch4_cont + [channel4] self.ydata_ch5_cont = self.ydata_ch5_cont + [channel5] self.ydata_ch6_cont = self.ydata_ch6_cont + [channel6] #print(time.time()) if (self.show_ch1.isChecked()): self.plot_refs[0].set_visible(True) self.plot_refs[0].set_ydata(self.ydata_ch1) else: self.plot_refs[0].set_visible(False) if (self.show_ch2.isChecked()): self.plot_refs[1].set_visible(True) self.plot_refs[1].set_ydata(self.ydata_ch2) else: self.plot_refs[1].set_visible(False) if (self.show_ch3.isChecked()): self.plot_refs[2].set_visible(True) self.plot_refs[2].set_ydata(self.ydata_ch3) else: self.plot_refs[2].set_visible(False) if (self.show_ch4.isChecked()): self.plot_refs[3].set_visible(True) self.plot_refs[3].set_ydata(self.ydata_ch4) else: self.plot_refs[3].set_visible(False) if (self.show_ch5.isChecked()): self.plot_refs[4].set_visible(True) self.plot_refs[4].set_ydata(self.ydata_ch5) else: self.plot_refs[4].set_visible(False) if (self.show_ch6.isChecked()): self.plot_refs[5].set_visible(True) self.plot_refs[5].set_ydata(self.ydata_ch6) else: self.plot_refs[5].set_visible(False) self.fig.canvas.restore_region(self.background) self.fig.draw_artist(self.plot_refs[0]) self.fig.draw_artist(self.plot_refs[1]) self.fig.draw_artist(self.plot_refs[2]) self.fig.draw_artist(self.plot_refs[3]) self.fig.draw_artist(self.plot_refs[4]) self.fig.draw_artist(self.plot_refs[5]) self.fig.canvas.update() #self.fig.canvas.blit() self.fig.canvas.flush_events() #print(time.time()) #print('-------') #self.ser.reset_input_buffer() def send_data(self): if self.ser.isOpen() == False: return data = bytearray(ctypes.c_uint16(self.slider.value())) send_array = bytearray(ctypes.c_uint8(2)) + data + bytearray( ctypes.c_uint8(3)) cs = ctypes.c_uint8(0) for byte in data: cs = ctypes.c_uint8(cs.value + byte) send_array = send_array + cs self.ser.write(send_array)
class wxpygui_frame(wx.Frame): """The main gui frame.""" def __init__(self, tb): wx.Frame.__init__(self, parent=None, id=-1, title="gr-analyzer") self.tb = tb self.min_power = -120 # dBm self.max_power = 0 # dBm self.init_mpl_canvas() self.x = None # set by configure_mpl_plot # Setup a threshold level at None self.threshold = threshold.threshold(self, None) # Init markers (visible=False) self.mkr1 = marker.marker(self, 1, '#00FF00', 'd') # thin green diamond self.mkr2 = marker.marker(self, 2, '#00FF00', 'd') # thin green diamond # init control boxes self.gain_ctrls = gain.ctrls(self) self.threshold_ctrls = threshold.ctrls(self) self.mkr1_ctrls = marker.mkr1_ctrls(self) self.mkr2_ctrls = marker.mkr2_ctrls(self) self.res_ctrls = resolution.ctrls(self) self.windowfn_ctrls = window.ctrls(self) self.lo_offset_ctrls = lotuning.ctrls(self) self.nframes_ctrls = nframes.ctrls(self) self.tune_delay_ctrls = tune_delay.ctrls(self) self.frequency_ctrls = frequency.ctrls(self) self.span_ctrls = span.ctrls(self) self.trigger_ctrls = trigger.ctrls(self) self.power_ctrls = power.ctrls(self) self.export_ctrls = export.ctrls(self) self.detector_ctrls = detector.ctrls(self) self.scale_ctrls = scale.ctrls(self) self.set_layout() self.logger = logging.getLogger('gr-analyzer.wxpygui_frame') # gui event handlers self.Bind(wx.EVT_CLOSE, self.close) self.Bind(wx.EVT_IDLE, self.idle_notifier) self.canvas.mpl_connect('button_press_event', self.on_mousedown) self.canvas.mpl_connect('button_release_event', self.on_mouseup) self.plot_background = None # Used to peak search within range self.span = None # the actual matplotlib patch self.span_left = None # left bound x coordinate self.span_right = None # right bound x coordinate self.last_click_evt = None self.closed = False # Used to increment file numbers self.fft_data_export_counter = 0 self.time_data_export_counter = 0 #################### # GUI Sizers/Layout #################### def set_layout(self): """Setup frame layout and sizers""" # front panel to hold plot and control stack side-by-side frontpanel = wx.BoxSizer(wx.HORIZONTAL) # control stack to hold control clusters vertically controlstack = wx.BoxSizer(wx.VERTICAL) # first cluster - usrp state usrpstate_outline = wx.StaticBox(self, wx.ID_ANY, "USRP State") usrpstate_cluster = wx.StaticBoxSizer(usrpstate_outline, wx.HORIZONTAL) usrpstate_row1 = wx.BoxSizer(wx.HORIZONTAL) usrpstate_row1.Add(self.trigger_ctrls.layout, flag=wx.ALL, border=5) usrpstate_row1.Add(self.detector_ctrls.layout, flag=wx.ALL, border=5) usrpstate_row1.Add(self.gain_ctrls.layout, flag=wx.ALL, border=5) usrpstate_row1.Add(self.lo_offset_ctrls.layout, flag=wx.ALL, border=5) usrpstate_row2 = wx.BoxSizer(wx.HORIZONTAL) usrpstate_row2.Add( self.frequency_ctrls.layout, proportion=1, flag=wx.ALL, #|wx.EXPAND, border=5) usrpstate_row2.Add( self.span_ctrls.layout, proportion=1, flag=wx.ALL, #|wx.EXPAND, border=5) usrpstate_row2.Add( self.scale_ctrls.layout, proportion=1, flag=wx.ALL, #|wx.EXPAND, border=5) usrpstate_col1 = wx.BoxSizer(wx.VERTICAL) usrpstate_col1.Add(usrpstate_row1) usrpstate_col1.Add(usrpstate_row2, flag=wx.EXPAND) usrpstate_col2 = wx.BoxSizer(wx.VERTICAL) # col 1 usrpstate_cluster.Add(usrpstate_col1) # col 2 usrpstate_cluster.Add(usrpstate_col2) # second cluster - display controls display_outline = wx.StaticBox(self, wx.ID_ANY, "Display") display_cluster = wx.StaticBoxSizer(display_outline, wx.HORIZONTAL) nframesbox = wx.BoxSizer(wx.HORIZONTAL) nframesbox.Add(self.nframes_ctrls.layout, proportion=1, flag=wx.ALL, border=5) nframesbox.Add(self.tune_delay_ctrls.layout, proportion=1, flag=wx.ALL, border=5) display_col1 = wx.BoxSizer(wx.VERTICAL) display_col1.Add(self.res_ctrls.layout, flag=wx.ALL, border=5) display_col1.Add(nframesbox, flag=wx.EXPAND) display_col2 = wx.BoxSizer(wx.VERTICAL) display_col2.Add(self.windowfn_ctrls.layout, flag=wx.ALL, border=5) display_col2.Add(self.power_ctrls.layout, flag=wx.ALL | wx.EXPAND, border=5) # col 1 display_cluster.Add(display_col1) # col 2 display_cluster.Add(display_col2) # third cluster - data controls data_outline = wx.StaticBox(self, wx.ID_ANY, "Data") data_cluster = wx.StaticBoxSizer(data_outline, wx.HORIZONTAL) data_col3 = wx.BoxSizer(wx.VERTICAL) data_col3.Add(self.threshold_ctrls.layout) data_col3.Add(self.export_ctrls.layout) # col 1 data_cluster.Add(self.mkr1_ctrls.layout, flag=wx.ALL, border=5) # col 2 data_cluster.Add(self.mkr2_ctrls.layout, flag=wx.ALL, border=5) # col 3 data_cluster.Add(data_col3, flag=wx.ALL, border=5) # put everything together # Add control clusters vertically to control stack controlstack.Add(usrpstate_cluster, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) controlstack.Add(display_cluster, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) controlstack.Add(data_cluster, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) # Add plot and control stack side-by-side on the front panel frontpanel.Add(self.plot, flag=wx.ALIGN_CENTER_VERTICAL) frontpanel.Add(controlstack, flag=wx.ALIGN_CENTER_VERTICAL) self.SetSizer(frontpanel) self.Fit() #################### # GUI Initialization #################### def init_mpl_canvas(self): """Initialize a matplotlib plot.""" self.plot = wx.Panel(self, wx.ID_ANY, size=(700, 600)) self.figure = Figure(figsize=(7, 6), dpi=100) self.figure.subplots_adjust(right=.95) self.canvas = FigureCanvas(self.plot, -1, self.figure) def configure_mpl_plot(self, y, adjust_freq_range=True): """Configure or reconfigure the matplotlib plot""" maxbin = self.tb.cfg.max_plotted_bin self.x = self.tb.cfg.bin_freqs[:maxbin] # self.line in a numpy array in the form [[x-vals], [y-vals]], where # x-vals are bin center frequencies and y-vals are powers. So once we # initialize a power at each freq, just find the index of the # frequency that a measurement was taken at, and insert it into the # corresponding index in y-vals. if adjust_freq_range: len_x = len(self.x) len_y = len(y) if len_x != len_y: # There's a race condition when in continuous mode and # a frequency range-adjusting parameter (like span) is # changed, so we sometimes get updated x-values before # updated y-values. Since a) it only affects # continuous mode and b) the user has requested a # different view, there's no harm in simply dropping # the old data and re-calling configure_mpl_plot next frame. # Still - this is a workaround. # The most "correct" solution would be to have # controller_c tag the first sample propagated after # flowgraph starts, which plotter_f would look for and # use to trigger plot reconfig. self.logger.debug("data mismatch - frame dropped") return False if hasattr(self, 'mkr1'): self.mkr1.unplot() if hasattr(self, 'mkr2'): self.mkr2.unplot() if hasattr(self, 'line'): self.line.remove() # initialize a line self.line, = self.subplot.plot(self.x, y, animated=True, antialiased=True, linestyle='-', color='b') self.canvas.draw() self._update_background() return True def format_axis(self): """Set the formatting of the plot axes.""" if hasattr(self, "subplot"): ax = self.subplot else: ax = self.figure.add_subplot(111) xaxis_formatter = FuncFormatter(self.format_mhz) ax.xaxis.set_major_formatter(xaxis_formatter) ax.set_xlabel("Frequency (MHz)") ax.set_ylabel("Power (dBm)") cf = self.tb.cfg.center_freq lowest_xtick = cf - (self.tb.cfg.span / 2) highest_xtick = cf + (self.tb.cfg.span / 2) ax.set_xlim(lowest_xtick - 1e6, highest_xtick + 1e6) ax.set_ylim(self.min_power + 1, self.max_power - 1) xticks = np.linspace(lowest_xtick, highest_xtick, 5, endpoint=True) ax.set_xticks(xticks) ax.set_yticks(np.arange(self.min_power, self.max_power, 10)) ax.grid(color='.90', linestyle='-', linewidth=1) ax.set_title("Power Spectrum") self.subplot = ax self.canvas.draw() self._update_background() @staticmethod def format_mhz(x, pos): """Format x ticks (in Hz) to MHz with 0 decimal places.""" return "{:.1f}".format(x / float(1e6)) #################### # Plotting functions #################### def update_plot(self, y, redraw_plot, keep_alive): """Update the plot.""" if redraw_plot: #assert not keep_alive self.logger.debug("Reconfiguring matplotlib plot") self.format_axis() if not self.configure_mpl_plot(y): # Got bad data, try again next frame self.tb.plot_iface.redraw_plot.set() return # Required for plot blitting self.canvas.restore_region(self.plot_background) if keep_alive: # Just keep markers and span alive after single run y = self.line.get_ydata() self.subplot.draw_artist(self.line) else: self._draw_line(y) self._check_threshold(y) self._draw_span() self._draw_threshold() self._draw_markers(y) # blit canvas self.canvas.blit(self.subplot.bbox) def _update_background(self): """Force update of the plot background.""" self.plot_background = self.canvas.copy_from_bbox(self.subplot.bbox) def _draw_span(self): """Draw a span to bound the peak search functionality.""" if self.span is not None: self.subplot.draw_artist(self.span) def _draw_threshold(self): """Draw a span to bound the peak search functionality.""" if self.threshold.line is not None: self.subplot.draw_artist(self.threshold.line) def _draw_line(self, y): """Draw the latest chunk of line data.""" self.line.set_ydata(y) self.subplot.draw_artist(self.line) def _draw_markers(self, y): """Draw power markers at a specific frequency.""" # Update mkr1 if it's set if self.mkr1.freq is not None: m1bin = self.mkr1.bin_idx mkr1_power = y[m1bin] self.mkr1.point.set_ydata(mkr1_power) self.mkr1.point.set_visible(True) # make visible self.mkr1.text_label.set_visible(True) self.mkr1.text_power.set_text("{:.1f} dBm".format(mkr1_power)) self.mkr1.text_power.set_visible(True) # redraw self.subplot.draw_artist(self.mkr1.point) self.figure.draw_artist(self.mkr1.text_label) self.figure.draw_artist(self.mkr1.text_power) # Update mkr2 if it's set if self.mkr2.freq is not None: m2bin = self.mkr2.bin_idx mkr2_power = y[m2bin] self.mkr2.point.set_ydata(mkr2_power) self.mkr2.point.set_visible(True) # make visible self.mkr2.text_label.set_visible(True) self.mkr2.text_power.set_text("{:.2f} dBm".format(mkr2_power)) self.mkr2.text_power.set_visible(True) # Redraw self.subplot.draw_artist(self.mkr2.point) self.figure.draw_artist(self.mkr2.text_label) self.figure.draw_artist(self.mkr2.text_power) def _check_threshold(self, y): """Warn to stdout if the threshold level has been crossed.""" # Update threshold # indices of where the y-value is greater than self.threshold.level if self.threshold.level is not None: overloads, = np.where(y > self.threshold.level) if overloads.size: # is > 0 self.log_threshold_overloads(overloads, y) def log_threshold_overloads(self, overloads, y): """Outout threshold violations to the logging system.""" logheader = "============= Overload at {} =============" self.logger.warning(logheader.format(int(time.time()))) logmsg = "Exceeded threshold {0:.0f}dBm ({1:.2f}dBm) at {2:.2f}MHz" for i in overloads: self.logger.warning( logmsg.format(self.threshold.level, y[i], self.x[i] / 1e6)) ################ # Event handlers ################ def on_mousedown(self, event): """store event info for single click.""" self.last_click_evt = event def on_mouseup(self, event): """Determine if mouse event was single click or click-and-drag.""" if abs(self.last_click_evt.x - event.x) >= 5: # mouse was clicked and dragged more than 5 pxls, set a span self.span = self.subplot.axvspan( self.last_click_evt.xdata, event.xdata, color='red', alpha=0.2, # play nice with blitting: animated=True) xdata_points = [self.last_click_evt.xdata, event.xdata] # always set left bound as lower value self.span_left, self.span_right = sorted(xdata_points) else: # caught single click, clear span if self.subplot.patches: self.span.remove() self.subplot.patches = [] self.span = self.span_left = self.span_right = None def idle_notifier(self, event): self.tb.plot_iface.set_gui_idle() def set_continuous_run(self, event): self.tb.pending_cfg.export_raw_time_data = False self.tb.pending_cfg.export_raw_fft_data = False self.tb.pending_cfg.continuous_run = True self.tb.set_continuous_run() def set_single_run(self, event): self.tb.pending_cfg.continuous_run = False self.tb.set_single_run() @staticmethod def _verify_data_dir(dir): if not os.path.exists(dir): os.makedirs(dir) def export_time_data(self, event): if (self.tb.single_run.is_set() or self.tb.continuous_run.is_set()): msg = "Can't export data while the flowgraph is running." msg += " Use \"single\" run mode." self.logger.error(msg) return else: if not self.tb.timedata_sink.data(): self.logger.warn("No more time data to export") return # creates path string 'data/time_data_01_TIMESTAMP.dat' dirname = "data" self._verify_data_dir(dirname) fname = str.join( '', ('time_data_', str(self.time_data_export_counter).zfill(2), '_', str(int(time.time())), '.dat')) wildcard = "Data and Settings files (*.dat; *.mat)|*.dat;*.mat" style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT filepath_dialog = wx.FileDialog(self, message="Save As", defaultDir=dirname, defaultFile=fname, wildcard=wildcard, style=style) if filepath_dialog.ShowModal() == wx.ID_CANCEL: return self.time_data_export_counter += 1 filepath_dialog.Destroy() self.tb.save_time_data_to_file(filepath_dialog.GetPath()) def export_fft_data(self, event): if self.tb.single_run.is_set() or self.tb.continuous_run.is_set(): msg = "Can't export data while the flowgraph is running." msg += " Use \"single\" run mode." self.logger.error(msg) return else: if not self.tb.freqdata_sink.data(): self.logger.warn("No more FFT data to export") return False # creates path string 'data/fft_data_01_TIMESTAMP.dat' dirname = "data" self._verify_data_dir(dirname) fname = str.join( '', ('fft_data_', str(self.fft_data_export_counter).zfill(2), '_', str(int(time.time())), '.dat')) wildcard = "Data and Settings files (*.dat; *.mat)|*.dat;*.mat" style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT filepath_dialog = wx.FileDialog(self, message="Save As", defaultDir=dirname, defaultFile=fname, wildcard=wildcard, style=style) if filepath_dialog.ShowModal() == wx.ID_CANCEL: return self.fft_data_export_counter += 1 filepath_dialog.Destroy() self.tb.save_freq_data_to_file(filepath_dialog.GetPath()) def close(self, event): """Handle a closed gui window.""" self.closed = True self.tb.stop() self.tb.wait() self.Destroy() self.logger.debug("GUI closing.")
class Frame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1) self.SetTitle('Blitting Example') # Figure layout. self.fig = Figure((5,4), 100) self.canvas = FigureCanvas(self, -1, self.fig) # BEGIN: Widget layout. self.wx_widgets = [] sizer = wx.BoxSizer() sizer.Add(self.canvas, proportion=1, flag=wx.EXPAND) self.SetSizer(sizer) self.Fit() # END: Widget layout. # BEGIN: Plot initialization. self.axes = self.fig.add_subplot(111) self.line1_ydata = [numpy.random.normal()] self.line2_ydata = [numpy.random.normal()] self.line1, = self.axes.plot(self.line1_ydata, color='b', animated=BLIT) self.line2, = self.axes.plot(self.line2_ydata, color='r', animated=BLIT) self.fps_label = self.axes.annotate("FPS: 0", (0.01, 0.95), xycoords="axes fraction", animated=BLIT) self.xaxis = self.axes.get_xaxis() self.yaxis = self.axes.get_yaxis() # END: Plot initialization. # BEGIN: Timers # IMPORTANT: Use wx.EVT_TIMER for redraw to avoid resource conflicts/crashes. self.redraw_timer = wx.Timer(self, wx.NewId()) # Plot refresh timer. self.redraw_timer.Start(50) wx.EVT_TIMER(self, self.redraw_timer.GetId(), self.redraw) self.frames = 0 self.start = time.time() # END: Timers ############################### # BEGIN: Plot related methods # ############################### def redraw(self, evt): # update the ydata, normally this would be done by SyncDB y1 = self.line1_ydata[-1] + numpy.random.normal() y2 = self.line2_ydata[-1] + numpy.random.normal() self.line1_ydata.append(y1) self.line2_ydata.append(y2) self.line1.set_data(range(len(self.line1_ydata)), self.line1_ydata) self.line2.set_data(range(len(self.line2_ydata)), self.line2_ydata) self.axes.set_ylim(min(self.line1_ydata + self.line2_ydata), max(self.line1_ydata + self.line2_ydata)) self.axes.set_xlim(max(0, len(self.line1_ydata)-100), max(100, len(self.line1_ydata))) self.fps_label.set_text("FPS: %.2f" % (self.frames / float(time.time() - self.start))) if BLIT: self.fig.draw_artist(self.fig) self.axes.draw_artist(self.line1) self.axes.draw_artist(self.line2) self.axes.draw_artist(self.fps_label) self.fig.draw_artist(self.xaxis) self.fig.draw_artist(self.yaxis) self.canvas.blit(self.fig.bbox) else: self.canvas.draw() self.frames += 1