class MatplotlibWidget(QtGui.QWidget): def __init__(self, parent=None): super(MatplotlibWidget, self).__init__(parent) self.figure = Figure() self.canvas = FigureCanvasQTAgg(self.figure) #: Initialize Plots self.axis1 = self.figure.add_subplot(211) self.axis2 = self.figure.add_subplot(212) self.axis1.set_autoscaley_on(False) #: Disable Auto scale self.axis1.set_xlim([0, 2000]) self.axis1.set_ylim([-1.5, 1.5]) self.axis2.set_autoscaley_on(False) self.axis2.set_xlim([1, 60]) self.axis2.set_ylim([0, 65000]) self.axis1.set_xticks(np.arange(0, 2000, 100)) self.axis1.set_yticks(np.arange(-1.5, 1.5, 0.3)) self.axis1.grid(False) #: Useless. True doesnt work either. #: Non dynamic parts are cached so that we can decrease the draw time self.background = self.canvas.copy_from_bbox(self.axis1.bbox) self.layoutVertical = QtGui.QVBoxLayout(self) self.layoutVertical.addWidget(self.canvas)
class mpl_widget(QWidget): def __init__(self, parent=None, mainWidget=None): self._SELECTEDCELLS = list() # container for instances of selected cells, so we can delete them when we want self._SELECTEDCELLS_IJ = list() # container for coords of selected cells, so we can delete them when we want self._SELECTEDCELLLINES = list() # container for instances of selected cells, so we can delete them when we want self._GRIDLINES = None QWidget.__init__(self, parent) self.mainWidget = mainWidget self.create_main_frame() self.mpl_menu = mpl_menu(self) self.shift_is_held = False #self.connect(self.mpl_menu, QtCore.SIGNAL('mySignal'), self.mySlot) #print 'my parent is:', parent self.clear_selection() self.init_tooltips() def init_tooltips(self): self.canvas.setToolTip('If 2D plot => RMB click toggles menu <br> - RMB click selects cell <br> - selected cells are drawn with black border') self.grid_cb.setToolTip('If 2D plot => show computational grid <br> If 1D plot => show normal gridlines') self.cbar_button.setToolTip('If 2D plot => controls the color range. <br> Note: <br> - pressing UP and DOWN arrows cycles through colormaps <br> - dragging colorbar with RMB scales the color-range <br> - dragging colorbar with LMB shifts the color-range') self.mpl_toolbar.setToolTip('Shows coordinates (i,j, lat,lon) and data-value(z) under the cursor. <br> if you see <i>>>></i> coordinates are not visible. Enlarge the window') def create_main_frame(self): self.fig = Figure(dpi=100) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self) self.canvas.setFocusPolicy( Qt.ClickFocus ) self.canvas.setFocus() self.mpl_toolbar = myNavigationToolbar(self.canvas, self) self.canvas.mpl_connect('button_press_event', self.on_click) self.canvas.mpl_connect('key_press_event', self.on_key_press) self.canvas.mpl_connect('key_release_event', self.on_key_release) #self.canvas.mpl_connect('button_press_event', self.disable_clicks) self.cbar_button = QPushButton("Color Range") self.cbar_button.setFocusPolicy( Qt.NoFocus ) self.grid_cb = QCheckBox("Show Grid") self.grid_cb.setFocusPolicy( Qt.NoFocus ) self.grid_cb.stateChanged.connect(self.showGrid) vbox = QVBoxLayout() hbox = QHBoxLayout() vbox.addWidget(self.canvas) # the matplotlib canvas hbox.addWidget(self.mpl_toolbar) hbox.addWidget(self.cbar_button) hbox.addWidget(self.grid_cb) vbox.addLayout(hbox) self.setLayout(vbox) def on_click(self, event): if event.inaxes != self.get_axes()[0]: return #if self.get_axes()[0].format_coord(event.x, event.y) == 'outside data area': return if self.allow_menu(): self.allow_popup_menu = True if self.shift_is_held: self.allow_popup_menu = False point = [int(event.xdata + .5), int(event.ydata + .5)] #print '>>>', point, '\t currently {0} selected'.format(len(self._SELECTEDCELLS)) if event.button == 3 : #if RMB is clicked # working with dialog for transect! if self.mainWidget.transect_dlg: if self.mainWidget.transect_dlg.toogle_show_after_selected_cell: realx, realy = self.get_real_xy(event.xdata, event.ydata, self.mainWidget.detect_variable_dimensions()) realpoint = [realy, realx] #print 'real xy:', realpoint if self.mainWidget.transect_dlg.toogle_show_after_selected_cell == 1: # select point 1 self.allow_popup_menu = False self.mainWidget.transect_dlg.data.set_p1(realpoint) elif self.mainWidget.transect_dlg.toogle_show_after_selected_cell == 2: # select point 2 self.allow_popup_menu = False self.mainWidget.transect_dlg.data.set_p2(realpoint) self.mainWidget.transect_dlg.update() self.mainWidget.transect_dlg.show() # working with dialog for flux! if self.mainWidget.flux_dlg: if self.mainWidget.flux_dlg.toogle_show_after_selected_cell: if self.mainWidget.flux_dlg.toogle_show_after_selected_cell == 1: # select point 1 self.allow_popup_menu = False self.mainWidget.flux_dlg.data.set_p1(point) elif self.mainWidget.flux_dlg.toogle_show_after_selected_cell == 2: # select point 2 self.allow_popup_menu = False self.mainWidget.flux_dlg.data.set_p2(point) self.mainWidget.flux_dlg.update() self.mainWidget.flux_dlg.show() if len(self._SELECTEDCELLS) == 0: # if no cell is selected self.add_selected_cell(point) else: # if some cells are already selected if self.mpl_menu.allow_rmb_select_cells() or self.shift_is_held: # check if this point is already selected: already_selected = False for p in self._SELECTEDCELLS_IJ: if (point[0] == p[0]) and (point[1] == p[1]): already_selected = True print 'cell already selected... is not added' if not already_selected: self.add_selected_cell(point) else: pass #self.clear_selection() #self.add_selected_cell(point) def cells_selected(self): if self._SELECTEDCELLS: return len(self._SELECTEDCELLS) else: return False def add_selected_cell(self, point): ''' point = [i, j]''' print 'selected cell:', point[0], point[1] c = self.draw_picked_cell(point) self._SELECTEDCELLS.append(c) self._SELECTEDCELLS_IJ.append(point) def get_selected_cells_ij(self): return self._SELECTEDCELLS_IJ def clear_selection(self): ''' delete all graphical objects of selected cells redraw canvas ''' print 'clearing stuff' if len(self._SELECTEDCELLLINES) > 0: for line in self._SELECTEDCELLLINES: l = line.pop(0) l.remove() del l del self._SELECTEDCELLLINES[:] #print 'cells ---- before:', len(self._SELECTEDCELLS) if len(self._SELECTEDCELLS) > 0: for cell in self._SELECTEDCELLS: for line in cell: l = line.pop(0) l.remove() del l del self._SELECTEDCELLS[:] #print 'cells ---- left:', len(self._SELECTEDCELLS) #print 'cells-coords ----' #print len(self._SELECTEDCELLS_IJ) if self._SELECTEDCELLS_IJ: for cellcoords in self._SELECTEDCELLS_IJ: #cc = cellcoords.pop(0) #cellcoords.remove() del cellcoords del self._SELECTEDCELLS_IJ[:] #print 'cells ---- left,', len(self._SELECTEDCELLS_IJ) if len(self._SELECTEDCELLS) != 0: raise ValueError('List "self._SELECTEDCELLS" was not flushed') if len(self._SELECTEDCELLLINES) != 0: raise ValueError('List "self._SELECTEDCELLLINES" was not flushed') if len(self._SELECTEDCELLS_IJ) != 0: raise ValueError('List "self._SELECTEDCELLLINES" was not flushed') # update plot self.canvas.draw() #print 'finishing clear: cells left', len(self._SELECTEDCELLS) def showGrid(self, state): if self.fig.axes: current_plot = self.mainWidget.get_plotType() current_plot2D = self.mainWidget.get_plotType_for_timeseries() if state == Qt.Checked: if current_plot == '1D' or (current_plot2D =="2DZT"): self.fig.axes[0].grid(True) elif current_plot == '2D' and (not current_plot2D =="2DZT"): self.draw_pixel_grid(True) else: if current_plot == '1D' or (current_plot2D =="2DZT"): self.fig.axes[0].grid(False) elif current_plot == '2D' and (not current_plot2D =="2DZT"): self.draw_pixel_grid(False) self.canvas.draw() def draw_picked_cell(self, point): x = point[0] y = point[1] ''' approach drawing a patch... not working cell_bnd = patches.Rectangle((x-.5, y-.5), 1, 1, fill=False, edgecolor="black", hatch=None, linewidth=1.) cell_instance = self.fig.axes[0].add_patch(cell_bnd) ''' b_line = [(x-.5, x+.5), (y-.5, y-.5)] r_line = [(x+.5, x+.5), (y-.5, y+.5)] t_line = [(x-.5, x+.5), (y+.5, y+.5)] l_line = [(x-.5, x-.5), (y-.5, y+.5)] cell = [b_line, r_line, t_line, l_line] for i, l in enumerate(cell): ll = self.fig.axes[0].plot(l[0], l[1], 'k-', lw=.8) cell[i] = ll # overwriting current Line2D object with object binded to an axes #self._SELECTEDCELLS.append(cell) # collecting reference to this cell to be able to delete it #self._SELECTEDCELLS_IJ.append(point) # collecting reference to this cell to be able to delete it self.canvas.draw() return cell def draw_line(self, point1, point2): line = [(point1[0], point2[0]), (point1[1], point2[1])] l = self.fig.axes[0].plot(line[0], line[1], 'k-', lw=2) return l def draw_pixel_grid(self, enable=True): if enable: dx = 1 dy = 1 x0 = -.5 y0 = -.5 if self.mainWidget.get_plotType_for_timeseries() == '2DXY': nx = self.mainWidget.get_nX() ny = self.mainWidget.get_nY() elif self.mainWidget.get_plotType_for_timeseries() == '2DZY': nx = self.mainWidget.get_nY() ny = self.mainWidget.get_nZ() elif self.mainWidget.get_plotType_for_timeseries() == '2DZX': nx = self.mainWidget.get_nX() ny = self.mainWidget.get_nZ() self._GRIDLINES = list() for n_hline in np.arange(ny+1): hline = [(x0, x0+nx), (y0+n_hline, y0+n_hline)] l = self.fig.axes[0].plot(hline[0], hline[1], 'k-', lw=.2) self._GRIDLINES.append(l) # collecting reference to this line to be able to delete it for n_vline in np.arange(nx+1): vline = [(x0+n_vline, x0+n_vline), (y0, y0+ny)] l = self.fig.axes[0].plot(vline[0], vline[1], 'k-', lw=.2) self._GRIDLINES.append(l) # collecting reference to this line to be able to delete it if not enable: #print 'deleting lines...' if self._GRIDLINES: # if lines were created #print 'lines are here...' for line in self._GRIDLINES: #print line l = line.pop(0) l.remove() del l self.fig.canvas.draw() def on_key_press(self, event): #print 'key pressed:', event.key if event.key == 'shift': self.shift_is_held = True def on_key_release(self, event): #print 'key released:', event.key if event.key == 'shift': self.shift_is_held = False elif event.key == 'escape': self.clear_selection() def change_coordinate_formatter(self, ax, data2d, bruteforce_flag=None, bruteforce_dims=None): ''' see http://stackoverflow.com/questions/14754931/matplotlib-values-under-cursor ''' numrows, numcols = data2d.shape bruteforce_mode_on = False bruteforce_mode_on = (bruteforce_flag == '2DXY' and bruteforce_dims[-1] and bruteforce_dims[-2]) def format_coord(x, y): col = int(x+0.5) row = int(y+0.5) if not bruteforce_mode_on: if col >= 0 and col < numcols and row >= 0 and row < numrows: #z = data2d[row, col] # sice we have artificially reversed y-coordinate axes, now reverse data! # numrows-1, because if nrows=10 => row=[0:9] z = data2d[numrows-1-row, col] #return 'x=%1.1f y=%1.1f z=%1.6f' % (x, y, z) return 'i=%d j=%d z=%.6f' % (col, row, z) else: #return 'x=%1.4f, y=%1.4f' % (x, y) return 'outside data area' elif bruteforce_flag == '2DXY' and bruteforce_dims[-1] and bruteforce_dims[-2]: ''' our extend in X=[-0.5:numcols-0.5], Y=[-0.5:numrows-0.5], because col,row is cell center! ''' if col >= 0 and col < numcols and row >= 0 and row < numrows: #z = data2d[row, col] # sice we have artificially reversed y-coordinate axes, now reverse data! # numrows-1, because if nrows=10 => row=[0:9] z = data2d[numrows-1-row, col] real_x, real_y = self.get_real_xy(x, y, bruteforce_dims) #return 'x=%1.1f y=%1.1f z=%1.6f' % (x, y, z) #return 'i=%d j=%d z=%.3f x=%.4f y=%.4f' % (col, row, z, real_x, real_y) return 'i=%d j=%d z=%.3f, %s=%.2f %s=%.2f' % ( col, row, z, bruteforce_dims[-1], real_x, bruteforce_dims[-2], real_y) else: #return 'x=%1.4f, y=%1.4f' % (x, y) return 'outside data area' else: raise ValueError('bruteforce_flag can be $None$ or $"2DXY"$. Passed %s' % bruteforce_flag) ax.format_coord = format_coord def allow_menu(self): allow = False #print "self.mainWidget.get_plotType():", self.mainWidget.get_plotType() #print "self.mainWidget.get_plotType_for_timeseries():", self.mainWidget.get_plotType_for_timeseries() if self.mainWidget.get_plotType() == "2D" and not self.mainWidget.get_plotType_for_timeseries() == "2DZT": allow = True return allow def get_real_xy(self, i, j, dimension_list): ''' functions returns values of x,y based on passed indexes i, j ''' if any(dimension_list[-2:-1]) is None: print 'Dimensions X,Y of current variable are not specified (variables that have same name as dimensions not found)' raise ValueError('Dimensions X,Y of current variable are not specified (variables that have same name as dimensions not found)') nc = self.mainWidget.get_selected_ncfile_instance() try: x_var = nc.variables[dimension_list[-1]] y_var = nc.variables[dimension_list[-2]] except: print ('Failed to load variables: {0}, {1}'.format(dimension_list[-1], dimension_list[-2])) raise ValueError('Failed to load variables: {0}, {1}'.format(dimension_list[-1], dimension_list[-2])) x_ratio = (x_var[-1]-x_var[0])/(len(x_var)-1) y_ratio = (y_var[-1]-y_var[0])/(len(y_var)-1) #x[i] = x_var[0]+x_ratio*i #y[j] = y_var[0]+y_ratio*j x = x_var[0] + x_ratio*i y = y_var[0] + y_ratio*j nc.close() return (x, y) def get_axes(self): return self.fig.get_axes() def fast_redraw(self, artist): background = [self.canvas.copy_from_bbox(self.get_axes()[0].bbox)] self.get_axes()[0].draw_artist(artist) self.canvas.restore_region(background) self.canvas.blit(self.get_axes()[0].bbox) self.canvas.update() self.canvas.flush_events()
class RTWindowWidget(QtGui.QWidget): """Matplotlib wxFrame with animation effect""" ### connects widgets and signals ### def __init__(self, parent = None): super(RTWindowWidget, self).__init__(parent) # Matplotlib Figure self.fig = Figure((6, 4), 100) # bind the Figure to the backend specific canvas self.canvas = FigureCanvas(self.fig) # add a subplot self.ax = self.fig.add_subplot(111) # limit the X and Y axes dimensions # we prefer 2 separate functions for clarity self.ax.set_ylim([0, 100]) self.ax.set_xlim([0, POINTS]) # but we want a "frozen" window (defined by y/xlim functions) self.ax.set_autoscale_on(False) # we do not want ticks on X axis self.ax.set_xticks([]) # we want a tick every 10 point on Y (101 is to have 100 too) self.ax.set_yticks(range(0, 101, 10)) # disable autoscale, since we don't want the Axes to adapt # draw a grid (it will be only for Y) self.ax.grid(True) # generates first "empty" plots self.user = [None] * POINTS self.nice = [None] * POINTS self.sys = [None] * POINTS self.idle = [None] * POINTS self.l_user, = self.ax.plot(range(POINTS), self.user, label='User %') self.l_nice, = self.ax.plot(range(POINTS), self.nice, label='Nice %') self.l_sys, = self.ax.plot(range(POINTS), self.sys, label='Sys %') self.l_idle, = self.ax.plot(range(POINTS), self.idle, label='Idle %') # add the legend self.ax.legend(loc='upper center', ncol=4, prop=font_manager.FontProperties(size=10)) # force a draw on the canvas() # trick to show the grid and the legend self.canvas.draw() # save the clean background - everything but the line # is drawn and saved in the pixel buffer background self.bg = self.canvas.copy_from_bbox(self.ax.bbox) # take a snapshot of CPU usage, needed for the update algorithm self.before = self.prepare_cpu_usage() # timerCallback = lambda: self.onTimer() myTimer = QtCore.QTimer() myTimer.timeout.connect(self.onTimer) myTimer.start(1000) #once a sec # print 1 self.canvas.show() def prepare_cpu_usage(self): """helper function to return CPU usage info""" # get the CPU times using psutil module t = p.cpu_times() # return only the values we're interested in if hasattr(t, 'nice'): return [t.user, t.nice, t.system, t.idle] else: # special case for Windows, without 'nice' value return [t.user, 0, t.system, t.idle] def get_cpu_usage(self): """Compute CPU usage comparing previous and current measurements""" # take the current CPU usage information now = self.prepare_cpu_usage() # compute deltas between current and previous measurements delta = [now[i]-self.before[i] for i in range(len(now))] # compute the total (needed for percentages calculation) total = sum(delta) # save the current measurement to before object self.before = now # return the percentage of CPU usage for our 4 categories return [(100.0*dt)/total for dt in delta] def onTimer(self): """callback function for timer events""" print 1 # get the CPU usage information tmp = self.get_cpu_usage() # restore the clean background, saved at the beginning self.canvas.restore_region(self.bg) # update the data self.user = self.user[1:] + [tmp[0]] self.nice = self.nice[1:] + [tmp[1]] self.sys = self.sys[1:] + [tmp[2]] self.idle = self.idle[1:] + [tmp[3]] # update the plot self.l_user.set_ydata(self.user) self.l_nice.set_ydata(self.nice) self.l_sys.set_ydata( self.sys) self.l_idle.set_ydata(self.idle) # just draw the "animated" objects self.ax.draw_artist(self.l_user) self.ax.draw_artist(self.l_nice) self.ax.draw_artist(self.l_sys) self.ax.draw_artist(self.l_idle) # "blit" the background with the animated lines self.canvas.blit(self.ax.bbox)
class SignalViewerWidget(QtGui.QWidget): """Shows different visualizations of a seismic signal (magnitude, envelope, spectrogram, characteristic function). Allows the user to manipulate it (navigate through it, zoom in/out, edit detected events, select threshold value, etc...) """ CF_loaded = QtCore.Signal(bool) event_selected = QtCore.Signal(rc.ApasvoEvent) def __init__(self, parent, document=None): super(SignalViewerWidget, self).__init__(parent) self.document = document self.xmin = 0.0 self.xmax = 0.0 self.xleft = 0.0 self.xright = 0.0 self.time = np.array([]) self.fs = 0.0 self.signal = None self.envelope = None self.cf = None self.time = None self._signal_data = None self._envelope_data = None self._cf_data = None self.fig, _ = plt.subplots(3, 1) self.signal_ax = self.fig.axes[0] self.cf_ax = self.fig.axes[1] self.specgram_ax = self.fig.axes[2] self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy( QtGui.QSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)) self.canvas.setMinimumHeight(320) self.graphArea = QtGui.QScrollArea(self) self.graphArea.setWidget(self.canvas) self.graphArea.setWidgetResizable(True) self.eventMarkers = {} self.last_right_clicked_event = None self.thresholdMarker = None self.playback_marker = None self.selector = SpanSelector(self.fig) self.minimap = MiniMap(self, self.signal_ax, None) # Load Spectrogram settings self.update_specgram_settings() # Animation related attributes self.background = None self.animated = False # Create context menus self.event_context_menu = QtGui.QMenu(self) self.takanami_on_event_action = QtGui.QAction( "Apply Takanami to Event", self) self.takanami_on_event_action.setStatusTip( "Refine event position by using Takanami algorithm") self.event_context_menu.addAction(self.takanami_on_event_action) self.takanami_on_event_action.triggered.connect( self.apply_takanami_to_selected_event) self.selection_context_menu = QtGui.QMenu(self) self.create_event_action = QtGui.QAction( "Create New Event on Selection", self) self.create_event_action.setStatusTip( "Create a new event on selection") self.takanami_on_selection_action = QtGui.QAction( "Apply Takanami to Selection", self) self.takanami_on_selection_action.setStatusTip( "Apply Takanami algorithm to selection") self.selection_context_menu.addAction(self.create_event_action) self.selection_context_menu.addAction( self.takanami_on_selection_action) self.create_event_action.triggered.connect( self.create_event_on_selection) self.takanami_on_selection_action.triggered.connect( self.apply_takanami_to_selection) # format axes formatter = FuncFormatter(lambda x, pos: clt.float_secs_2_string_date( x, self.document.record.starttime)) for ax in self.fig.axes: ax.callbacks.connect('xlim_changed', self.on_xlim_change) ax.xaxis.set_major_formatter(formatter) plt.setp(ax.get_xticklabels(), visible=True) ax.grid(True, which='both') self.specgram_ax.callbacks.connect('ylim_changed', self.on_ylim_change) self.specgram_ax.set_xlabel('Time (seconds)') plt.setp(self.signal_ax.get_yticklabels(), visible=False) #self.signal_ax.set_ylabel('Signal Amp.') self.cf_ax.set_ylabel('CF Amp.') self.specgram_ax.set_ylabel('Frequency (Hz)') # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.graphArea) self.layout.addWidget(self.minimap) self.selector.toggled.connect(self.minimap.set_selection_visible) self.selector.valueChanged.connect(self.minimap.set_selection_limits) self.selector.right_clicked.connect(self.on_selector_right_clicked) if self.document is not None: self.set_record(document) @property def data_loaded(self): return self.document is not None def set_record(self, document, step=120.0): self.document = document self.fs = self.document.record.fs self.signal = self.document.record.signal self.envelope = env.envelope(self.signal) self.cf = self.document.record.cf self.time = np.linspace(0, len(self.signal) / self.fs, num=len(self.signal), endpoint=False) self.xmax = self.time[-1] # Draw minimap self.minimap.minimapSelector.set( visible=False) # Hide minimap selector while loading self.minimap.set_record(self.document.record, step) # Plot signal step_samples = step * self.fs self._signal_data = self.signal_ax.plot(self.time[:step_samples], self.signal[:step_samples], color='black', rasterized=True)[0] # Plot envelope self._envelope_data = self.signal_ax.plot(self.time[:step_samples], self.envelope[:step_samples], color='red', rasterized=True)[0] # Adjust y axis for signal plot signal_yaxis_max_value = max(np.max(self.signal), np.max(self.envelope)) signal_yaxis_min_value = np.min(self.signal) plotting.adjust_axes_height(self.signal_ax, max_value=signal_yaxis_max_value, min_value=signal_yaxis_min_value) # Plot CF cf_loaded = (self.cf.size != 0) self.set_cf_visible(cf_loaded) self.CF_loaded.emit(cf_loaded) cf_step_samples = min(step_samples, len(self.cf)) self._cf_data = self.cf_ax.plot(self.time[:cf_step_samples], self.cf[:cf_step_samples], color='black', rasterized=True)[0] # Adjust y axis for CF plot if cf_loaded: plotting.adjust_axes_height(self.cf_ax, max_value=np.max(self.cf), min_value=np.min(self.cf)) self.thresholdMarker = ThresholdMarker(self.cf_ax) # Plot espectrogram plotting.plot_specgram(self.specgram_ax, self.signal, self.fs, nfft=self.specgram_windowlen, noverlap=self.specgram_noverlap, window=self.specgram_window) # Set the span selector self.selector.fs = self.fs self.selector.set_active(False) self.selector.set_selection_limits(self.xmin, self.xmax) # Set the playback marker self.playback_marker = PlayBackMarker(self.fig, self) # Set the initial xlimits self.set_xlim(0, step) self.subplots_adjust() # Set event markers self.eventMarkers = {} for event in self.document.record.events: self.create_event(event) # Now activate selector again on minimap self.minimap.minimapSelector.set(visible=True) self.minimap.draw() def unset_record(self): self.document = None self.signal = None self.envelope = None self.cf = None self.time = None self._signal_data = None self._envelope_data = None self._cf_data = None self.xmin, self.xmax = 0.0, 0.0 self.eventMarkers = {} # Clear axes self.signal_ax.lines = [] self.cf_ax.lines = [] self.specgram_ax.lines = [] self.specgram_ax.images = [] self.CF_loaded.emit(False) def update_cf(self): if self.data_loaded: self.cf = self.document.record.cf self._cf_data.set_xdata(self.time[:len(self.cf)]) self._cf_data.set_ydata(self.cf) plotting.adjust_axes_height(self.cf_ax) cf_loaded = (self.cf.size != 0) self.CF_loaded.emit(cf_loaded) self.set_cf_visible(cf_loaded) self.draw() def create_events(self, new_events_set): for event in new_events_set.get(self.document.record.uuid, []): self.create_event(event) def create_event(self, event): event_id = event.resource_id.uuid if event_id not in self.eventMarkers: marker = EventMarker(self.fig, self.minimap, self.document, event) self.eventMarkers[event_id] = marker marker.event_selected.connect(self.event_selected.emit) marker.right_clicked.connect(self.on_event_right_clicked) def delete_events(self, new_events_set): for event in new_events_set.get(self.document.record.uuid, []): self.delete_event(event) def delete_event(self, event): event_id = event.resource_id.uuid self.eventMarkers[event_id].remove() self.eventMarkers.pop(event_id) def update_event(self, event): self.eventMarkers[event.resource_id.uuid].update() def set_xlim(self, l, r): xmin = max(0, l) xmax = min(self.xmax, r) self.signal_ax.set_xlim(xmin, xmax) def on_xlim_change(self, ax): xmin, xmax = ax.get_xlim() if (self.xleft, self.xright) != (xmin, xmax): self.xleft, self.xright = xmin, xmax if self.xmin <= xmin <= xmax <= self.xmax: # Update minimap selector if (xmin, xmax) != self.minimap.get_selector_limits(): self.minimap.set_selector_limits(xmin, xmax) # Update axes for axes in self.fig.axes: if ax != axes: axes.set_xlim(xmin, xmax) # Update data xmin = int(max(0, xmin) * self.fs) xmax = int(min(self.xmax, xmax) * self.fs) pixel_width = np.ceil(self.fig.get_figwidth() * self.fig.get_dpi()) if self._signal_data is not None: x_data, y_data = plotting.reduce_data( self.time, self.signal, pixel_width, xmin, xmax) self._signal_data.set_xdata(x_data) self._signal_data.set_ydata(y_data) if self._envelope_data is not None: x_data, y_data = plotting.reduce_data( self.time, self.envelope, pixel_width, xmin, xmax) self._envelope_data.set_xdata(x_data) self._envelope_data.set_ydata(y_data) if self._cf_data is not None and self.cf_ax.get_visible(): x_data, y_data = plotting.reduce_data( self.time[:len(self.cf)], self.cf, pixel_width, xmin, xmax) self._cf_data.set_xdata(x_data) self._cf_data.set_ydata(y_data) # Draw graph self.draw() else: xmin = max(self.xmin, xmin) xmax = min(self.xmax, xmax) ax.set_xlim(xmin, xmax) def on_ylim_change(self, ax): if self.data_loaded: if ax == self.specgram_ax: ymin, ymax = ax.get_ylim() nyquist_freq = (self.fs / 2.0) if ymin < 0.0: ax.set_ylim(0.0, ymax) elif ymax > nyquist_freq: ax.set_ylim(ymin, nyquist_freq) def set_event_selection(self, events): event_id_list = [event.resource_id.uuid for event in events] for event_id in self.eventMarkers: self.eventMarkers[event_id].set_selected(event_id in event_id_list) self.draw() self.minimap.draw() def set_position(self, pos): """""" xmin, xmax = self.signal_ax.get_xlim() mrange = xmax - xmin l, r = pos - mrange / 2.0, pos + mrange / 2.0 if l < self.xmin: l, r = self.xmin, mrange elif r > self.xmax: l, r = self.xmax - mrange, self.xmax self.set_xlim(l, r) def goto_event(self, event): if event.resource_id.uuid in self.eventMarkers: self.set_position(event.stime / self.fs) def showEvent(self, event): self.draw() self.minimap.draw_animate() def resizeEvent(self, event): self.draw() self.minimap.draw_animate() def set_signal_amplitude_visible(self, show_sa): if self._signal_data is not None and self._envelope_data is not None: if self._signal_data.get_visible() != show_sa: self._signal_data.set_visible(show_sa) show_axis = (self._signal_data.get_visible() + self._envelope_data.get_visible()) self.signal_ax.set_visible(show_axis) if self.data_loaded: self.subplots_adjust() self.draw() def set_signal_envelope_visible(self, show_se): if self._signal_data is not None and self._envelope_data is not None: if self._envelope_data.get_visible() != show_se: self._envelope_data.set_visible(show_se) show_axis = (self._signal_data.get_visible() + self._envelope_data.get_visible()) self.signal_ax.set_visible(show_axis) if self.data_loaded: self.subplots_adjust() self.draw() def set_cf_visible(self, show_cf): if self.cf_ax.get_visible() != show_cf: if self.data_loaded: if len(self.cf) <= 0: self.cf_ax.set_visible(False) else: self.cf_ax.set_visible(show_cf) self.subplots_adjust() self.draw() def set_espectrogram_visible(self, show_eg): if self.specgram_ax.get_visible() != show_eg: self.specgram_ax.set_visible(show_eg) if self.data_loaded: self.subplots_adjust() self.draw() def set_minimap_visible(self, show_mm): if self.minimap.get_visible() != show_mm: self.minimap.set_visible(show_mm) self.minimap.draw_animate() def set_threshold_visible(self, show_thr): if self.thresholdMarker: if self.thresholdMarker.get_visible() != show_thr: self.thresholdMarker.set_visible(show_thr) self.draw() def subplots_adjust(self): visible_subplots = [ ax for ax in self.fig.get_axes() if ax.get_visible() ] for i, ax in enumerate(visible_subplots): correct_geometry = (len(visible_subplots), 1, i + 1) if correct_geometry != ax.get_geometry(): ax.change_geometry(len(visible_subplots), 1, i + 1) # Adjust space between subplots self.fig.subplots_adjust(left=0.06, right=0.95, bottom=0.14, top=0.95, hspace=0.22) def get_selector_limits(self): return self.selector.get_selector_limits() def set_selector_limits(self, xleft, xright): self.selector.set_selector_limits(xleft, xright) def set_selection_enabled(self, value): self.selector.set_enabled(value) def set_playback_position(self, position): if self.playback_marker is not None: self.playback_marker.set_position(position) self.minimap.playback_marker.set_position(position) def set_playback_marker_visible(self, show_marker): if self.playback_marker is not None: self.playback_marker.set_visible(show_marker) self.minimap.playback_marker.set_visible(show_marker) def on_event_right_clicked(self, event): self.last_right_clicked_event = event self.event_context_menu.exec_(QtGui.QCursor.pos()) def apply_takanami_to_selected_event(self): takanamidialog.TakanamiDialog( self.document, seismic_event=self.last_right_clicked_event).exec_() def apply_takanami_to_selection(self): xleft, xright = self.get_selector_limits() takanamidialog.TakanamiDialog(self.document, xleft, xright).exec_() def create_event_on_selection(self): xleft, xright = self.get_selector_limits() xleft, xright = xleft * self.fs, xright * self.fs cf = self.cf[xleft:xright] if cf.size > 0: time = (xleft + np.argmax(cf)) else: time = (xleft + ((xright - xleft) / 2.0)) self.document.createEvent(time=time) def draw(self): if self.animated: self._draw_animate() else: self.canvas.draw_idle() def _draw_animate(self): self.canvas.restore_region(self.background) for artist in self._get_animated_artists(): if artist.get_visible(): ax = artist.get_axes() if ax is not None: if artist.get_axes().get_visible(): self.fig.draw_artist(artist) else: self.fig.draw_artist(artist) self.canvas.blit(self.fig.bbox) def _set_animated(self, value): if self.animated != value: self.animated = value for artist in self._get_animated_artists(): artist.set_animated(value) if self.animated == True: images = [] for ax in self.fig.axes: images.extend(ax.images) for image in images: image.set_visible(False) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) for image in images: image.set_visible(True) def _get_animated_artists(self): artists = [] for ax in self.fig.axes: artists.extend(ax.images) artists.extend(ax.lines) artists.append(ax.xaxis) artists.append(ax.yaxis) artists.extend(ax.patches) artists.extend(ax.spines.values()) for artist in artists: yield artist def update_specgram_settings(self): # load specgram settings settings = QtCore.QSettings(_organization, _application_name) settings.beginGroup("specgram_settings") self.specgram_windowlen = int( settings.value('window_len', settingsdialog.SPECGRAM_WINDOW_LENGTHS[4])) self.specgram_noverlap = int( settings.value('noverlap', self.specgram_windowlen / 2)) self.specgram_window = settings.value('window', plotting.SPECGRAM_WINDOWS[2]) settings.endGroup() if self.data_loaded: # Plot espectrogram self.specgram_ax.images = [] # Save x-axis limits limits = self.signal_ax.get_xlim() # Draw spectrogram plotting.plot_specgram(self.specgram_ax, self.signal, self.fs, nfft=self.specgram_windowlen, noverlap=self.specgram_noverlap, window=self.specgram_window) # Restore x-axis limits self.signal_ax.set_xlim(*limits) def paintEvent(self, paintEvent): super(SignalViewerWidget, self).paintEvent(paintEvent) def on_selector_right_clicked(self): xleft, xright = self.get_selector_limits() self.takanami_on_selection_action.setEnabled( (xright - xleft) >= (takanamidialog.MINIMUM_MARGIN_IN_SECS * 2)) self.selection_context_menu.exec_(QtGui.QCursor.pos())
class MiniMap(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ def __init__(self, parent, ax, record=None): super(MiniMap, self).__init__(parent) self.ax = ax self.xmin = 0.0 self.xmax = 0.0 self.step = 10.0 self.xrange = np.array([]) self.minimapFig = plt.figure() self.minimapFig.set_figheight(0.75) self.minimapFig.add_axes((0, 0, 1, 1)) self.minimapCanvas = FigureCanvas(self.minimapFig) self.minimapCanvas.setFixedHeight(64) self.minimapSelector = self.minimapFig.axes[0].axvspan(0, self.step, color='gray', alpha=0.5, animated=True) self.minimapSelection = self.minimapFig.axes[0].axvspan( 0, self.step, color='LightCoral', alpha=0.5, animated=True) self.minimapSelection.set_visible(False) self.minimapBackground = [] self.minimapSize = (self.minimapFig.bbox.width, self.minimapFig.bbox.height) self.press_selector = None self.playback_marker = None self.minimapCanvas.mpl_connect('button_press_event', self.onpress) self.minimapCanvas.mpl_connect('button_release_event', self.onrelease) self.minimapCanvas.mpl_connect('motion_notify_event', self.onmove) # Animation related attrs. self.background = None self.animated = False # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.minimapCanvas) # Animation related attributes self.parentViewer = parent # Set Markers dict self.markers = {} self.record = None if record is not None: self.set_record(record) def set_record(self, record, step): self.record = record self.step = step self.xrange = np.linspace(0, len(self.record.signal) / self.record.fs, num=len(self.record.signal), endpoint=False) self.xmin = self.xrange[0] self.xmax = self.xrange[-1] self.markers = {} ax = self.minimapFig.axes[0] ax.lines = [] formatter = FuncFormatter( lambda x, pos: str(datetime.timedelta(seconds=x))) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which='both') # Set dataseries to plot xmin = self.xmin * self.record.fs xmax = self.xmax * self.record.fs pixel_width = np.ceil(self.minimapFig.get_figwidth() * self.minimapFig.get_dpi()) x_data, y_data = plotting.reduce_data(self.xrange, self.record.signal, pixel_width, xmin, xmax) # self._plot_data.set_xdata(x_data) # self._plot_data.set_ydata(y_data) ax.plot(x_data, y_data, color='black', rasterized=True) ax.set_xlim(self.xmin, self.xmax) plotting.adjust_axes_height(ax) # Set the playback marker self.playback_marker = PlayBackMarker(self.minimapFig, self) self.playback_marker.markers[0].set_animated(True) # Draw canvas self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox( self.minimapFig.bbox) self.draw_animate() def onpress(self, event): self.press_selector = event xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.parentViewer._set_animated(True) self.set_selector_limits(xmin, xmax) def onrelease(self, event): self.press_selector = None # Finish parent animation self.parentViewer._set_animated(False) def onmove(self, event): if self.press_selector is not None: xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.set_selector_limits(xmin, xmax) def get_xdata(self, event): inv = self.minimapFig.axes[0].transData.inverted() xdata, _ = inv.transform((event.x, event.y)) return xdata def set_selector_limits(self, xmin, xmax): step = xmax - xmin if step >= self.xmax - self.xmin: xleft = self.xmin xright = self.xmax if xmin < self.xmin: xleft = self.xmin xright = self.step elif xmax > self.xmax: xleft = self.xmax - step xright = self.xmax else: xleft = xmin xright = xmax if (xleft, xright) != (self.minimapSelector.xy[1, 0], self.minimapSelector.xy[2, 0]): self.step = step self.minimapSelector.xy[:2, 0] = xleft self.minimapSelector.xy[2:4, 0] = xright self.ax.set_xlim(xleft, xright) self.draw_animate() else: self.parentViewer.draw() def get_selector_limits(self): return self.minimapSelector.xy[0, 0], self.minimapSelector.xy[2, 0] def draw(self): self.draw_animate() def draw_animate(self): size = self.minimapFig.bbox.width, self.minimapFig.bbox.height if size != self.minimapSize: self.minimapSize = size self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox( self.minimapFig.bbox) self.minimapCanvas.restore_region(self.minimapBackground) self.minimapFig.draw_artist(self.minimapSelection) self.minimapFig.draw_artist(self.minimapSelector) self.minimapFig.draw_artist(self.playback_marker.markers[0]) for marker in self.markers.values(): self.minimapFig.draw_artist(marker) self.minimapCanvas.blit(self.minimapFig.bbox) def set_visible(self, value): self.minimapCanvas.setVisible(value) def get_visible(self): return self.minimapCanvas.isVisible() def set_selection_limits(self, xleft, xright): self.minimapSelection.xy[:2, 0] = xleft self.minimapSelection.xy[2:4, 0] = xright self.draw_animate() def set_selection_visible(self, value): self.minimapSelection.set_visible(value) self.draw_animate() def create_marker(self, key, position, **kwargs): if self.xmin <= position <= self.xmax: marker = self.minimapFig.axes[0].axvline(position, animated=True) self.markers[key] = marker self.markers[key].set(**kwargs) def set_marker_position(self, key, value): marker = self.markers.get(key) if marker is not None: if self.xmin <= value <= self.xmax: marker.set_xdata(value) def set_marker(self, key, **kwargs): marker = self.markers.get(key) if marker is not None: kwargs.pop( "animated", None ) # marker's animated property must be always true to be drawn properly marker.set(**kwargs) def delete_marker(self, key): marker = self.markers.get(key) if marker is not None: self.minimapFig.axes[0].lines.remove(marker) self.markers.pop(key)
class Plotter(QMainWindow): def __init__(self,groundtruthfile,n_particles,parent=None): # init parent class QMainWindow.__init__(self,parent) # load ground truth f = tables.openFile(groundtruthfile) self.true_traj = f.root.traj[:] self.true_map = f.root.staticMap[:] f.close() self.n_particles = n_particles self.create_widgets() self.logfiles = [] self.frame = 0 self.data_loaded = False self.poses = [] self.maps = [] self.particle_poses = [] self.weights = [] def create_widgets(self): self.top_widget = QWidget() self.fig = Figure(figsize=(12,6)) self.canvas = FigureCanvas(self.fig) gs = GridSpec(2,4) # main axes self.ax = self.fig.add_subplot(gs[:,0:2]) self.ax.plot( self.true_traj[:,0], self.true_traj[:,1],'k' ) self.ax.plot( self.true_map[:,0], self.true_map[:,1],'k*') # particle scatterplot axes self.ax_particles = self.fig.add_subplot(gs[0,2]) self.ax_particles.set_xmargin(0.1) self.ax_particles.set_ymargin(0.1) # particle weights axes self.ax_weights = self.fig.add_subplot(gs[0,3]) # cardinality axes self.ax_cn = self.fig.add_subplot(gs[1,2:]) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.background_particles = self.canvas.copy_from_bbox(self.ax_particles.bbox) # define animated artists init_x = [self.true_traj[0,0]] init_y = [self.true_traj[0,1]] self.est_pose, = self.ax.plot(init_x, init_y, 'rd', ms=8, mec='r', mew=2,mfc='None',animated=True ) self.est_traj, = self.ax.plot(init_x, init_y, 'r--',animated=True) self.est_map = [] self.zlines = [] for i in xrange(100): l, = self.ax.plot([0],[0],color='b',linestyle='None',animated=True) self.est_map.append(l) l, = self.ax.plot([0],[0],color='g',linestyle='None',animated=True) self.zlines.append(l) self.particles, = self.ax.plot(init_x*ones(self.n_particles), init_y*ones(self.n_particles),color='b', animated=True,marker=',',ls='None') self.particles2, = self.ax_particles.plot(init_x*ones(self.n_particles), init_y*ones(self.n_particles), color='b',animated=True, marker='.',ls='None') self.load_button = QPushButton('Load Data') self.play_button = QPushButton('Play') self.play_button.setEnabled(False) self.play_button.setCheckable(True) self.timer = QTimer() self.timer.setInterval(100) # signals and slots self.connect(self.load_button,SIGNAL('clicked()'),self.load_callback) self.connect(self.play_button,SIGNAL('clicked()'),self.play_callback) self.connect(self.timer,SIGNAL('timeout()'),self.timer_callback) hbox = QHBoxLayout() hbox.addWidget(self.load_button) hbox.addWidget(self.play_button) vbox = QVBoxLayout() vbox.addWidget(self.canvas) vbox.addLayout(hbox) self.top_widget.setLayout(vbox) self.setCentralWidget(self.top_widget) def update_plot(self): if not self.data_loaded: return if self.frame >= self.n_steps: return # append the pose to the estimated trajectory pose = self.poses[self.frame,:] traj_x = self.poses[0:self.frame,0] traj_y = self.poses[0:self.frame,1] self.est_traj.set_xdata(traj_x) self.est_traj.set_ydata(traj_y) self.est_pose.set_xdata(pose[0]) self.est_pose.set_ydata(pose[1]) # compute feature ellipses features = self.maps[self.frame] ellipses = [ g.draw(10) for g in features ] # restore the background self.canvas.restore_region(self.background) self.canvas.restore_region(self.background_particles) # draw the animated elements particles = self.particle_poses[self.frame] self.ax.draw_artist(self.est_traj) self.ax.draw_artist(self.est_pose) n_features = len(ellipses) for i in xrange(n_features): l = self.est_map[i] e = ellipses[i] l.set_xdata(e[:,0]) l.set_ydata(e[:,1]) l.set_linestyle('-') self.ax.draw_artist(l) self.particles.set_xdata(particles[0,:]) self.particles.set_ydata(particles[1,:]) self.ax.draw_artist(self.particles) self.canvas.blit(self.ax.bbox) self.particles2.set_xdata(particles[0,:]) self.particles2.set_ydata(particles[1,:]) self.ax_particles.draw_artist(self.particles2) self.ax_particles.set_xlim( left = min(particles[0,:]), right = max(particles[0,:])) self.ax_particles.set_ylim( bottom = min(particles[1,:]), top = max(particles[1,:])) self.canvas.blit(self.ax_particles.bbox) self.frame += 1 def keyPressEvent(self,evt): QMainWindow.keyPressEvent() print 'key pressed: ',evt.key if evt.key == Qt.Key_Escape: self.close() def resizeEvent(self,evt): self.ax.clear() self.ax.plot( self.true_traj[:,0], self.true_traj[:,1],'k' ) self.ax.plot( self.true_map[:,0], self.true_map[:,1],'k*') self.ax_particles.clear() self.ax_cn.clear() self.ax_weights.clear() self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.background_particles = self.canvas.copy_from_bbox(self.ax_particles.bbox) def load_data(self): data_dir = QFileDialog.getExistingDirectory(plot, 'Select data directory') pattern = join( str(data_dir),'state_estimate*.log' ) self.logfiles = glob.glob( pattern ) self.logfiles.sort() self.n_steps = len(self.logfiles) self.frame = 0 self.poses = empty([self.n_steps,2]) self.maps = [] self.weights = [] self.particle_poses = [] for k in xrange(self.n_steps): print k f = open(self.logfiles[k]) pose = fromstring( f.readline(), sep=' ' ) map_all = fromstring( f.readline(), sep=' ' ) particle_weights = fromstring( f.readline(), sep=' ' ) particles = fromstring( f.readline(), sep=' ' ) particles = vstack((particles[0::6],particles[1::6])) f.close() n_features = map_all.size/7 est_map = [] for i in xrange(n_features): ii = i*7 weight = map_all[ii] if weight >= 0.33: mean = array([map_all[ii+1],map_all[ii+2]]) # cov matrix stored in col major format cov = array([[map_all[ii+3], map_all[ii+5]], [map_all[ii+4], map_all[ii+6]]]) est_map.append( Gaussian2D(weight,mean,cov) ) self.poses[k,:] = pose[0:2] self.weights.append(particle_weights) self.particle_poses.append(particles) self.maps.append(est_map) self.data_loaded = True self.play_button.setEnabled(True) self.frame = 0 def load_callback(self): self.load_data() def play_callback(self): if not self.timer.isActive(): self.timer.start() self.play_button.setChecked(True) else: self.timer.stop() self.play_button.setChecked(False) def timer_callback(self): self.update_plot()
class Widget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self._ind = None self.showVerts = True QVBoxLayout(self) self.figure = Figure() self.canvas = FigureCanvas(self.figure) self.canvas.setParent(self) codes, verts = zip(*pathdata) path = mpath.Path(verts, codes) self.patch = mpatches.PathPatch(path, facecolor='red', edgecolor='yellow', alpha=0.5) self.axes = self.figure.add_subplot(111) self.axes.add_patch(self.patch) self.patch.set_animated(True) x, y = zip(*path.vertices) self.line, = self.axes.plot(x, y, 'go-', animated=True) self.axes.grid() self.axes.set_xlim(-3, 4) self.axes.set_ylim(-3, 4) self.axes.set_title('spline paths') self.canvas.mpl_connect('draw_event', self.drawCallback) self.canvas.mpl_connect('button_press_event', self.buttonPressCallback) self.canvas.mpl_connect('button_release_event', self.buttonReleaseCallback) self.canvas.mpl_connect('motion_notify_event', self.motionNotifyCallback) self.layout().addWidget(self.canvas) # Callbacks def drawCallback(self, event): self.background = self.canvas.copy_from_bbox(self.axes.bbox) self.axes.draw_artist(self.patch) self.axes.draw_artist(self.line) self.canvas.blit(self.axes.bbox) def buttonPressCallback(self, event): if (not self.showVerts) or (event.inaxes == None) or (event.button != 1): return self._ind = self.getIndUnderPoint(event) def buttonReleaseCallback(self, event): if (not self.showVerts) or (event.button != 1): return self._ind = None def keyPressEvent(self, event): if event.key() == Qt.Key_T: self.showVerts = not self.showVerts self.line.set_visible(self.showVerts) if not self.showVerts: self._ind = None self.canvas.draw() event.accept() else: QWidget.keyPressEvent(self, event) def motionNotifyCallback(self, event): if (not self.showVerts) or (self._ind == None) or ( event.inaxes == None) or (event.button != 1): return x, y = event.xdata, event.ydata vertices = self.patch.get_path().vertices vertices[self._ind] = x, y self.line.set_data(zip(*vertices)) self.canvas.restore_region(self.background) self.axes.draw_artist(self.patch) self.axes.draw_artist(self.line) self.canvas.blit(self.axes.bbox) # Other methods def getIndUnderPoint(self, event): xy = np.asarray(self.patch.get_path().vertices) xyt = self.patch.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2) ind = d.argmin() if d[ind] >= epsilon: ind = None return ind
class MainWindow(QMainWindow): analyzed_data_received_event = QtCore.Signal(object) def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self._ui = Ui_MainWindow() self._ui.setupUi(self) self.device = None self.analyzer = None self.in_error = False self.current_expected_pcm_clock_rate = None self.error_ticks = 0 self.error_counts = 0 self.audio = pyaudio.PyAudio() self.event_listener = SaleaeEventListener() self.event_listener.saleae_event_received.connect(self.on_saleae_event) self.play_sound_thread = SoundOutputRenderer(self.audio) self.analyzed_data_received_event.connect(self.play_sound_thread.on_data_received) PyDevicesManager.register_listener(self.event_listener, EVENT_ID_ONCONNECT) PyDevicesManager.register_listener(self.event_listener, EVENT_ID_ONDISCONNECT) PyDevicesManager.register_listener(self.event_listener, EVENT_ID_ONERROR) PyDevicesManager.register_listener(self.event_listener, EVENT_ID_ONREADDATA) PyDevicesManager.register_listener(self.event_listener, EVENT_ID_ONANALYZERDATA) self.audio_output_devices = [] for i in range(self.audio.get_device_count()): info = self.audio.get_device_info_by_index(i) if info['maxOutputChannels'] > 0: self.audio_output_devices.append(info) self.initialize_ui_items() self.recording_state = STATE_IDLE self.last_record_start = time.clock() self.realtime_timer = QtCore.QTimer() self.realtime_timer.timeout.connect(self.realtime_timer_timeout) self.plot_timer = QtCore.QTimer() self.plot_timer.timeout.connect(self.plot_timer_timeout) self.figure = Figure(dpi=100) self.plotCanvas = FigureCanvas(self.figure) self.plotCanvas.setParent(self._ui.plotWidget) # Hook this up so we can resize the plot canvas dynamically self._ui.plotWidget.installEventFilter(self) self.fft_axis = self.figure.add_subplot(111) self.fft_line = None ytick_values = range(-140, -6, 6) self.fft_axis.set_yticks(ytick_values) self.fft_axis.set_yticklabels(["%d" % w for w in ytick_values], size='xx-small') self.fft_axis.set_xlabel("Frequency (kHz)", size='small') self.fft_axis.set_ylabel("dBFS", size='small') self.fft_axis.grid(True) self.fft_axis.autoscale(enable=False, axis='both') self.plot_background = None self.update_controls() self.show_message("Waiting for a Logic device to connect...") self.event_listener.start() PyDevicesManager.begin_connect() def initialize_ui_items(self,): self._ui.onDecodeErrorComboBox.addItems(ON_DECODE_ERROR_OPTS) self._ui.onDecodeErrorComboBox.setCurrentIndex(HALT) self._ui.frameAlignmentComboBox.addItems(FRAME_ALIGNMENTS) self._ui.frameAlignmentComboBox.setCurrentIndex(FRAME_ALIGN_LAST_BIT) self._ui.clockEdgeComboBox.addItems(EDGES) self._ui.clockEdgeComboBox.setCurrentIndex(FALLING_EDGE) self._ui.outputLocationLineEdit.setText(RUN_PATH) for item in self.audio_output_devices: self._ui.comboOutputDeviceSelection.addItem(item['name'], item) # Select the default audio output default = self.audio.get_default_output_device_info() index = self._ui.comboOutputDeviceSelection.findData(default) if index < 0: index = 0 self._ui.comboOutputDeviceSelection.setCurrentIndex(index) num_channels = self._ui.channelsPerFrameSpinBox.value() self._ui.comboPCMChannelToListenTo.addItems(['%d' % w for w in range(1, num_channels + 1)]) self._ui.comboPCMChannelToListenTo.setCurrentIndex(0) def show_message(self, msg): self._ui.messagesLabel.setText(msg) def start_recording(self, mode): # Create an analyzer channels_per_frame = self._ui.channelsPerFrameSpinBox.value() sampling_rate = int(self._ui.samplingRateLineEdit.text()) bits_per_channel = self._ui.bitsPerChannelSpinBox.value() clock_channel = self._ui.clockChannelSpinBox.value() frame_channel = self._ui.frameChannelSpinBox.value() data_channel = self._ui.dataChannelSpinBox.value() clock_edge = self._ui.clockEdgeComboBox.currentIndex() frame_edge = LEADING_EDGE self.current_expected_pcm_clock_rate = \ channels_per_frame * sampling_rate * bits_per_channel if clock_edge == LEADING_EDGE: frame_edge = FALLING_EDGE decode_error = self._ui.onDecodeErrorComboBox.currentIndex() frame_transition = self._ui.frameAlignmentComboBox.currentIndex() output_dir = None if mode == MODE_WRITE_TO_FILE: output_dir = self._ui.outputLocationLineEdit.text() plot_spectrum = self._ui.checkboxShowSpectrum.isChecked() self.analyzer = PCMAnalyzer( output_folder = output_dir, audio_channels_per_frame = channels_per_frame, audio_sampling_rate_hz = sampling_rate, bits_per_channel = bits_per_channel, clock_channel = clock_channel, frame_channel = frame_channel, data_channel = data_channel, frame_align = frame_edge, frame_transition = frame_transition, clock_edge = clock_edge, on_decode_error = decode_error, calculate_ffts = plot_spectrum, logging = False) # Do not enable this unless you have a HUUUGE hard drive! self.device.set_analyzer(self.analyzer) self.device.set_active_channels(list(range(4))) rate = self.device.get_analyzer().get_minimum_acquisition_rate() self.device.set_sampling_rate_hz(rate) self.device.set_use_5_volts(False) self.recording_state = STATE_WAITING_FOR_FRAME self.last_record_start = time.clock() self.realtime_timer.start(100) if plot_spectrum: self.plot_timer.start(150) if mode == MODE_LISTEN: # Configure the audio player data = self._ui.comboOutputDeviceSelection.itemData( self._ui.comboOutputDeviceSelection.currentIndex()) format = pyaudio.paInt16 if bits_per_channel > 16: format = pyaudio.paInt32 self.play_sound_thread.configure(data['index'], format=format, channels=1, rate = sampling_rate, channel_to_play=self._ui.comboPCMChannelToListenTo.currentIndex()) self.play_sound_thread.start() self.show_message("Waiting for valid frame...") self.device.read_start() self.update_controls() def stop_recording(self,): self.reset() self.recording_state = STATE_IDLE self.show_message("Recording stopped.") self.update_controls() self.validate() def eventFilter(self, object, event): if event.type() == QtCore.QEvent.Resize: if object == self._ui.plotWidget: self.update_plot_canvas() return QWidget.eventFilter(self, object, event) @QtCore.Slot() def on_recordButton_clicked(self,): if self._ui.recordButton.text() == 'Record': self.start_recording(MODE_WRITE_TO_FILE) elif self._ui.recordButton.text() == 'Listen': self.start_recording(MODE_LISTEN) else: self.stop_recording() def realtime_timer_timeout(self,): elapsed = time.clock() - self.last_record_start time_text = "%d:%02.1f" % (elapsed / 60, elapsed % 60) self._ui.recordTimeLabel.setText(time_text) if self.in_error: if self.error_ticks > 15: self._ui.messagesLabel.setStyleSheet("background-color: none;") self.show_message("Continuing recording (last error: %s)..." % time_text) self.error_ticks = 0 self.error_counts = 0 self.in_error = False else: self._ui.messagesLabel.setStyleSheet("background-color: red;") self.error_ticks += 1 def plot_timer_timeout(self, d=None): if self.device is not None and self.device.get_analyzer() is not None: analyzer = self.device.get_analyzer() data = analyzer.get_latest_fft_data(purge=True) if data is not None: self.update_plot(data) @QtCore.Slot() def on_outputLocationBrowseButton_clicked(self,): # Prompt for file name dirname = QFileDialog.getExistingDirectory(self, "Select Output Folder", self._ui.outputLocationLineEdit.text(), QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) # Returns a tuple of (filename, filetype) if dirname is not None and len(dirname) > 0: self._ui.outputLocationLineEdit.setText(dirname) @QtCore.Slot(int) def on_channelsPerFrameSpinBox_valueChanged(self, i): self._ui.comboPCMChannelToListenTo.clear() num_channels = self._ui.channelsPerFrameSpinBox.value() self._ui.comboPCMChannelToListenTo.addItems(['%d' % w for w in range(1, num_channels + 1)]) self._ui.comboPCMChannelToListenTo.setCurrentIndex(0) self.validate() @QtCore.Slot(int) def on_clockChannelSpinBox_valueChanged(self, i): self.validate() @QtCore.Slot(int) def on_frameChannelSpinBox_valueChanged(self, i): self.validate() @QtCore.Slot(int) def on_dataChannelSpinBox_valueChanged(self, i): self.validate() @QtCore.Slot(int) def on_comboPCMChannelToListenTo_currentIndexChanged(self, i): if self.play_sound_thread is not None: self.play_sound_thread.channel_to_play = self._ui.comboPCMChannelToListenTo.currentIndex() @QtCore.Slot(int) def on_comboOutputDeviceSelection_currentIndexChanged(self, i): pass @QtCore.Slot() def on_tabOutputRenderer_currentChanged(self,): if self.recording_state != STATE_IDLE: self.stop_recording() self.update_controls() def update_controls(self,): is_recording = (self.recording_state != STATE_IDLE) if not is_recording: current_tab = self._ui.tabOutputRenderer.currentWidget() if current_tab == self._ui.recordToFile: self._ui.recordButton.setText("Record") elif current_tab == self._ui.outputToSoundCard: self._ui.recordButton.setText("Listen") else: self._ui.recordButton.setText("Stop") self._ui.comboOutputDeviceSelection.setEnabled(not is_recording) self._ui.outputLocationBrowseButton.setEnabled(not is_recording) self._ui.logicGroupBox.setEnabled(not is_recording) self._ui.pcmGroupBox.setEnabled(not is_recording) self._ui.outputGroupBox.setEnabled(not is_recording) self._ui.checkboxShowSpectrum.setEnabled(not is_recording) self.update_plot_canvas() def update_plot_canvas(self,): self.fft_axis.set_xlim(0, int(self._ui.samplingRateLineEdit.text()) / 2) freq_values = range(0, int(self._ui.samplingRateLineEdit.text()) / 2, 1000) + \ [int(self._ui.samplingRateLineEdit.text()) / 2] self.fft_axis.set_xticks(freq_values) self.fft_axis.set_xticklabels(["%d" % (w / 1000) for w in freq_values], size='xx-small') self.plotCanvas.resize(self._ui.plotWidget.size().width(), self._ui.plotWidget.size().height()) self.plotCanvas.draw() self.plot_background = None def validate(self,): valid = False if self.device is not None: if (self._ui.clockChannelSpinBox.value() != self._ui.frameChannelSpinBox.value()) and \ (self._ui.frameChannelSpinBox.value() != self._ui.dataChannelSpinBox.value()) and \ (self._ui.clockChannelSpinBox.value() != self._ui.dataChannelSpinBox.value()): dirname = self._ui.outputLocationLineEdit.text() if dirname is not None and len(dirname) > 0: valid = True self.set_valid(valid) def set_valid(self, is_valid): self._ui.recordButton.setEnabled(is_valid) def on_saleae_event(self, event, device): analyzer = None if device is not None and device == self.device and \ self.device.get_analyzer() is not None: analyzer = self.device.get_analyzer() if event.id == EVENT_ID_ONCONNECT: self.show_message("Device connected with id %d" % device.get_id()) self.device = device self.update_controls() self.validate() elif event.id == EVENT_ID_ONERROR: if self._ui.onDecodeErrorComboBox.currentIndex() == HALT: self.stop_recording() self.show_message("ERROR: %s" % event.data) else: if not self.in_error: self.in_error = True self.show_message("ERROR: %s" % event.data) else: self.error_counts += 1 if self.error_counts > 5: self.stop_recording() self.show_message("Too many errors! %s" % event.data) elif event.id == EVENT_ID_ONDISCONNECT: self.recording_state = STATE_IDLE self.show_message("Device id %d disconnected." % device.get_id()) self.shutdown() elif event.id == EVENT_ID_ONREADDATA: if analyzer is not None: if self.recording_state == STATE_WAITING_FOR_FRAME: if self.device.get_analyzer().first_valid_frame_received(): self.show_message("Recording. Press 'Stop' to stop recording.") self.recording_state = STATE_RECORDING elif event.id == EVENT_ID_ONANALYZERDATA: if self.recording_state == STATE_RECORDING and \ self.current_expected_pcm_clock_rate is not None: # Sanity check the sampling rate with the detected clock frequency if analyzer is not None: clock_period_samples = self.device.get_analyzer().get_average_clock_period_in_samples() meas_clock_freq = self.device.get_sampling_rate_hz() / clock_period_samples if (1.2 * self.current_expected_pcm_clock_rate) < meas_clock_freq or \ (0.8 * self.current_expected_pcm_clock_rate) > meas_clock_freq: # The user's setup is probably wrong, so bail immediately self.stop_recording() self.show_message("Detected a PCM clock of ~%d Hz. Check your settings!" % meas_clock_freq) self.analyzed_data_received_event.emit(event.data) def update_plot(self, data): if self.plot_background is None: self.plot_background = self.plotCanvas.copy_from_bbox(self.fft_axis.bbox) channel = self._ui.comboPCMChannelToListenTo.currentIndex() channel_data = data[channel] numpoints = len(channel_data) if self.fft_line is None: self.fft_line, = self.fft_axis.plot(numpy.zeros(numpoints), animated=True) sampling_rate = int(self._ui.samplingRateLineEdit.text()) freqs = numpy.fft.fftfreq(numpoints * 2, d=1.0 / float(sampling_rate)) # Restore the clean slate background (this is the 'blit' method, which # is much faster to render) self.plotCanvas.restore_region(self.plot_background, bbox=self.fft_axis.bbox) self.fft_line.set_ydata(channel_data) self.fft_line.set_xdata(freqs[:numpoints]) # Draw the line self.fft_axis.draw_artist(self.fft_line) # Blit the canvas self.plotCanvas.blit(self.fft_axis.bbox) def reset(self,): self.current_expected_pcm_clock_rate = None if self.device is not None: self.device.stop() self.analyzer = None self.realtime_timer.stop() self.plot_timer.stop() if self.play_sound_thread.isRunning(): self.play_sound_thread.quit() self.play_sound_thread.wait() self._ui.messagesLabel.setStyleSheet("background-color: none;") self.error_ticks = 0 self.error_counts = 0 self.in_error = False def shutdown(self,): self.recording_state = STATE_IDLE self.reset() self.device = None try: self.figure.close() except: pass def closeEvent(self, event): """Intercepts the close event of the MainWindow.""" self.show_message("Closing device...") try: self.shutdown() self.event_listener.quit() self.event_listener.wait() self.audio.terminate() finally: super(MainWindow, self).closeEvent(event)
class Widget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self._ind=None self.showVerts=True QVBoxLayout(self) self.figure=Figure() self.canvas=FigureCanvas(self.figure) self.canvas.setParent(self) codes, verts = zip(*pathdata) path = mpath.Path(verts, codes) self.patch = mpatches.PathPatch(path, facecolor='red', edgecolor='yellow', alpha=0.5) self.axes=self.figure.add_subplot(111) self.axes.add_patch(self.patch) self.patch.set_animated(True) x,y=zip(*path.vertices) self.line,=self.axes.plot(x, y, 'go-', animated=True) self.axes.grid() self.axes.set_xlim(-3,4) self.axes.set_ylim(-3,4) self.axes.set_title('spline paths') self.canvas.mpl_connect('draw_event', self.drawCallback) self.canvas.mpl_connect('button_press_event', self.buttonPressCallback) self.canvas.mpl_connect('button_release_event', self.buttonReleaseCallback) self.canvas.mpl_connect('motion_notify_event', self.motionNotifyCallback) self.layout().addWidget(self.canvas) # Callbacks def drawCallback(self, event): self.background = self.canvas.copy_from_bbox(self.axes.bbox) self.axes.draw_artist(self.patch) self.axes.draw_artist(self.line) self.canvas.blit(self.axes.bbox) def buttonPressCallback(self, event): if (not self.showVerts) or (event.inaxes==None) or (event.button!=1): return self._ind=self.getIndUnderPoint(event) def buttonReleaseCallback(self, event): if (not self.showVerts) or (event.button!=1): return self._ind=None def keyPressEvent(self, event): if event.key()==Qt.Key_T: self.showVerts=not self.showVerts self.line.set_visible(self.showVerts) if not self.showVerts: self._ind=None self.canvas.draw() event.accept() else: QWidget.keyPressEvent(self, event) def motionNotifyCallback(self, event): if (not self.showVerts) or (self._ind==None) or (event.inaxes==None) or (event.button!=1): return x,y=event.xdata,event.ydata vertices=self.patch.get_path().vertices vertices[self._ind]=x,y self.line.set_data(zip(*vertices)) self.canvas.restore_region(self.background) self.axes.draw_artist(self.patch) self.axes.draw_artist(self.line) self.canvas.blit(self.axes.bbox) # Other methods def getIndUnderPoint(self, event): xy=np.asarray(self.patch.get_path().vertices) xyt=self.patch.get_transform().transform(xy) xt,yt=xyt[:,0],xyt[:,1] d=np.sqrt((xt-event.x)**2 + (yt-event.y)**2) ind=d.argmin() if d[ind]>=epsilon: ind=None return ind
class StreamViewerWidget(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ trace_selected = QtCore.Signal(int) selection_made = QtCore.Signal(bool) def __init__(self, parent, stream=None): super(StreamViewerWidget, self).__init__(parent) self.fig = plt.figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy( QtGui.QSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)) self.canvas.setMinimumHeight(320) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocus() self.graphArea = QtGui.QScrollArea(self) self.graphArea.setWidget(self.canvas) self.graphArea.setWidgetResizable(True) self.graphArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.graphArea) # Animation related attrs. self.background = [] self.animated = False self.size = (self.fig.bbox.width, self.fig.bbox.height) # Set TracePlot list self.trace_plots = [] self.stream = None if stream is not None: self.set_stream(stream) # Event handling self.visible_axes = [] self._selected_traces = set() self.shift_pressed = False self.press_selector = None self.fig.canvas.mpl_connect('motion_notify_event', self.on_move) self.fig.canvas.mpl_connect('button_press_event', self.on_press) self.fig.canvas.mpl_connect('key_press_event', self.on_key_press) self.fig.canvas.mpl_connect('key_release_event', self.on_key_release) @property def selected_traces(self): if self.stream is not None: return [self.stream.traces[i] for i in self._selected_traces] return [] def on_move(self, event): axes_selected = False for i, axes in enumerate(self.fig.axes): if axes.get_visible(): ymin, ymax = axes.get_position().ymin, axes.get_position().ymax xmin, xmax = axes.get_position().xmin, axes.get_position().xmax xfig, yfig = self._event_to_fig_coords(event) if ymin <= yfig <= ymax and xmin <= xfig <= xmax: self.canvas.setToolTip(self.stream.traces[i].name) axes_selected = True break if not axes_selected: self.canvas.setToolTip("") def on_key_press(self, event): if event.key == 'control': self.shift_pressed = True def on_key_release(self, event): self.shift_pressed = False def on_press(self, event): trace_selected = False if event.button == 1: # and event.dblclick: for i, ax in enumerate(self.fig.axes): if ax.get_visible(): ymin, ymax = ax.get_position().ymin, ax.get_position().ymax xmin, xmax = ax.get_position().xmin, ax.get_position().xmax xfig, yfig = self._event_to_fig_coords(event) if ymin <= yfig <= ymax and xmin <= xfig <= xmax: trace_selected = True if self.shift_pressed: if self._selected_traces: self.trace_selected.emit(i) self.selection_made.emit(True) self._selected_traces.add(i) else: self.trace_selected.emit(i) self.selection_made.emit(True) self._selected_traces = {i} break # if the user clicked out of any trace (and he's not using shift), then deselect all if not trace_selected and not self.shift_pressed: self._selected_traces = set() self.selection_made.emit(False) # Now update selection status on plots for i, plot in enumerate(self.trace_plots): plot.set_selected(i in self._selected_traces) self.draw() def _event_to_fig_coords(self, event): inv = self.fig.transFigure.inverted() return inv.transform((event.x, event.y)) def set_stream(self, stream): self.stream = stream self._selected_traces = set() # Clear canvas for plot in self.trace_plots: plot.remove() self.trace_plots = [] # Plot stream traces for i, trace in enumerate(self.stream.traces): self.trace_plots.append( TracePlot(self, trace, fig_nrows=len(stream), ax_pos=i + 1)) # Draw canvas self.subplots_adjust() self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) self.draw() def refresh_stream_data(self): for plot in self.trace_plots: plot.update_data() def draw(self): self.canvas.draw() #self.draw_animate() def draw_animate(self): size = self.fig.bbox.width, self.fig.bbox.height if size != self.size: self.size = size self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) self.canvas.restore_region(self.background) for artist in self._get_animated_artists(): if artist.get_visible(): ax = artist.get_axes() if ax is not None: if artist.get_axes().get_visible(): self.fig.draw_artist(artist) else: self.fig.draw_artist(artist) self.canvas.blit(self.fig.bbox) def _get_animated_artists(self): artists = [] for ax in self.fig.axes: artists.extend(ax.images) artists.extend(ax.lines) artists.append(ax.xaxis) artists.append(ax.yaxis) artists.extend(ax.patches) artists.extend(ax.spines.values()) for artist in artists: if artist.get_animated(): yield artist def set_visible(self, value): self.canvas.setVisible(value) def get_visible(self): return self.canvas.isVisible() def remove_trace(self, idx): self.trace_plots.pop(idx).remove() def subplots_adjust(self): visible_subplots = [ ax for ax in self.fig.get_axes() if ax.get_visible() ] for i, ax in enumerate(visible_subplots): correct_geometry = (len(visible_subplots), 1, i + 1) if correct_geometry != ax.get_geometry(): ax.change_geometry(len(visible_subplots), 1, i + 1) # Adjust space between subplots self.fig.subplots_adjust(left=0.02, right=0.98, bottom=0.02, top=0.98, hspace=0.05) def showEvent(self, event): self.draw() def resizeEvent(self, event): self.draw() def update_markers(self): for plot in self.trace_plots: plot.update_markers() self.draw() def visualize_stream_range(self, start_trace=None, end_trace=None): for i, ax in enumerate(self.fig.axes): ax.set_visible(start_trace <= i < end_trace) self.subplots_adjust() self.canvas.draw()
class PlotCanvas: """ Class handling the plotting area in the application. """ def __init__(self, container): """ The constructor configures the Matplotlib figure that will contain all plots, creates the base axes and connects events to the plotting area. :param container: The parent container in which to draw plots. :rtype: PlotCanvas """ # Options self.x_margin = 15 # pixels self.y_margin = 25 # Pixels # Parent container self.container = container # Plots go onto a single matplotlib.figure self.figure = Figure(dpi=50) # TODO: dpi needed? self.figure.patch.set_visible(False) # These axes show the ticks and grid. No plotting done here. # New axes must have a label, otherwise mpl returns an existing one. self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0) self.axes.set_aspect(1) self.axes.grid(True) # The canvas is the top level container (Gtk.DrawingArea) self.canvas = FigureCanvas(self.figure) # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) # self.canvas.setFocus() #self.canvas.set_hexpand(1) #self.canvas.set_vexpand(1) #self.canvas.set_can_focus(True) # For key press # Attach to parent #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? self.container.addWidget(self.canvas) # Qt # Copy a bitmap of the canvas for quick animation. # Update every time the canvas is re-drawn. self.background = self.canvas.copy_from_bbox(self.axes.bbox) # Events self.canvas.mpl_connect('button_press_event', self.on_mouse_press) self.canvas.mpl_connect('button_release_event', self.on_mouse_release) self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) #self.canvas.connect('configure-event', self.auto_adjust_axes) self.canvas.mpl_connect('resize_event', self.auto_adjust_axes) #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) #self.canvas.connect("scroll-event", self.on_scroll) self.canvas.mpl_connect('scroll_event', self.on_scroll) self.canvas.mpl_connect('key_press_event', self.on_key_down) self.canvas.mpl_connect('key_release_event', self.on_key_up) self.canvas.mpl_connect('draw_event', self.on_draw) self.mouse = [0, 0] self.key = None self.pan_axes = [] self.panning = False def on_key_down(self, event): """ :param event: :return: """ FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key)) self.key = event.key def on_key_up(self, event): """ :param event: :return: """ self.key = None def mpl_connect(self, event_name, callback): """ Attach an event handler to the canvas through the Matplotlib interface. :param event_name: Name of the event :type event_name: str :param callback: Function to call :type callback: func :return: Connection id :rtype: int """ return self.canvas.mpl_connect(event_name, callback) def mpl_disconnect(self, cid): """ Disconnect callback with the give id. :param cid: Callback id. :return: None """ self.canvas.mpl_disconnect(cid) def connect(self, event_name, callback): """ Attach an event handler to the canvas through the native GTK interface. :param event_name: Name of the event :type event_name: str :param callback: Function to call :type callback: function :return: Nothing """ self.canvas.connect(event_name, callback) def clear(self): """ Clears axes and figure. :return: None """ # Clear self.axes.cla() try: self.figure.clf() except KeyError: FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()") # Re-build self.figure.add_axes(self.axes) self.axes.set_aspect(1) self.axes.grid(True) # Re-draw self.canvas.draw_idle() def adjust_axes(self, xmin, ymin, xmax, ymax): """ Adjusts all axes while maintaining the use of the whole canvas and an aspect ratio to 1:1 between x and y axes. The parameters are an original request that will be modified to fit these restrictions. :param xmin: Requested minimum value for the X axis. :type xmin: float :param ymin: Requested minimum value for the Y axis. :type ymin: float :param xmax: Requested maximum value for the X axis. :type xmax: float :param ymax: Requested maximum value for the Y axis. :type ymax: float :return: None """ # FlatCAMApp.App.log.debug("PC.adjust_axes()") width = xmax - xmin height = ymax - ymin try: r = width / height except ZeroDivisionError: FlatCAMApp.App.log.error("Height is %f" % height) return canvas_w, canvas_h = self.canvas.get_width_height() canvas_r = float(canvas_w) / canvas_h x_ratio = float(self.x_margin) / canvas_w y_ratio = float(self.y_margin) / canvas_h if r > canvas_r: ycenter = (ymin + ymax) / 2.0 newheight = height * r / canvas_r ymin = ycenter - newheight / 2.0 ymax = ycenter + newheight / 2.0 else: xcenter = (xmax + xmin) / 2.0 newwidth = width * canvas_r / r xmin = xcenter - newwidth / 2.0 xmax = xcenter + newwidth / 2.0 # Adjust axes for ax in self.figure.get_axes(): if ax._label != 'base': ax.set_frame_on(False) # No frame ax.set_xticks([]) # No tick ax.set_yticks([]) # No ticks ax.patch.set_visible(False) # No background ax.set_aspect(1) ax.set_xlim((xmin, xmax)) ax.set_ylim((ymin, ymax)) ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio]) # Sync re-draw to proper paint on form resize self.canvas.draw() def auto_adjust_axes(self, *args): """ Calls ``adjust_axes()`` using the extents of the base axes. :rtype : None :return: None """ xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() self.adjust_axes(xmin, ymin, xmax, ymax) def zoom(self, factor, center=None): """ Zooms the plot by factor around a given center point. Takes care of re-drawing. :param factor: Number by which to scale the plot. :type factor: float :param center: Coordinates [x, y] of the point around which to scale the plot. :type center: list :return: None """ xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() width = xmax - xmin height = ymax - ymin if center is None or center == [None, None]: center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0] # For keeping the point at the pointer location relx = (xmax - center[0]) / width rely = (ymax - center[1]) / height new_width = width / factor new_height = height / factor xmin = center[0] - new_width * (1 - relx) xmax = center[0] + new_width * relx ymin = center[1] - new_height * (1 - rely) ymax = center[1] + new_height * rely # Adjust axes for ax in self.figure.get_axes(): ax.set_xlim((xmin, xmax)) ax.set_ylim((ymin, ymax)) # Async re-draw self.canvas.draw_idle() def pan(self, x, y): xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() width = xmax - xmin height = ymax - ymin # Adjust axes for ax in self.figure.get_axes(): ax.set_xlim((xmin + x * width, xmax + x * width)) ax.set_ylim((ymin + y * height, ymax + y * height)) # Re-draw self.canvas.draw_idle() def new_axes(self, name): """ Creates and returns an Axes object attached to this object's Figure. :param name: Unique label for the axes. :return: Axes attached to the figure. :rtype: Axes """ return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) def on_scroll(self, event): """ Scroll event handler. :param event: Event object containing the event information. :return: None """ # So it can receive key presses # self.canvas.grab_focus() self.canvas.setFocus() # Event info # z, direction = event.get_scroll_direction() if self.key is None: if event.button == 'up': self.zoom(1.5, self.mouse) else: self.zoom(1 / 1.5, self.mouse) return if self.key == 'shift': if event.button == 'up': self.pan(0.3, 0) else: self.pan(-0.3, 0) return if self.key == 'control': if event.button == 'up': self.pan(0, 0.3) else: self.pan(0, -0.3) return def on_mouse_press(self, event): # Check for middle mouse button press if event.button == 2: # Prepare axes for pan (using 'matplotlib' pan function) self.pan_axes = [] for a in self.figure.get_axes(): if (event.x is not None and event.y is not None and a.in_axes(event) and a.get_navigate() and a.can_pan()): a.start_pan(event.x, event.y, 1) self.pan_axes.append(a) # Set pan view flag if len(self.pan_axes) > 0: self.panning = True; def on_mouse_release(self, event): # Check for middle mouse button release to complete pan procedure if event.button == 2: for a in self.pan_axes: a.end_pan() # Clear pan flag self.panning = False def on_mouse_move(self, event): """ Mouse movement event hadler. Stores the coordinates. Updates view on pan. :param event: Contains information about the event. :return: None """ self.mouse = [event.xdata, event.ydata] # Update pan view on mouse move if self.panning is True: for a in self.pan_axes: a.drag_pan(1, event.key, event.x, event.y) # Async re-draw (redraws only on thread idle state, uses timer on backend) self.canvas.draw_idle() def on_draw(self, renderer): # Store background on canvas redraw self.background = self.canvas.copy_from_bbox(self.axes.bbox)
class InteractiveVelocityPlot(VelocityPlot): """ Visual tool to help with fitting Voigt profiles to absorption lines in QSO spectra. """ def __init__(self, filename, transitions, wavelength, flux, error, continuum, redshift, **kwargs): # Main window: self.window = QMainWindow() # Host widget for plot, push button, and checkboxes: self.main_frame = QWidget() # Spectrum filename: self.filename = filename # Plotting options: self.options = kwargs # Optimise screen usage: if len(transitions) > 20: ncols = int(ceil(len(transitions) / 10)) else: ncols = int(ceil(len(transitions) / 5)) # Initialise plot: super(InteractiveVelocityPlot, self).__init__( transitions, ncols=ncols, aspect=0.45, **self.options['WINDOW']) # Attach canvas to host widget: self.canvas = FigureCanvasQTAgg(self) self.canvas.setParent(self.main_frame) # Text input/output: self.text_output = QTextEdit() # Push buttons: self.load_button = QPushButton('&Load') self.save_button = QPushButton('&Save') self.preview_button = QPushButton('&Preview') self.clear_button = QPushButton('&Clear') self.refresh_plot_button = QPushButton('&Refresh plot') self.plot_model_button = QPushButton('&Plot model') self.plot_all_models_button = QPushButton('&Plot all models') self.clear_models_button = QPushButton('&Clear models') self.runvpfit_button = QPushButton('&Run VPFIT') self.set_resolution = QPushButton('&Set resolution') self.help = QPushButton('&Help') self.quit = QPushButton('&Quit') # Checkboxes: self.cos_fuv_checkbox = QCheckBox('&use COS FUV LSF') self.cos_nuv_checkbox = QCheckBox('&use COS NUV LSF') self.include_checkbox = QCheckBox('&include previous') # Push button `clicked` connections: self.load_button.clicked.connect(self.on_load) self.save_button.clicked.connect(self.on_save) self.preview_button.clicked.connect(self.on_preview) self.clear_button.clicked.connect(self.on_clear) self.refresh_plot_button.clicked.connect(self.on_refresh) self.plot_model_button.clicked.connect(self.on_plot_model) self.plot_all_models_button.clicked.connect(self.on_plot_all_models) self.clear_models_button.clicked.connect(self.on_clear_models) self.runvpfit_button.clicked.connect(self.on_runvpfit) self.set_resolution.clicked.connect(self.on_resolution) self.help.clicked.connect(self.on_help) self.quit.clicked.connect(self.window.close) # Checkbox `stateChanged` connections: self.cos_fuv_checkbox.stateChanged.connect(self.on_cos_fuv_checkbox) self.cos_nuv_checkbox.stateChanged.connect(self.on_cos_nuv_checkbox) self.include_checkbox.stateChanged.connect(self.on_include_checkbox) # Set up grid layout: grid = QGridLayout() grid.setSpacing(10) # Add widgets: grid.addWidget(self.text_output, 1, 1, 4, 3) grid.addWidget(self.load_button, 1, 4) grid.addWidget(self.save_button, 2, 4) grid.addWidget(self.preview_button, 3, 4) grid.addWidget(self.clear_button, 4, 4) grid.addWidget(self.refresh_plot_button, 1, 5) grid.addWidget(self.plot_model_button, 2, 5) grid.addWidget(self.plot_all_models_button, 3, 5) grid.addWidget(self.clear_models_button, 4, 5) grid.addWidget(self.runvpfit_button, 1, 6) grid.addWidget(self.cos_fuv_checkbox, 2, 6) grid.addWidget(self.cos_nuv_checkbox, 3, 6) grid.addWidget(self.include_checkbox, 4, 6) grid.addWidget(self.set_resolution, 1, 7) grid.addWidget(self.help, 3, 7) grid.addWidget(self.quit, 4, 7) # Place plotting canvas above the options grid: vbox = QVBoxLayout() vbox.addWidget(self.canvas) vbox.addLayout(grid) # Set the layout to the host widget: self.main_frame.setLayout(vbox) # Store all static elements (can be very slow to re-draw these): self.canvas.draw() self.backgrounds = [self.canvas.copy_from_bbox(ax.bbox) for ax in self.axes] # Plot the data: self.plot_data(wavelength, flux, error, continuum, redshift, **self.options['DATA']) # Set the window title: self.window.setWindowTitle('vpguess z = {0:.3f}'.format(self.redshift)) # Give keyboard focus to the figure: self.canvas.setFocusPolicy(Qt.StrongFocus) self.canvas.setFocus() # Variable defaults: self.cos_fuv = False self.cos_nuv = False self.include = False self.last_loaded = None self.resolution = None # Keypress variables: self.previous_keypress = None self.previous_wavelength = None # Set up the key-press events: self.canvas.mpl_connect('key_press_event', self.on_keypress) # Set the main frame as the central widget: self.window.setCentralWidget(self.main_frame) # Resize the window so it will display the canvas with the # requested size: layout_height = vbox.sizeHint().height() layout_width = vbox.sizeHint().width() status_bar_height = self.window.statusBar().sizeHint().height() height = layout_height + status_bar_height self.window.resize(layout_width, height) # Re-do labels: del self.texts[:] self.text(0.45, 0.02, 'Velocity offset (km s$^{-1}$)') self.text(0.01, 0.57, 'Transmission', rotation=90) # Disable any existing callbacks: self.cids = dict() cids1 = list(self.canvas.callbacks.callbacks['key_press_event']) cids2 = list(self.canvas.callbacks.callbacks['button_press_event']) cids = cids1 + cids2 for cid in cids: self.canvas.callbacks.disconnect(cid) # Connect new callbacks: self.connect() def update_plot(self, redshift): # Restore background regions: [self.canvas.restore_region(background) for background in self.backgrounds] # Plot data: [data.pop(0).remove() for data in self.data] self.plot_data( self.wavelength, self.flux, self.error, self.continuum, redshift, **self.options['DATA']) # Draw the artists: for artists in self.data: for element in artists: ax = element.get_axes() ax.draw_artist(element) # Plot the models (if any): if self.options['MODEL']['absorbers'] is not None: self.plot_models(resolution=self.resolution, convolve_with_cos_fuv=self.cos_fuv, convolve_with_cos_nuv=self.cos_nuv, **self.options['MODEL']) # Draw the artists: for artists in self.models: for element in artists: ax = element.get_axes() ax.draw_artist(element) # Draw new panel labels: for i, transition in enumerate(self.transitions): ax = self.axes[self._ind[i]] transf = blended_transform_factory(ax.transAxes, ax.transData) name = transition.name # Use TeX fonts if specified: if self.usetex: name = name.split() if name[0][1].islower(): name = name[0][:2] + '$\;$\\textsc{' + \ name[0][2:].lower() + '} $\lambda$' + name[1] else: name = name[0][:1] + '$\;$\\textsc{' + \ name[0][1:].lower() + '} $\lambda$' + name[1] label = ax.text(0.03, 0.5, name, fontsize=self.label_fontsize, bbox=bbox, transform=transf) ax.draw_artist(label) # Update: self.canvas.update() self.window.setWindowTitle( 'vpguess z = {0:.3f}'.format(self.redshift)) @staticmethod def concatenate_results(): from glob import glob results = glob('*.result') with open('.all_results', 'wb') as combined: for result in results: with open(result, 'rb') as single: combined.write(single.read()) @pyqtSlot(str) def on_output(self, output): self.text_output.moveCursor(QTextCursor.End) self.text_output.insertPlainText(output) def on_load(self): self.text_output.clear() directory = os.getcwd() filename = QFileDialog.getOpenFileName( self.window, 'Select f26 file', directory) with open(filename, 'rb') as f26: self.text_output.setText(f26.read()) self.last_loaded = filename def on_save(self): directory = os.getcwd() filename, ok = QFileDialog.getSaveFileName( self.window, 'Save f26 file', directory) with open(filename, 'w') as f26: f26.write(str(self.text_output.toPlainText())) self.text_output.clear() self.last_loaded = None def on_preview(self): self.concatenate_results() with open('.f26_preview', 'wb') as preview: with open('.all_results', 'rb') as results: preview.write(str(self.text_output.toPlainText())) preview.write(results.read()) f26 = read_f26('.f26_preview') self.options['MODEL']['absorbers'] = f26.absorbers self.update_plot(self.redshift) def on_clear(self): self.text_output.clear() self.last_loaded = None def on_refresh(self): self.update_plot(self.redshift) def on_plot_model(self): directory = os.getcwd() filename = QFileDialog.getOpenFileName( self.window, 'Select f26 file', directory) f26 = read_f26(filename) self.options['MODEL']['absorbers'] = f26.absorbers if f26.absorbers is not None: self.update_plot(self.redshift) def on_plot_all_models(self): self.concatenate_results() f26 = read_f26('.all_results') self.options['MODEL']['absorbers'] = f26.absorbers if f26.absorbers is not None: self.update_plot(self.redshift) def on_clear_models(self): self.options['MODEL']['absorbers'] = None self.update_plot(self.redshift) def on_runvpfit(self): from .vpfit import run_vpfit directory = os.getcwd() if self.last_loaded is not None: filename = self.last_loaded if self.text_output.document().isModified(): with open(filename, 'w') as f26: f26.write(str(self.text_output.toPlainText())) else: filename = QFileDialog.getSaveFileName( self.window, 'Save f26 file', directory) with open(filename, 'w') as f26: f26.write(str(self.text_output.toPlainText())) self.concatenate_results() inc = '.all_results' if self.include else None self.text_output.clear() fwhm = self.resolution if self.resolution is not None else 10 run_vpfit(filename, inc, fwhm=fwhm, cos_fuv=self.cos_fuv, cos_nuv=self.cos_nuv) self.concatenate_results() f26 = read_f26('.all_results') self.options['MODEL']['absorbers'] = f26.absorbers if f26.absorbers is not None: self.update_plot(self.redshift) self.last_loaded = None def on_cos_fuv_checkbox(self, state): if state == Qt.Checked: self.cos_fuv = True else: self.cos_fuv = False def on_cos_nuv_checkbox(self, state): if state == Qt.Checked: self.cos_nuv = True else: self.cos_nuv = False def on_include_checkbox(self, state): if state == Qt.Checked: self.include = True else: self.include = False def on_resolution(self): resolution, ok = SpectralResolutionDialog.get_spectral_resolution( self.main_frame) if ok: self.resolution = resolution def on_help(self): QMessageBox.information(self.main_frame, 'Help', info, False) def on_buttonpress(self, event): if event.inaxes is None: return i = self.axes.index(event.inaxes) transition = self.transitions[np.where(self._ind == i)[0]] z = (1 + event.xdata / c_kms) * (1 + self.redshift) - 1 wavelength = transition.wavelength.value * (1 + z) isort = np.argsort(self.ticks['wavelength']) wavelengths = self.ticks['wavelength'][isort] transitions = self.ticks['transition'][isort] idx = wavelengths.searchsorted(wavelength) wavelength = wavelengths[idx] transition = atom.get_transition(transitions[idx]) z = wavelength / transition.wavelength.value - 1 message = '{0}, z = {1:.3f}'.format(transition.name, z) QMessageBox.information(self.main_frame, 'Transition', message, False) def on_keypress(self, event): if event.key == ' ' and event.inaxes is not None: z = self.redshift # Get amount to add to redshift: dz = (event.xdata / c_kms) * (1 + z) # Get new axis limits, if any: vmin, vmax = event.inaxes.get_xlim() self.vmin = min(0, vmin) self.vmax = max(0, vmax) self.update_plot(z + dz) if event.key == 'z': redshift, ok = QInputDialog.getText( self.main_frame, 'New Redshift', 'Enter redshift: ', False) if ok: self.update_plot(float(redshift)) if event.key == 'b': i = self.axes.index(event.inaxes) transition = self.transitions[np.where(self._ind == i)[0]] z = (1 + event.xdata / c_kms) * (1 + self.redshift) - 1 wavelength = transition.wavelength.value * (1 + z) if (self.previous_keypress == 'b' and self.previous_wavelength is not None): wmin = self.previous_wavelength wmax = wavelength if wmin > wmax: wmin, wmax = wmax, wmin print('%% {0} 1 {1:.3f} {2:.3f} vfwhm=10.0'.format( self.filename, wmin, wmax)) self.previous_keypress = None self.previous_wavelength = None else: self.previous_wavelength = wavelength if event.key == 'l': from ..calculations.absorption import logn_from_tau_peak i = self.axes.index(event.inaxes) transition = self.transitions[np.where(self._ind == i)[0]] z = (1 + event.xdata / c_kms) * (1 + self.redshift) - 1 wavelength = transition.wavelength.value * (1 + z) index = self.wavelength.searchsorted(wavelength) flux = self.flux[index - 1:index + 1] error = self.error[index - 1:index + 1] continuum = self.continuum[index - 1:index + 1] flux_norm = flux / continuum error_norm = error / continuum valid = (error_norm > 0) & ~np.isnan(flux_norm) if not any(valid): print('No good pixels!') return flux_norm = np.median(flux_norm[valid]) error_norm = np.median(error_norm[valid]) if flux_norm < error_norm: flux_norm = error_norm elif flux_norm > 1 - error_norm: flux_norm = 1 - error_norm b = 20 # assume 20 km/s tau = -np.log(flux_norm) logn = logn_from_tau_peak(transition, tau, b) print('{0:6s} {1:8.6f} 0.0 {2:4.1f} 0.0 {3:4.1f} 0.0'.format( transition.parent, z, b, logn)) if event.key == 'S': filename, ok = QInputDialog.getText( self.main_frame, 'Save Figure', 'Enter filename: ', False) if ok: self.savefig(filename) self.previous_keypress = event.key def connect(self): cids = dict() cids['key_press_event'] = self.canvas.mpl_connect( 'key_press_event', self.on_keypress) cids['button_press_event'] = self.canvas.mpl_connect( 'button_press_event', self.on_buttonpress) self.cids.update(cids)
class AnalysisEditor(QWidget): def __init__(self, ui): QWidget.__init__(self) self.ui = ui self.chooseAnalysis = QPushButton() self.chooseAnalysis.setText("Choose Analysis") self.chooseAnalysis.setFixedWidth(100) self.lineEdit = QLineEdit() self.chooseAnalysis.clicked.connect(self.selectAnalysis) self.analysisFig = plt.figure() self.analysisFig.patch.set_facecolor('white') self.analysisFig.clf() self.analysisCanvas = FigureCanvas(self.analysisFig) self.toolbar = NavigationToolbar(self.analysisCanvas, self) self.analysisOperationsLayout = self.createOperationsLayout() self.analysisAx = self.analysisFig.add_subplot(1,1,1) #self.gridLayout.addLayout(analysisOperationsLayout,1,7) def initCanvas(self): self.lineEdit.clear() self.analysisAx.cla() self.analysisAx = self.analysisFig.add_subplot(1,1,1) self.analysisCanvas.draw() def analysisOptionHandler(self): sender = self.sender() if sender.isChecked(): vec = sender.getVec() sender.line = self.plotAnalysis(vec) #sender.line, = self.analysisAx.plot(sender.getVec()) else: sender.line.remove() self.analysisCanvas.draw() def prepareCheckBox(self, getVec, label): operationCheck = QCheckBox() operationCheck.setText(label) operationCheck.setFixedWidth(100) operationCheck.getVec = getVec operationCheck.clicked.connect(self.analysisOptionHandler) return operationCheck def Original(self): self.analysisVec = self.original.vec return self.analysisVec def Derivative(self): self.analysisVec = np.diff(self.analysisVec) return self.analysisVec def LPF(self): self.analysisVec = ma.movingAverage(self.analysisVec, 30, 1.1) return self.analysisVec def cluster(self): pass def createOperationsLayout(self): analysisOptionsLayout = QVBoxLayout() self.original = self.prepareCheckBox(self.Original, 'Original') analysisOptionsLayout.addWidget(self.original) analysisOptionsLayout.addWidget(self.prepareCheckBox(self.LPF, 'LPF')) analysisOptionsLayout.addWidget(self.prepareCheckBox(self.Derivative, 'Derivative')) analysisOptionsLayout.addWidget(self.prepareCheckBox(self.cluster, 'cluster')) return analysisOptionsLayout def selectAnalysis(self): dlg = QFileDialog() dlg.setDirectory('Laban/analysis') analysisFile= unicode(dlg.getOpenFileName(filter='*.py')) self.lineEdit.setText(analysisFile) if not os.path.isfile(analysisFile): return rel = os.path.relpath(analysisFile).split('.')[-2] rel = rel.replace('\\','.')#[1:] analayzer = importlib.import_module(rel) self.analysisVec = analayzer.analyze(self.ui.selectedFile) self.original.line = self.plotAnalysis(self.analysisVec) self.original.vec = self.analysisVec self.original.setChecked(True) def plotAnalysis(self, vec): max_height = np.max(vec) min_height = np.min(vec) ax = self.analysisAx ax.set_ylim(min_height, max_height) line, = ax.plot(vec) self.analysisCanvas.draw() return line def clearTimeMarker(self): self.syncSkeletonWithAnalysis = False self.analysisTimeLine.remove() self.analysisCanvas.draw() def initTimeMarker(self): ax = self.analysisAx self.analysisBackground = self.analysisCanvas.copy_from_bbox(ax.bbox) vec = self.analysisVec max_height = np.max(vec) min_height = np.min(vec) y1 = [min_height, max_height] x1 = [0, 0] self.analysisTimeLine, = ax.plot(x1,y1,color='r',animated=True,label='timeMarker') self.updateAnalysisTimeMarker() def updateAnalysisTimeMarker(self): self.analysisCanvas.restore_region(self.analysisBackground) currTime = self.ui.mainWindow.currTime x1 = [currTime,currTime] self.analysisTimeLine.set_xdata(x1) self.analysisAx.draw_artist(self.analysisTimeLine) self.analysisCanvas.blit(self.analysisAx.bbox) def prepareAnalysisEditorBeforeSync(self, handler): self.analysisCanvas.mpl_connect('button_press_event',handler) #self.analysisCanvas.draw() #self.updateAnalysisTimeMarker()
class SignalViewerWidget(QtGui.QWidget): """Shows different visualizations of a seismic signal (magnitude, envelope, spectrogram, characteristic function). Allows the user to manipulate it (navigate through it, zoom in/out, edit detected events, select threshold value, etc...) """ CF_loaded = QtCore.Signal(bool) event_selected = QtCore.Signal(rc.ApasvoEvent) def __init__(self, parent, document=None): super(SignalViewerWidget, self).__init__(parent) self.document = document self.xmin = 0.0 self.xmax = 0.0 self.xleft = 0.0 self.xright = 0.0 self.time = np.array([]) self.fs = 0.0 self.signal = None self.envelope = None self.cf = None self.time = None self._signal_data = None self._envelope_data = None self._cf_data = None self.fig, _ = plt.subplots(3, 1) self.signal_ax = self.fig.axes[0] self.cf_ax = self.fig.axes[1] self.specgram_ax = self.fig.axes[2] self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)) self.canvas.setMinimumHeight(320) self.graphArea = QtGui.QScrollArea(self) self.graphArea.setWidget(self.canvas) self.graphArea.setWidgetResizable(True) self.eventMarkers = {} self.last_right_clicked_event = None self.thresholdMarker = None self.playback_marker = None self.selector = SpanSelector(self.fig) self.minimap = MiniMap(self, self.signal_ax, None) # Load Spectrogram settings self.update_specgram_settings() # Animation related attributes self.background = None self.animated = False # Create context menus self.event_context_menu = QtGui.QMenu(self) self.takanami_on_event_action = QtGui.QAction("Apply Takanami to Event", self) self.takanami_on_event_action.setStatusTip("Refine event position by using Takanami algorithm") self.event_context_menu.addAction(self.takanami_on_event_action) self.takanami_on_event_action.triggered.connect(self.apply_takanami_to_selected_event) self.selection_context_menu = QtGui.QMenu(self) self.create_event_action = QtGui.QAction("Create New Event on Selection", self) self.create_event_action.setStatusTip("Create a new event on selection") self.takanami_on_selection_action = QtGui.QAction("Apply Takanami to Selection", self) self.takanami_on_selection_action.setStatusTip("Apply Takanami algorithm to selection") self.selection_context_menu.addAction(self.create_event_action) self.selection_context_menu.addAction(self.takanami_on_selection_action) self.create_event_action.triggered.connect(self.create_event_on_selection) self.takanami_on_selection_action.triggered.connect(self.apply_takanami_to_selection) # format axes formatter = FuncFormatter(lambda x, pos: clt.float_secs_2_string_date(x, self.document.record.starttime)) for ax in self.fig.axes: ax.callbacks.connect('xlim_changed', self.on_xlim_change) ax.xaxis.set_major_formatter(formatter) plt.setp(ax.get_xticklabels(), visible=True) ax.grid(True, which='both') self.specgram_ax.callbacks.connect('ylim_changed', self.on_ylim_change) self.specgram_ax.set_xlabel('Time (seconds)') plt.setp(self.signal_ax.get_yticklabels(), visible=False) #self.signal_ax.set_ylabel('Signal Amp.') self.cf_ax.set_ylabel('CF Amp.') self.specgram_ax.set_ylabel('Frequency (Hz)') # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.graphArea) self.layout.addWidget(self.minimap) self.selector.toggled.connect(self.minimap.set_selection_visible) self.selector.valueChanged.connect(self.minimap.set_selection_limits) self.selector.right_clicked.connect(self.on_selector_right_clicked) if self.document is not None: self.set_record(document) @property def data_loaded(self): return self.document is not None def set_record(self, document, step=120.0): self.document = document self.fs = self.document.record.fs self.signal = self.document.record.signal self.envelope = env.envelope(self.signal) self.cf = self.document.record.cf self.time = np.linspace(0, len(self.signal) / self.fs, num=len(self.signal), endpoint=False) self.xmax = self.time[-1] # Draw minimap self.minimap.minimapSelector.set(visible=False) # Hide minimap selector while loading self.minimap.set_record(self.document.record, step) # Plot signal step_samples = step * self.fs self._signal_data = self.signal_ax.plot(self.time[:step_samples], self.signal[:step_samples], color='black', rasterized=True)[0] # Plot envelope self._envelope_data = self.signal_ax.plot(self.time[:step_samples], self.envelope[:step_samples], color='red', rasterized=True)[0] # Adjust y axis for signal plot signal_yaxis_max_value = max(np.max(self.signal), np.max(self.envelope)) signal_yaxis_min_value = np.min(self.signal) plotting.adjust_axes_height(self.signal_ax, max_value=signal_yaxis_max_value, min_value=signal_yaxis_min_value) # Plot CF cf_loaded = (self.cf.size != 0) self.set_cf_visible(cf_loaded) self.CF_loaded.emit(cf_loaded) cf_step_samples = min(step_samples,len(self.cf)) self._cf_data = self.cf_ax.plot(self.time[:cf_step_samples], self.cf[:cf_step_samples], color='black', rasterized=True)[0] # Adjust y axis for CF plot if cf_loaded: plotting.adjust_axes_height(self.cf_ax, max_value=np.max(self.cf), min_value=np.min(self.cf)) self.thresholdMarker = ThresholdMarker(self.cf_ax) # Plot espectrogram plotting.plot_specgram(self.specgram_ax, self.signal, self.fs, nfft=self.specgram_windowlen, noverlap=self.specgram_noverlap, window=self.specgram_window) # Set the span selector self.selector.fs = self.fs self.selector.set_active(False) self.selector.set_selection_limits(self.xmin, self.xmax) # Set the playback marker self.playback_marker = PlayBackMarker(self.fig, self) # Set the initial xlimits self.set_xlim(0, step) self.subplots_adjust() # Set event markers self.eventMarkers = {} for event in self.document.record.events: self.create_event(event) # Now activate selector again on minimap self.minimap.minimapSelector.set(visible=True) self.minimap.draw() def unset_record(self): self.document = None self.signal = None self.envelope = None self.cf = None self.time = None self._signal_data = None self._envelope_data = None self._cf_data = None self.xmin, self.xmax = 0.0, 0.0 self.eventMarkers = {} # Clear axes self.signal_ax.lines = [] self.cf_ax.lines = [] self.specgram_ax.lines = [] self.specgram_ax.images = [] self.CF_loaded.emit(False) def update_cf(self): if self.data_loaded: self.cf = self.document.record.cf self._cf_data.set_xdata(self.time[:len(self.cf)]) self._cf_data.set_ydata(self.cf) plotting.adjust_axes_height(self.cf_ax) cf_loaded = (self.cf.size != 0) self.CF_loaded.emit(cf_loaded) self.set_cf_visible(cf_loaded) self.draw() def create_events(self, new_events_set): for event in new_events_set.get(self.document.record.uuid, []): self.create_event(event) def create_event(self, event): event_id = event.resource_id.uuid if event_id not in self.eventMarkers: marker = EventMarker(self.fig, self.minimap, self.document, event) self.eventMarkers[event_id] = marker marker.event_selected.connect(self.event_selected.emit) marker.right_clicked.connect(self.on_event_right_clicked) def delete_events(self, new_events_set): for event in new_events_set.get(self.document.record.uuid, []): self.delete_event(event) def delete_event(self, event): event_id = event.resource_id.uuid self.eventMarkers[event_id].remove() self.eventMarkers.pop(event_id) def update_event(self, event): self.eventMarkers[event.resource_id.uuid].update() def set_xlim(self, l, r): xmin = max(0, l) xmax = min(self.xmax, r) self.signal_ax.set_xlim(xmin, xmax) def on_xlim_change(self, ax): xmin, xmax = ax.get_xlim() if (self.xleft, self.xright) != (xmin, xmax): self.xleft, self.xright = xmin, xmax if self.xmin <= xmin <= xmax <= self.xmax: # Update minimap selector if (xmin, xmax) != self.minimap.get_selector_limits(): self.minimap.set_selector_limits(xmin, xmax) # Update axes for axes in self.fig.axes: if ax != axes: axes.set_xlim(xmin, xmax) # Update data xmin = int(max(0, xmin) * self.fs) xmax = int(min(self.xmax, xmax) * self.fs) pixel_width = np.ceil(self.fig.get_figwidth() * self.fig.get_dpi()) if self._signal_data is not None: x_data, y_data = plotting.reduce_data(self.time, self.signal, pixel_width, xmin, xmax) self._signal_data.set_xdata(x_data) self._signal_data.set_ydata(y_data) if self._envelope_data is not None: x_data, y_data = plotting.reduce_data(self.time, self.envelope, pixel_width, xmin, xmax) self._envelope_data.set_xdata(x_data) self._envelope_data.set_ydata(y_data) if self._cf_data is not None and self.cf_ax.get_visible(): x_data, y_data = plotting.reduce_data(self.time[:len(self.cf)], self.cf, pixel_width, xmin, xmax) self._cf_data.set_xdata(x_data) self._cf_data.set_ydata(y_data) # Draw graph self.draw() else: xmin = max(self.xmin, xmin) xmax = min(self.xmax, xmax) ax.set_xlim(xmin, xmax) def on_ylim_change(self, ax): if self.data_loaded: if ax == self.specgram_ax: ymin, ymax = ax.get_ylim() nyquist_freq = (self.fs / 2.0) if ymin < 0.0: ax.set_ylim(0.0, ymax) elif ymax > nyquist_freq: ax.set_ylim(ymin, nyquist_freq) def set_event_selection(self, events): event_id_list = [event.resource_id.uuid for event in events] for event_id in self.eventMarkers: self.eventMarkers[event_id].set_selected(event_id in event_id_list) self.draw() self.minimap.draw() def set_position(self, pos): """""" xmin, xmax = self.signal_ax.get_xlim() mrange = xmax - xmin l, r = pos - mrange / 2.0, pos + mrange / 2.0 if l < self.xmin: l, r = self.xmin, mrange elif r > self.xmax: l, r = self.xmax - mrange, self.xmax self.set_xlim(l, r) def goto_event(self, event): if event.resource_id.uuid in self.eventMarkers: self.set_position(event.stime / self.fs) def showEvent(self, event): self.draw() self.minimap.draw_animate() def resizeEvent(self, event): self.draw() self.minimap.draw_animate() def set_signal_amplitude_visible(self, show_sa): if self._signal_data is not None and self._envelope_data is not None: if self._signal_data.get_visible() != show_sa: self._signal_data.set_visible(show_sa) show_axis = (self._signal_data.get_visible() + self._envelope_data.get_visible()) self.signal_ax.set_visible(show_axis) if self.data_loaded: self.subplots_adjust() self.draw() def set_signal_envelope_visible(self, show_se): if self._signal_data is not None and self._envelope_data is not None: if self._envelope_data.get_visible() != show_se: self._envelope_data.set_visible(show_se) show_axis = (self._signal_data.get_visible() + self._envelope_data.get_visible()) self.signal_ax.set_visible(show_axis) if self.data_loaded: self.subplots_adjust() self.draw() def set_cf_visible(self, show_cf): if self.cf_ax.get_visible() != show_cf: if self.data_loaded: if len(self.cf) <= 0: self.cf_ax.set_visible(False) else: self.cf_ax.set_visible(show_cf) self.subplots_adjust() self.draw() def set_espectrogram_visible(self, show_eg): if self.specgram_ax.get_visible() != show_eg: self.specgram_ax.set_visible(show_eg) if self.data_loaded: self.subplots_adjust() self.draw() def set_minimap_visible(self, show_mm): if self.minimap.get_visible() != show_mm: self.minimap.set_visible(show_mm) self.minimap.draw_animate() def set_threshold_visible(self, show_thr): if self.thresholdMarker: if self.thresholdMarker.get_visible() != show_thr: self.thresholdMarker.set_visible(show_thr) self.draw() def subplots_adjust(self): visible_subplots = [ax for ax in self.fig.get_axes() if ax.get_visible()] for i, ax in enumerate(visible_subplots): correct_geometry = (len(visible_subplots), 1, i + 1) if correct_geometry != ax.get_geometry(): ax.change_geometry(len(visible_subplots), 1, i + 1) # Adjust space between subplots self.fig.subplots_adjust(left=0.06, right=0.95, bottom=0.14, top=0.95, hspace=0.22) def get_selector_limits(self): return self.selector.get_selector_limits() def set_selector_limits(self, xleft, xright): self.selector.set_selector_limits(xleft, xright) def set_selection_enabled(self, value): self.selector.set_enabled(value) def set_playback_position(self, position): if self.playback_marker is not None: self.playback_marker.set_position(position) self.minimap.playback_marker.set_position(position) def set_playback_marker_visible(self, show_marker): if self.playback_marker is not None: self.playback_marker.set_visible(show_marker) self.minimap.playback_marker.set_visible(show_marker) def on_event_right_clicked(self, event): self.last_right_clicked_event = event self.event_context_menu.exec_(QtGui.QCursor.pos()) def apply_takanami_to_selected_event(self): takanamidialog.TakanamiDialog(self.document, seismic_event=self.last_right_clicked_event).exec_() def apply_takanami_to_selection(self): xleft, xright = self.get_selector_limits() takanamidialog.TakanamiDialog(self.document, xleft, xright).exec_() def create_event_on_selection(self): xleft, xright = self.get_selector_limits() xleft, xright = xleft * self.fs, xright * self.fs cf = self.cf[xleft:xright] if cf.size > 0: time = (xleft + np.argmax(cf)) else: time = (xleft + ((xright - xleft) / 2.0)) self.document.createEvent(time=time) def draw(self): if self.animated: self._draw_animate() else: self.canvas.draw_idle() def _draw_animate(self): self.canvas.restore_region(self.background) for artist in self._get_animated_artists(): if artist.get_visible(): ax = artist.get_axes() if ax is not None: if artist.get_axes().get_visible(): self.fig.draw_artist(artist) else: self.fig.draw_artist(artist) self.canvas.blit(self.fig.bbox) def _set_animated(self, value): if self.animated != value: self.animated = value for artist in self._get_animated_artists(): artist.set_animated(value) if self.animated == True: images = [] for ax in self.fig.axes: images.extend(ax.images) for image in images: image.set_visible(False) self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) for image in images: image.set_visible(True) def _get_animated_artists(self): artists = [] for ax in self.fig.axes: artists.extend(ax.images) artists.extend(ax.lines) artists.append(ax.xaxis) artists.append(ax.yaxis) artists.extend(ax.patches) artists.extend(ax.spines.values()) for artist in artists: yield artist def update_specgram_settings(self): # load specgram settings settings = QtCore.QSettings(_organization, _application_name) settings.beginGroup("specgram_settings") self.specgram_windowlen = int(settings.value('window_len', settingsdialog.SPECGRAM_WINDOW_LENGTHS[4])) self.specgram_noverlap = int(settings.value('noverlap', self.specgram_windowlen / 2)) self.specgram_window = settings.value('window', plotting.SPECGRAM_WINDOWS[2]) settings.endGroup() if self.data_loaded: # Plot espectrogram self.specgram_ax.images = [] # Save x-axis limits limits = self.signal_ax.get_xlim() # Draw spectrogram plotting.plot_specgram(self.specgram_ax, self.signal, self.fs, nfft=self.specgram_windowlen, noverlap=self.specgram_noverlap, window=self.specgram_window) # Restore x-axis limits self.signal_ax.set_xlim(*limits) def paintEvent(self, paintEvent): super(SignalViewerWidget, self).paintEvent(paintEvent) def on_selector_right_clicked(self): xleft, xright = self.get_selector_limits() self.takanami_on_selection_action.setEnabled((xright - xleft) >= (takanamidialog.MINIMUM_MARGIN_IN_SECS * 2)) self.selection_context_menu.exec_(QtGui.QCursor.pos())
class MiniMap(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ def __init__(self, parent, ax, record=None): super(MiniMap, self).__init__(parent) self.ax = ax self.xmin = 0.0 self.xmax = 0.0 self.step = 10.0 self.xrange = np.array([]) self.minimapFig = plt.figure() self.minimapFig.set_figheight(0.75) self.minimapFig.add_axes((0, 0, 1, 1)) self.minimapCanvas = FigureCanvas(self.minimapFig) self.minimapCanvas.setFixedHeight(64) self.minimapSelector = self.minimapFig.axes[0].axvspan(0, self.step, color='gray', alpha=0.5, animated=True) self.minimapSelection = self.minimapFig.axes[0].axvspan(0, self.step, color='LightCoral', alpha = 0.5, animated=True) self.minimapSelection.set_visible(False) self.minimapBackground = [] self.minimapSize = (self.minimapFig.bbox.width, self.minimapFig.bbox.height) self.press_selector = None self.playback_marker = None self.minimapCanvas.mpl_connect('button_press_event', self.onpress) self.minimapCanvas.mpl_connect('button_release_event', self.onrelease) self.minimapCanvas.mpl_connect('motion_notify_event', self.onmove) # Animation related attrs. self.background = None self.animated = False # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.minimapCanvas) # Animation related attributes self.parentViewer = parent # Set Markers dict self.markers = {} self.record = None if record is not None: self.set_record(record) def set_record(self, record, step): self.record = record self.step = step self.xrange = np.linspace(0, len(self.record.signal) / self.record.fs, num=len(self.record.signal), endpoint=False) self.xmin = self.xrange[0] self.xmax = self.xrange[-1] self.markers = {} ax = self.minimapFig.axes[0] ax.lines = [] formatter = FuncFormatter(lambda x, pos: str(datetime.timedelta(seconds=x))) ax.xaxis.set_major_formatter(formatter) ax.grid(True, which='both') # Set dataseries to plot xmin = self.xmin * self.record.fs xmax = self.xmax * self.record.fs pixel_width = np.ceil(self.minimapFig.get_figwidth() * self.minimapFig.get_dpi()) x_data, y_data = plotting.reduce_data(self.xrange, self.record.signal, pixel_width, xmin, xmax) # self._plot_data.set_xdata(x_data) # self._plot_data.set_ydata(y_data) ax.plot(x_data, y_data, color='black', rasterized=True) ax.set_xlim(self.xmin, self.xmax) plotting.adjust_axes_height(ax) # Set the playback marker self.playback_marker = PlayBackMarker(self.minimapFig, self) self.playback_marker.markers[0].set_animated(True) # Draw canvas self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox(self.minimapFig.bbox) self.draw_animate() def onpress(self, event): self.press_selector = event xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.parentViewer._set_animated(True) self.set_selector_limits(xmin, xmax) def onrelease(self, event): self.press_selector = None # Finish parent animation self.parentViewer._set_animated(False) def onmove(self, event): if self.press_selector is not None: xdata = round(self.get_xdata(event), 2) xmin = round(xdata - (self.step / 2.0), 2) xmax = round(xdata + (self.step / 2.0), 2) self.set_selector_limits(xmin, xmax) def get_xdata(self, event): inv = self.minimapFig.axes[0].transData.inverted() xdata, _ = inv.transform((event.x, event.y)) return xdata def set_selector_limits(self, xmin, xmax): step = xmax - xmin if step >= self.xmax - self.xmin: xleft = self.xmin xright = self.xmax if xmin < self.xmin: xleft = self.xmin xright = self.step elif xmax > self.xmax: xleft = self.xmax - step xright = self.xmax else: xleft = xmin xright = xmax if (xleft, xright) != (self.minimapSelector.xy[1, 0], self.minimapSelector.xy[2, 0]): self.step = step self.minimapSelector.xy[:2, 0] = xleft self.minimapSelector.xy[2:4, 0] = xright self.ax.set_xlim(xleft, xright) self.draw_animate() else: self.parentViewer.draw() def get_selector_limits(self): return self.minimapSelector.xy[0, 0], self.minimapSelector.xy[2, 0] def draw(self): self.draw_animate() def draw_animate(self): size = self.minimapFig.bbox.width, self.minimapFig.bbox.height if size != self.minimapSize: self.minimapSize = size self.minimapCanvas.draw() self.minimapBackground = self.minimapCanvas.copy_from_bbox(self.minimapFig.bbox) self.minimapCanvas.restore_region(self.minimapBackground) self.minimapFig.draw_artist(self.minimapSelection) self.minimapFig.draw_artist(self.minimapSelector) self.minimapFig.draw_artist(self.playback_marker.markers[0]) for marker in self.markers.values(): self.minimapFig.draw_artist(marker) self.minimapCanvas.blit(self.minimapFig.bbox) def set_visible(self, value): self.minimapCanvas.setVisible(value) def get_visible(self): return self.minimapCanvas.isVisible() def set_selection_limits(self, xleft, xright): self.minimapSelection.xy[:2, 0] = xleft self.minimapSelection.xy[2:4, 0] = xright self.draw_animate() def set_selection_visible(self, value): self.minimapSelection.set_visible(value) self.draw_animate() def create_marker(self, key, position, **kwargs): if self.xmin <= position <= self.xmax: marker = self.minimapFig.axes[0].axvline(position, animated=True) self.markers[key] = marker self.markers[key].set(**kwargs) def set_marker_position(self, key, value): marker = self.markers.get(key) if marker is not None: if self.xmin <= value <= self.xmax: marker.set_xdata(value) def set_marker(self, key, **kwargs): marker = self.markers.get(key) if marker is not None: kwargs.pop("animated", None) # marker's animated property must be always true to be drawn properly marker.set(**kwargs) def delete_marker(self, key): marker = self.markers.get(key) if marker is not None: self.minimapFig.axes[0].lines.remove(marker) self.markers.pop(key)
class PlotCanvas(QtCore.QObject): """ Class handling the plotting area in the application. """ # Signals: # Request for new bitmap to display. The parameter # is a list with [xmin, xmax, ymin, ymax, zoom(optional)] update_screen_request = QtCore.pyqtSignal(list) def __init__(self, container, app): """ The constructor configures the Matplotlib figure that will contain all plots, creates the base axes and connects events to the plotting area. :param container: The parent container in which to draw plots. :rtype: PlotCanvas """ super(PlotCanvas, self).__init__() self.app = app # Options self.x_margin = 15 # pixels self.y_margin = 25 # Pixels # Parent container self.container = container # Plots go onto a single matplotlib.figure self.figure = Figure(dpi=50) # TODO: dpi needed? self.figure.patch.set_visible(False) # These axes show the ticks and grid. No plotting done here. # New axes must have a label, otherwise mpl returns an existing one. self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0) self.axes.set_aspect(1) self.axes.grid(True) self.axes.axhline(color='Black') self.axes.axvline(color='Black') # The canvas is the top level container (FigureCanvasQTAgg) self.canvas = FigureCanvas(self.figure) # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) # self.canvas.setFocus() #self.canvas.set_hexpand(1) #self.canvas.set_vexpand(1) #self.canvas.set_can_focus(True) # For key press # Attach to parent #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? self.container.addWidget(self.canvas) # Qt # Copy a bitmap of the canvas for quick animation. # Update every time the canvas is re-drawn. self.background = self.canvas.copy_from_bbox(self.axes.bbox) ### Bitmap Cache self.cache = CanvasCache(self, self.app) self.cache_thread = QtCore.QThread() self.cache.moveToThread(self.cache_thread) #super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run) # self.connect() self.cache_thread.start() self.cache.new_screen.connect(self.on_new_screen) # Events self.canvas.mpl_connect('button_press_event', self.on_mouse_press) self.canvas.mpl_connect('button_release_event', self.on_mouse_release) self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) #self.canvas.connect('configure-event', self.auto_adjust_axes) self.canvas.mpl_connect('resize_event', self.auto_adjust_axes) #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) #self.canvas.connect("scroll-event", self.on_scroll) self.canvas.mpl_connect('scroll_event', self.on_scroll) self.canvas.mpl_connect('key_press_event', self.on_key_down) self.canvas.mpl_connect('key_release_event', self.on_key_up) self.canvas.mpl_connect('draw_event', self.on_draw) self.mouse = [0, 0] self.key = None self.pan_axes = [] self.panning = False def on_new_screen(self): log.debug("Cache updated the screen!") def on_key_down(self, event): """ :param event: :return: """ FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key)) self.key = event.key def on_key_up(self, event): """ :param event: :return: """ self.key = None def mpl_connect(self, event_name, callback): """ Attach an event handler to the canvas through the Matplotlib interface. :param event_name: Name of the event :type event_name: str :param callback: Function to call :type callback: func :return: Connection id :rtype: int """ return self.canvas.mpl_connect(event_name, callback) def mpl_disconnect(self, cid): """ Disconnect callback with the give id. :param cid: Callback id. :return: None """ self.canvas.mpl_disconnect(cid) def connect(self, event_name, callback): """ Attach an event handler to the canvas through the native Qt interface. :param event_name: Name of the event :type event_name: str :param callback: Function to call :type callback: function :return: Nothing """ self.canvas.connect(event_name, callback) def clear(self): """ Clears axes and figure. :return: None """ # Clear self.axes.cla() try: self.figure.clf() except KeyError: FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()") # Re-build self.figure.add_axes(self.axes) self.axes.set_aspect(1) self.axes.grid(True) # Re-draw self.canvas.draw_idle() def adjust_axes(self, xmin, ymin, xmax, ymax): """ Adjusts all axes while maintaining the use of the whole canvas and an aspect ratio to 1:1 between x and y axes. The parameters are an original request that will be modified to fit these restrictions. :param xmin: Requested minimum value for the X axis. :type xmin: float :param ymin: Requested minimum value for the Y axis. :type ymin: float :param xmax: Requested maximum value for the X axis. :type xmax: float :param ymax: Requested maximum value for the Y axis. :type ymax: float :return: None """ # FlatCAMApp.App.log.debug("PC.adjust_axes()") width = xmax - xmin height = ymax - ymin try: r = width / height except ZeroDivisionError: FlatCAMApp.App.log.error("Height is %f" % height) return canvas_w, canvas_h = self.canvas.get_width_height() canvas_r = float(canvas_w) / canvas_h x_ratio = float(self.x_margin) / canvas_w y_ratio = float(self.y_margin) / canvas_h if r > canvas_r: ycenter = (ymin + ymax) / 2.0 newheight = height * r / canvas_r ymin = ycenter - newheight / 2.0 ymax = ycenter + newheight / 2.0 else: xcenter = (xmax + xmin) / 2.0 newwidth = width * canvas_r / r xmin = xcenter - newwidth / 2.0 xmax = xcenter + newwidth / 2.0 # Adjust axes for ax in self.figure.get_axes(): if ax._label != 'base': ax.set_frame_on(False) # No frame ax.set_xticks([]) # No tick ax.set_yticks([]) # No ticks ax.patch.set_visible(False) # No background ax.set_aspect(1) ax.set_xlim((xmin, xmax)) ax.set_ylim((ymin, ymax)) ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio]) # Sync re-draw to proper paint on form resize self.canvas.draw() ##### Temporary place-holder for cached update ##### self.update_screen_request.emit([0, 0, 0, 0, 0]) def auto_adjust_axes(self, *args): """ Calls ``adjust_axes()`` using the extents of the base axes. :rtype : None :return: None """ xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() self.adjust_axes(xmin, ymin, xmax, ymax) def zoom(self, factor, center=None): """ Zooms the plot by factor around a given center point. Takes care of re-drawing. :param factor: Number by which to scale the plot. :type factor: float :param center: Coordinates [x, y] of the point around which to scale the plot. :type center: list :return: None """ xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() width = xmax - xmin height = ymax - ymin if center is None or center == [None, None]: center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0] # For keeping the point at the pointer location relx = (xmax - center[0]) / width rely = (ymax - center[1]) / height new_width = width / factor new_height = height / factor xmin = center[0] - new_width * (1 - relx) xmax = center[0] + new_width * relx ymin = center[1] - new_height * (1 - rely) ymax = center[1] + new_height * rely # Adjust axes for ax in self.figure.get_axes(): ax.set_xlim((xmin, xmax)) ax.set_ylim((ymin, ymax)) # Async re-draw self.canvas.draw_idle() ##### Temporary place-holder for cached update ##### self.update_screen_request.emit([0, 0, 0, 0, 0]) def pan(self, x, y): xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() width = xmax - xmin height = ymax - ymin # Adjust axes for ax in self.figure.get_axes(): ax.set_xlim((xmin + x * width, xmax + x * width)) ax.set_ylim((ymin + y * height, ymax + y * height)) # Re-draw self.canvas.draw_idle() ##### Temporary place-holder for cached update ##### self.update_screen_request.emit([0, 0, 0, 0, 0]) def new_axes(self, name): """ Creates and returns an Axes object attached to this object's Figure. :param name: Unique label for the axes. :return: Axes attached to the figure. :rtype: Axes """ return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) def on_scroll(self, event): """ Scroll event handler. :param event: Event object containing the event information. :return: None """ # So it can receive key presses # self.canvas.grab_focus() self.canvas.setFocus() # Event info # z, direction = event.get_scroll_direction() if self.key is None: if event.button == 'up': self.zoom(1.5, self.mouse) else: self.zoom(1 / 1.5, self.mouse) return if self.key == 'shift': if event.button == 'up': self.pan(0.3, 0) else: self.pan(-0.3, 0) return if self.key == 'control': if event.button == 'up': self.pan(0, 0.3) else: self.pan(0, -0.3) return def on_mouse_press(self, event): # Check for middle mouse button press if event.button == self.app.mouse_pan_button: # Prepare axes for pan (using 'matplotlib' pan function) self.pan_axes = [] for a in self.figure.get_axes(): if (event.x is not None and event.y is not None and a.in_axes(event) and a.get_navigate() and a.can_pan()): a.start_pan(event.x, event.y, 1) self.pan_axes.append(a) # Set pan view flag if len(self.pan_axes) > 0: self.panning = True; def on_mouse_release(self, event): # Check for middle mouse button release to complete pan procedure if event.button == self.app.mouse_pan_button: for a in self.pan_axes: a.end_pan() # Clear pan flag self.panning = False def on_mouse_move(self, event): """ Mouse movement event hadler. Stores the coordinates. Updates view on pan. :param event: Contains information about the event. :return: None """ self.mouse = [event.xdata, event.ydata] # Update pan view on mouse move if self.panning is True: for a in self.pan_axes: a.drag_pan(1, event.key, event.x, event.y) # Async re-draw (redraws only on thread idle state, uses timer on backend) self.canvas.draw_idle() ##### Temporary place-holder for cached update ##### self.update_screen_request.emit([0, 0, 0, 0, 0]) def on_draw(self, renderer): # Store background on canvas redraw self.background = self.canvas.copy_from_bbox(self.axes.bbox) def get_axes_pixelsize(self): """ Axes size in pixels. :return: Pixel width and height :rtype: tuple """ bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) width, height = bbox.width, bbox.height width *= self.figure.dpi height *= self.figure.dpi return width, height def get_density(self): """ Returns unit length per pixel on horizontal and vertical axes. :return: X and Y density :rtype: tuple """ xpx, ypx = self.get_axes_pixelsize() xmin, xmax = self.axes.get_xlim() ymin, ymax = self.axes.get_ylim() width = xmax - xmin height = ymax - ymin return width / xpx, height / ypx
class StreamViewerWidget(QtGui.QWidget): """Shows the entire signal and allows the user to navigate through it. Provides an scrollable selector over the entire signal. Attributes: xmin: Selector lower limit (measured in h-axis units). xmax: Selector upper limit (measured in h-axis units). step: Selector length (measured in h-axis units). """ trace_selected = QtCore.Signal(int) selection_made = QtCore.Signal(bool) def __init__(self, parent, stream=None): super(StreamViewerWidget, self).__init__(parent) self.fig = plt.figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)) self.canvas.setMinimumHeight(320) self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) self.canvas.setFocus() self.graphArea = QtGui.QScrollArea(self) self.graphArea.setWidget(self.canvas) self.graphArea.setWidgetResizable(True) self.graphArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) # Set the layout self.layout = QtGui.QVBoxLayout(self) self.layout.addWidget(self.graphArea) # Animation related attrs. self.background = [] self.animated = False self.size = (self.fig.bbox.width, self.fig.bbox.height) # Set TracePlot list self.trace_plots = [] self.stream = None if stream is not None: self.set_stream(stream) # Event handling self.visible_axes = [] self._selected_traces = set() self.shift_pressed = False self.press_selector = None self.fig.canvas.mpl_connect('motion_notify_event', self.on_move) self.fig.canvas.mpl_connect('button_press_event', self.on_press) self.fig.canvas.mpl_connect('key_press_event', self.on_key_press) self.fig.canvas.mpl_connect('key_release_event', self.on_key_release) @property def selected_traces(self): if self.stream is not None: return [self.stream.traces[i] for i in self._selected_traces] return [] def on_move(self, event): axes_selected = False for i, axes in enumerate(self.fig.axes): if axes.get_visible(): ymin, ymax = axes.get_position().ymin, axes.get_position().ymax xmin, xmax = axes.get_position().xmin, axes.get_position().xmax xfig, yfig = self._event_to_fig_coords(event) if ymin <= yfig <= ymax and xmin <= xfig <= xmax: self.canvas.setToolTip(self.stream.traces[i].name) axes_selected = True break if not axes_selected: self.canvas.setToolTip("") def on_key_press(self, event): if event.key == 'control': self.shift_pressed = True def on_key_release(self, event): self.shift_pressed = False def on_press(self, event): trace_selected = False if event.button == 1:# and event.dblclick: for i, ax in enumerate(self.fig.axes): if ax.get_visible(): ymin, ymax = ax.get_position().ymin, ax.get_position().ymax xmin, xmax = ax.get_position().xmin, ax.get_position().xmax xfig, yfig = self._event_to_fig_coords(event) if ymin <= yfig <= ymax and xmin <= xfig <= xmax: trace_selected = True if self.shift_pressed: if self._selected_traces: self.trace_selected.emit(i) self.selection_made.emit(True) self._selected_traces.add(i) else: self.trace_selected.emit(i) self.selection_made.emit(True) self._selected_traces = {i} break # if the user clicked out of any trace (and he's not using shift), then deselect all if not trace_selected and not self.shift_pressed: self._selected_traces = set() self.selection_made.emit(False) # Now update selection status on plots for i, plot in enumerate(self.trace_plots): plot.set_selected(i in self._selected_traces) self.draw() def _event_to_fig_coords(self, event): inv = self.fig.transFigure.inverted() return inv.transform((event.x, event.y)) def set_stream(self, stream): self.stream = stream self._selected_traces = set() # Clear canvas for plot in self.trace_plots: plot.remove() self.trace_plots = [] # Plot stream traces for i, trace in enumerate(self.stream.traces): self.trace_plots.append(TracePlot(self, trace, fig_nrows=len(stream), ax_pos=i + 1)) # Draw canvas self.subplots_adjust() self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) self.draw() def refresh_stream_data(self): for plot in self.trace_plots: plot.update_data() def draw(self): self.canvas.draw() #self.draw_animate() def draw_animate(self): size = self.fig.bbox.width, self.fig.bbox.height if size != self.size: self.size = size self.canvas.draw() self.background = self.canvas.copy_from_bbox(self.fig.bbox) self.canvas.restore_region(self.background) for artist in self._get_animated_artists(): if artist.get_visible(): ax = artist.get_axes() if ax is not None: if artist.get_axes().get_visible(): self.fig.draw_artist(artist) else: self.fig.draw_artist(artist) self.canvas.blit(self.fig.bbox) def _get_animated_artists(self): artists = [] for ax in self.fig.axes: artists.extend(ax.images) artists.extend(ax.lines) artists.append(ax.xaxis) artists.append(ax.yaxis) artists.extend(ax.patches) artists.extend(ax.spines.values()) for artist in artists: if artist.get_animated(): yield artist def set_visible(self, value): self.canvas.setVisible(value) def get_visible(self): return self.canvas.isVisible() def remove_trace(self, idx): self.trace_plots.pop(idx).remove() def subplots_adjust(self): visible_subplots = [ax for ax in self.fig.get_axes() if ax.get_visible()] for i, ax in enumerate(visible_subplots): correct_geometry = (len(visible_subplots), 1, i + 1) if correct_geometry != ax.get_geometry(): ax.change_geometry(len(visible_subplots), 1, i + 1) # Adjust space between subplots self.fig.subplots_adjust(left=0.02, right=0.98, bottom=0.02, top=0.98, hspace=0.05) def showEvent(self, event): self.draw() def resizeEvent(self, event): self.draw() def update_markers(self): for plot in self.trace_plots: plot.update_markers() self.draw() def visualize_stream_range(self, start_trace=None, end_trace=None): for i, ax in enumerate(self.fig.axes): ax.set_visible(start_trace <= i < end_trace) self.subplots_adjust() self.canvas.draw()
class mpl_widget(QWidget): def __init__(self, parent=None, mainWidget=None): self._SELECTEDCELLS = list() # container for instances of selected cells, so we can delete them when we want self._SELECTEDCELLS_IJ = list() # container for coords of selected cells, so we can delete them when we want self._SELECTEDCELLLINES = list() # container for instances of selected cells, so we can delete them when we want self._GRIDLINES = None QWidget.__init__(self, parent) self.mainWidget = mainWidget self.create_main_frame() self.mpl_menu = mpl_menu(self) self.shift_is_held = False #self.connect(self.mpl_menu, QtCore.SIGNAL('mySignal'), self.mySlot) #print 'my parent is:', parent self.clear_selection() self.init_tooltips() def init_tooltips(self): self.canvas.setToolTip('If 2D plot => RMB click toggles menu <br> - RMB click selects cell <br> - selected cells are drawn with black border') self.grid_cb.setToolTip('If 2D plot => show computational grid <br> If 1D plot => show normal gridlines') self.cbar_button.setToolTip('If 2D plot => controls the color range. <br> Note: <br> - pressing UP and DOWN arrows cycles through colormaps <br> - dragging colorbar with RMB scales the color-range <br> - dragging colorbar with LMB shifts the color-range') self.mpl_toolbar.setToolTip('Shows coordinates (i,j, lat,lon) and data-value(z) under the cursor. <br> if you see <i>>>></i> coordinates are not visible. Enlarge the window') def create_main_frame(self): self.fig = Figure(dpi=100) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self) self.canvas.setFocusPolicy( Qt.ClickFocus ) self.canvas.setFocus() self.mpl_toolbar = myNavigationToolbar(self.canvas, self) self.canvas.mpl_connect('button_press_event', self.on_click) self.canvas.mpl_connect('key_press_event', self.on_key_press) self.canvas.mpl_connect('key_release_event', self.on_key_release) #self.canvas.mpl_connect('button_press_event', self.disable_clicks) self.cbar_button = QPushButton("Color Range") self.cbar_button.setFocusPolicy( Qt.NoFocus ) self.grid_cb = QCheckBox("Show Grid") self.grid_cb.setFocusPolicy( Qt.NoFocus ) self.grid_cb.stateChanged.connect(self.showGrid) vbox = QVBoxLayout() hbox = QHBoxLayout() vbox.addWidget(self.canvas) # the matplotlib canvas hbox.addWidget(self.mpl_toolbar) hbox.addWidget(self.cbar_button) hbox.addWidget(self.grid_cb) vbox.addLayout(hbox) self.setLayout(vbox) def on_click(self, event): if event.inaxes != self.get_axes()[0]: return #if self.get_axes()[0].format_coord(event.x, event.y) == 'outside data area': return if self.allow_menu(): self.allow_popup_menu = True if self.shift_is_held: self.allow_popup_menu = False point = [int(event.xdata + .5), int(event.ydata + .5)] #print '>>>', point, '\t currently {0} selected'.format(len(self._SELECTEDCELLS)) if event.button == 3 : #if RMB is clicked # working with dialog for transect! if self.mainWidget.transect_dlg: if self.mainWidget.transect_dlg.toogle_show_after_selected_cell: realx, realy = self.get_real_xy(event.xdata, event.ydata, self.mainWidget.detect_variable_dimensions()) realpoint = [realy, realx] #print 'real xy:', realpoint if self.mainWidget.transect_dlg.toogle_show_after_selected_cell == 1: # select point 1 self.allow_popup_menu = False self.mainWidget.transect_dlg.data.set_p1(realpoint) elif self.mainWidget.transect_dlg.toogle_show_after_selected_cell == 2: # select point 2 self.allow_popup_menu = False self.mainWidget.transect_dlg.data.set_p2(realpoint) self.mainWidget.transect_dlg.update() self.mainWidget.transect_dlg.show() # working with dialog for flux! if self.mainWidget.flux_dlg: if self.mainWidget.flux_dlg.toogle_show_after_selected_cell: if self.mainWidget.flux_dlg.toogle_show_after_selected_cell == 1: # select point 1 self.allow_popup_menu = False self.mainWidget.flux_dlg.data.set_p1(point) elif self.mainWidget.flux_dlg.toogle_show_after_selected_cell == 2: # select point 2 self.allow_popup_menu = False self.mainWidget.flux_dlg.data.set_p2(point) self.mainWidget.flux_dlg.update() self.mainWidget.flux_dlg.show() if len(self._SELECTEDCELLS) == 0: # if no cell is selected self.add_selected_cell(point) else: # if some cells are already selected if self.mpl_menu.allow_rmb_select_cells() or self.shift_is_held: # check if this point is already selected: already_selected = False for p in self._SELECTEDCELLS_IJ: if (point[0] == p[0]) and (point[1] == p[1]): already_selected = True print 'cell already selected... is not added' if not already_selected: self.add_selected_cell(point) else: pass #self.clear_selection() #self.add_selected_cell(point) def cells_selected(self): if self._SELECTEDCELLS: return len(self._SELECTEDCELLS) else: return False def add_selected_cell(self, point): ''' point = [i, j]''' print 'selected cell:', point[0], point[1] c = self.draw_picked_cell(point) self._SELECTEDCELLS.append(c) self._SELECTEDCELLS_IJ.append(point) def get_selected_cells_ij(self): return self._SELECTEDCELLS_IJ def clear_selection(self): ''' delete all graphical objects of selected cells redraw canvas ''' print 'clearing stuff' if len(self._SELECTEDCELLLINES) > 0: for line in self._SELECTEDCELLLINES: l = line.pop(0) l.remove() del l del self._SELECTEDCELLLINES[:] #print 'cells ---- before:', len(self._SELECTEDCELLS) if len(self._SELECTEDCELLS) > 0: for cell in self._SELECTEDCELLS: for line in cell: l = line.pop(0) l.remove() del l del self._SELECTEDCELLS[:] #print 'cells ---- left:', len(self._SELECTEDCELLS) #print 'cells-coords ----' #print len(self._SELECTEDCELLS_IJ) if self._SELECTEDCELLS_IJ: for cellcoords in self._SELECTEDCELLS_IJ: #cc = cellcoords.pop(0) #cellcoords.remove() del cellcoords del self._SELECTEDCELLS_IJ[:] #print 'cells ---- left,', len(self._SELECTEDCELLS_IJ) if len(self._SELECTEDCELLS) != 0: raise ValueError('List "self._SELECTEDCELLS" was not flushed') if len(self._SELECTEDCELLLINES) != 0: raise ValueError('List "self._SELECTEDCELLLINES" was not flushed') if len(self._SELECTEDCELLS_IJ) != 0: raise ValueError('List "self._SELECTEDCELLLINES" was not flushed') # update plot self.canvas.draw() #print 'finishing clear: cells left', len(self._SELECTEDCELLS) def showGrid(self, state): if self.fig.axes: current_plot = self.mainWidget.get_plotType() current_plot2D = self.mainWidget.get_plotType_for_timeseries() if state == Qt.Checked: if current_plot == '1D' or (current_plot2D =="2DZT"): self.fig.axes[0].grid(True) elif current_plot == '2D' and (not current_plot2D =="2DZT"): self.draw_pixel_grid(True) else: if current_plot == '1D' or (current_plot2D =="2DZT"): self.fig.axes[0].grid(False) elif current_plot == '2D' and (not current_plot2D =="2DZT"): self.draw_pixel_grid(False) self.canvas.draw() def draw_picked_cell(self, point): x = point[0] y = point[1] ''' approach drawing a patch... not working cell_bnd = patches.Rectangle((x-.5, y-.5), 1, 1, fill=False, edgecolor="black", hatch=None, linewidth=1.) cell_instance = self.fig.axes[0].add_patch(cell_bnd) ''' b_line = [(x-.5, x+.5), (y-.5, y-.5)] r_line = [(x+.5, x+.5), (y-.5, y+.5)] t_line = [(x-.5, x+.5), (y+.5, y+.5)] l_line = [(x-.5, x-.5), (y-.5, y+.5)] cell = [b_line, r_line, t_line, l_line] for i, l in enumerate(cell): ll = self.fig.axes[0].plot(l[0], l[1], 'k-', lw=.8) cell[i] = ll # overwriting current Line2D object with object binded to an axes #self._SELECTEDCELLS.append(cell) # collecting reference to this cell to be able to delete it #self._SELECTEDCELLS_IJ.append(point) # collecting reference to this cell to be able to delete it self.canvas.draw() return cell def draw_line(self, point1, point2): line = [(point1[0], point2[0]), (point1[1], point2[1])] l = self.fig.axes[0].plot(line[0], line[1], 'k-', lw=2) return l def draw_pixel_grid(self, enable=True): if enable: dx = 1 dy = 1 x0 = -.5 y0 = -.5 if self.mainWidget.get_plotType_for_timeseries() == '2DXY': nx = self.mainWidget.get_nX() ny = self.mainWidget.get_nY() elif self.mainWidget.get_plotType_for_timeseries() == '2DZY': nx = self.mainWidget.get_nY() ny = self.mainWidget.get_nZ() elif self.mainWidget.get_plotType_for_timeseries() == '2DZX': nx = self.mainWidget.get_nX() ny = self.mainWidget.get_nZ() self._GRIDLINES = list() for n_hline in np.arange(ny+1): hline = [(x0, x0+nx), (y0+n_hline, y0+n_hline)] l = self.fig.axes[0].plot(hline[0], hline[1], 'k-', lw=.2) self._GRIDLINES.append(l) # collecting reference to this line to be able to delete it for n_vline in np.arange(nx+1): vline = [(x0+n_vline, x0+n_vline), (y0, y0+ny)] l = self.fig.axes[0].plot(vline[0], vline[1], 'k-', lw=.2) self._GRIDLINES.append(l) # collecting reference to this line to be able to delete it if not enable: #print 'deleting lines...' if self._GRIDLINES: # if lines were created #print 'lines are here...' for line in self._GRIDLINES: #print line l = line.pop(0) l.remove() del l self.fig.canvas.draw() def on_key_press(self, event): #print 'key pressed:', event.key if event.key == 'shift': self.shift_is_held = True def on_key_release(self, event): #print 'key released:', event.key if event.key == 'shift': self.shift_is_held = False elif event.key == 'escape': self.clear_selection() def change_coordinate_formatter(self, ax, data2d, bruteforce_flag=None, bruteforce_dims=None): ''' see http://stackoverflow.com/questions/14754931/matplotlib-values-under-cursor ''' numrows, numcols = data2d.shape bruteforce_mode_on = False bruteforce_mode_on = (bruteforce_flag == '2DXY' and bruteforce_dims[-1] and bruteforce_dims[-2]) def format_coord(x, y): col = int(x+0.5) row = int(y+0.5) if not bruteforce_mode_on: if col >= 0 and col < numcols and row >= 0 and row < numrows: #z = data2d[row, col] # sice we have artificially reversed y-coordinate axes, now reverse data! # numrows-1, because if nrows=10 => row=[0:9] z = data2d[numrows-1-row, col] #return 'x=%1.1f y=%1.1f z=%1.6f' % (x, y, z) return 'i=%d j=%d z=%.6f' % (col, row, z) else: #return 'x=%1.4f, y=%1.4f' % (x, y) return 'outside data area' elif bruteforce_flag == '2DXY' and bruteforce_dims[-1] and bruteforce_dims[-2]: ''' our extend in X=[-0.5:numcols-0.5], Y=[-0.5:numrows-0.5], because col,row is cell center! ''' if col >= 0 and col < numcols and row >= 0 and row < numrows: #z = data2d[row, col] # sice we have artificially reversed y-coordinate axes, now reverse data! # numrows-1, because if nrows=10 => row=[0:9] z = data2d[numrows-1-row, col] real_x, real_y = self.get_real_xy(x, y, bruteforce_dims) #return 'x=%1.1f y=%1.1f z=%1.6f' % (x, y, z) #return 'i=%d j=%d z=%.3f x=%.4f y=%.4f' % (col, row, z, real_x, real_y) return 'i=%d j=%d z=%.3f, %s=%.2f %s=%.2f' % ( col, row, z, bruteforce_dims[-1], real_x, bruteforce_dims[-2], real_y) else: #return 'x=%1.4f, y=%1.4f' % (x, y) return 'outside data area' else: raise ValueError('bruteforce_flag can be $None$ or $"2DXY"$. Passed %s' % bruteforce_flag) ax.format_coord = format_coord def allow_menu(self): allow = False #print "self.mainWidget.get_plotType():", self.mainWidget.get_plotType() #print "self.mainWidget.get_plotType_for_timeseries():", self.mainWidget.get_plotType_for_timeseries() if self.mainWidget.get_plotType() == "2D" and not self.mainWidget.get_plotType_for_timeseries() == "2DZT": allow = True return allow def get_real_xy(self, i, j, dimension_list): ''' functions returns values of x,y based on passed indexes i, j ''' if any(dimension_list[-2:-1]) is None: print 'Dimensions X,Y of current variable are not specified (variables that have same name as dimensions not found)' raise ValueError('Dimensions X,Y of current variable are not specified (variables that have same name as dimensions not found)') nc = self.mainWidget.get_selected_ncfile_instance() try: x_var = nc.variables[dimension_list[-1]] y_var = nc.variables[dimension_list[-2]] except: print ('Failed to load variables: {0}, {1}'.format(dimension_list[-1], dimension_list[-2])) raise ValueError('Failed to load variables: {0}, {1}'.format(dimension_list[-1], dimension_list[-2])) x_ratio = (x_var[-1]-x_var[0])/(len(x_var)-1) y_ratio = (y_var[-1]-y_var[0])/(len(y_var)-1) #x[i] = x_var[0]+x_ratio*i #y[j] = y_var[0]+y_ratio*j x = x_var[0] + x_ratio*i y = y_var[0] + y_ratio*j nc.close() return (x, y) def get_axes(self): return self.fig.get_axes() def fast_redraw(self, artist): background = [self.canvas.copy_from_bbox(self.get_axes()[0].bbox)] self.get_axes()[0].draw_artist(artist) self.canvas.restore_region(background) self.canvas.blit(self.get_axes()[0].bbox) self.canvas.update() self.canvas.flush_events()