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 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 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 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 live_fig(QGroupBox): def __init__(self, name, title, ylabel, ylim=None, scale=1.1, show=False, parent=None): super(live_fig, self).__init__(title, parent) self.name = name self.title = title self.scale = scale self.clear_data() self.layout = QHBoxLayout() self.add_fig(title, ylabel, ylim, scale) self.setLayout(self.layout) if not show: self.hide() self.timer = QTimer() def on_draw(self, event): self.bg = self.canvas.copy_from_bbox(self.ax.bbox) def add_fig(self, title, ylabel, ylim=None, scale=1.1): c = self.parent().palette().button().color() self.fig = Figure(facecolor=(c.redF(), c.greenF(), c.blueF()), edgecolor=(0,0,0)) self.ax = self.fig.add_axes([0.15, 0.1, 0.75, 0.75]) self.ax.set_ylabel(ylabel) if ylim: self.ax.set_ylim(0,ylim) self.ax.grid(True) self.ax.xaxis.set_ticks([]) self.ax.set_color_cycle(color_cycle.values()) self.canvas = FigureCanvas(self.fig) self.canvas.mpl_connect('draw_event', self.on_draw) self.layout.addWidget(self.canvas, 10) def add_node(self, node): self.lines[node], = self.ax.plot([0], [0], label=node.title(), animated=True) def update_lines(self): if not self.bg: return if self.pause: return self.canvas.restore_region(self.bg) for node in self.data: if node not in self.lines: continue x,y = self.data[node] self.ax.set_xlim(x[0], x[-1]) self.lines[node].set_data(x, y) self.ax.draw_artist(self.lines[node]) self.canvas.blit(self.ax.bbox) def update_data(self, node, x, y): self.data[node] = (range(len(x)), y) self.rescale(max(y)) def clear_data(self): if hasattr(self, "lines"): # Remove lines from figure and reset color cycle for line in self.lines.values(): line.remove() self.ax.set_color_cycle(color_cycle.values()) # Clear data self.bg = None self.pause = False self.lines = {} self.data = {} def rescale(self, new_max): if not self.scale: return if not self.ax._cachedRenderer: return if self.pause: return # Read minimum (d) and maximum (max_view) from plot d,max_view = self.ax.get_ybound() current_max = self.current_max() if new_max > max_view or (max_view > 10 and current_max*2 < max_view): # Scale axes if new maximum has arrived self.ax.relim() self.ax.autoscale_view(scalex=False) self.ax.draw_artist(self.ax.yaxis) self.update_lines() self.canvas.draw() def current_max(self): current_max = 0 for line in self.lines.values(): m = max(line.get_ydata()) current_max = m if m > current_max else current_max return current_max def set_animated(self, b): # Make sure we get animated elements for (node,line) in self.lines.items(): line.set_animated(b) def save_fig(self): self.toggle_pause(True) filename,ext = QFileDialog.getSaveFileName(self, "Save plot as file", "", "Portable Document File (*.pdf);;Portable Network Graphics (*.png)") if filename and 'png' in ext: print("Saving PNG file to {}".format(filename)) fmt = 'png' elif filename and 'pdf' in ext: print("Saving PDF file to {}".format(filename)) fmt = 'pdf' else: self.resume() return self.save_file(filename, fmt) self.toggle_pause(False) def save_file(self, filename, fmt): if not filename: return self.set_animated(False) self.ax.set_title(self.title) self.fig.savefig(filename, format=fmt, transparent=True, bbox_inches='tight') self.ax.set_title("") self.set_animated(True) def toggle_hide(self): if self.isVisible(): self.hide() else: self.show() self.timer.singleShot(100, self.canvas.draw) def toggle_pause(self, b=None): if b == None: self.pause = not self.pause else: self.pause = b
class live_fig(QWidget): def __init__(self, title, ylabel, ylabel2=None, parent=None): super(live_fig, self).__init__(parent) self.gs = gridspec.GridSpec(1, 2, width_ratios=[1,5]) self.clear_data() self.layout = QHBoxLayout() self.add_fig(title, ylabel, ylabel2) self.setLayout(self.layout) def on_draw(self, event): self.bg = self.canvas.copy_from_bbox(self.ax.bbox) def add_fig(self, title, ylabel, ylabel2=None): c = self.parent().palette().button().color() self.fig = Figure(facecolor=(c.redF(), c.greenF(), c.blueF()), edgecolor=(0,0,0)) self.ax = self.fig.add_subplot(self.gs[1]) self.ax.set_ylabel(ylabel) self.ax.grid(True) self.ax.xaxis.set_ticks([]) self.ax.set_aspect("auto") if ylabel2: ax2 = self.ax.twinx() ax2.set_ylabel(ylabel2) #self.ax.set_color_cycle(color_cycle.values()) self.canvas = FigureCanvas(self.fig) self.canvas.mpl_connect('draw_event', self.on_draw) self.layout.addWidget(self.canvas, 10) def add_line(self, key): ax = self.fig.add_subplot(self.gs[0]) ax.set_axis_off() ax.set_aspect("auto") self.lines[key], = self.ax.plot([0], [0], label=key.title(), animated=True) l = ax.legend(self.lines.values(), self.lines.keys(), "right") for t in l.get_texts(): t.set_fontsize('medium') self.canvas.draw() def update_lines(self): if not self.bg: return self.canvas.restore_region(self.bg) for key in self.data: x,y = self.data[key] self.ax.set_xlim(x[0], x[-1]) self.lines[key].set_data(x, y) self.ax.draw_artist(self.lines[key]) self.canvas.blit(self.ax.bbox) def update_data(self, key, x, y): self.data[key] = (x, y) self.rescale(max(y)) def clear_data(self): if hasattr(self, "lines"): # Remove lines from figure and reset color cycle for line in self.lines.values(): line.remove() self.ax.set_color_cycle(color_cycle.values()) # Clear data self.bg = None self.lines = {} self.data = {} def rescale(self, new_max): if not self.ax._cachedRenderer: return # Read minimum (d) and maximum (max_view) from plot d,max_view = self.ax.get_ybound() current_max = self.current_max() if new_max > max_view or (max_view > 10 and current_max*2 < max_view): # Scale axes if new maximum has arrived self.ax.relim() self.ax.autoscale_view(scalex=False) self.ax.draw_artist(self.ax.yaxis) self.update_lines() self.canvas.draw() def current_max(self): current_max = 0 for line in self.lines.values(): m = max(line.get_ydata()) current_max = m if m > current_max else current_max return current_max
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 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 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 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 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 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): ''' MainWindow consisting of menu bar, tab widget, tree list menu bar: load data tab widget: - dragWindow - widget consisting of picture viewer and two plots (raw and rms) tree list: to show loaded data, expand to see their channels and click to plot ''' def __init__(self, parent=None): super(Plotter, self).__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setCanvas() w = DragWindow() grid = QGridLayout() grid.addWidget(w) self.ui.tab_2.acceptDrops() self.ui.tab_2.setLayout(grid) self.moveConnection = None self.currentParent = None self.datas = [] self.offsetRecording = 0 self.lines = [{}, {}] def setCanvas(self): ''' set widgets for a tab page - left side show image of current frame - right side show two plots: raw and rms - bot: button to edit triggers and slider to adjust video-emg offset ''' ######################## # set matplotlib plots # ######################## self.fig = Figure(dpi=70) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self.ui.mainFrame) self.axes = self.fig.add_subplot(211) self.axesRMS = self.fig.add_subplot(212) self.mpl_toolbar = NavigationToolbar2(self.canvas, self.ui.mainFrame) self.canvas.mpl_connect('draw_event', self.onDraw) #################################### # add button to matplotlib toolbar # #################################### redb = QPushButton('Edit Triggers') redb.setCheckable(True) self.mpl_toolbar.addWidget(redb) redb.clicked[bool].connect(self.toggleEditMode) # container for current frame of video layout = pq.GraphicsLayoutWidget() vb = layout.addViewBox() vb.setAspectLocked(True) self.ri = pq.ImageItem() vb.addItem(self.ri) # layout to organize elements grid = QGridLayout() wrapper = QWidget() vbox = QVBoxLayout(wrapper) splitter = QSplitter() vbox.addWidget(self.canvas) vbox.addWidget(self.mpl_toolbar) wrapper.setLayout(vbox) splitter.addWidget(layout) splitter.addWidget(wrapper) grid.addWidget(splitter) self.ri.show() layout.show() self.ui.mainFrame.setLayout(grid) def loadData(self): ''' open dialog for choosing files to load ''' fileNames, ok1= QFileDialog.getOpenFileNames( self, self.tr("Open data"), plattform.fixpath("D:\Master\emg\Benedictus"), self.tr("Pickle Files (*.pk)")) if ok1: self.load(fileNames) def load(self, fileNames): ''' unpickles data and add elements to tree view in case loading takes a while, a progress bar shows current status ''' progress = QProgressDialog("Loading files...", "Abort Copy", 0, len(fileNames), self) progress.setWindowModality(Qt.WindowModal) i = 0 for fileName in fileNames: with open(fileName, 'rb') as ifile: tmp = pickle.load(ifile) if os.path.isfile(fileName[:-2] + "oni"): tmp = tmp + (fileName[:-2]+"oni",) if isinstance(tmp, pyHPF.pyHPF): self.datas.append(tmp) else: self.datas.append(extendedPyHPF(tmp)) self.updateTree(fileName[-5:]) i+=1 progress.setValue(i) def updateTree(self, name): ''' adds DataItem to tree view ''' dataItem = QTreeWidgetItem(self.ui.dataView) dataItem.setText(0, name) dataItem.setFlags(Qt.ItemIsEnabled|Qt.ItemIsEditable) for i in range(len(self.datas[-1].data)/4): x = DataItem(dataItem) x.setText(0, self.datas[-1].name[i*4]) x.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable) x.setNumData(self.datas[-1].data[i*4]) if hasattr(self.datas[-1], 'triggers'): x.triggers = self.datas[-1].triggers if hasattr(self.datas[-1], 'frames'): x.depth = self.datas[-1].seekFrame def toggleEditMode(self, toggle): ''' button pressed to toggle edit mode for triggers - if pressed in edit mode again, dialog to save changes to a existing file ''' if toggle: item = self.ui.dataView.selectedItems()[-1] self.interactionMode = Interactor(self.canvas, [self.axes, self.axesRMS], item.triggers, self.lines, item.emg.shape[0]) else: self.interactionMode = None reply = QMessageBox.question(self, 'QMessageBox.question()', 'Update triggers?', QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: fileName, ok= QFileDialog.getOpenFileName( self, self.tr("Choose which file to overwrite data"), plattform.fixpath("D:\Master\emg\Benedictus"), self.tr("Pickle Files (*.pk)")) if ok: save(self.ui.dataView.selectedItems()[-1].triggers, fileName) def plotData(self): ''' clicking a DataItem will plot the raw data and rms of emg data - if oni files are present, it will connect the mouse position inside plots to frame number of video ''' ################# # clearing # ################# self.axes.clear() self.axesRMS.clear() if self.moveConnection is not None: self.canvas.mpl_disconnect(self.moveConnection) ######################## # get selected item # ######################## if len(self.ui.dataView.selectedItems()) > 0: item = self.ui.dataView.selectedItems()[-1] if hasattr(item, 'depth'): self.ui.offsetSlider.setEnabled(True) ''' if selected channel is from other dataset - slider is available - current frame of video is set - and new parent is saved ''' if item.parent() is not self.currentParent: self.offsetRecording = 0 self.ui.offsetSlider.setValue(150) if self.currentParent: self.currentParent.child(0).depth(-1) self.ri.setImage(item.depth(0)) self.currentParent = item.parent() ######################## # if deselected, reset # ######################## else: self.ui.offsetSlider.setEnabled(False) self.ri.setImage(np.zeros((1,1,1))) ########################## # plot selected data # ########################## for item in self.ui.dataView.selectedItems(): c = int(item.text(0)[-1])-1 if c >= len(colors): c = c % len(colors) self.axes.plot(item.emg, label=item.text(0), c=colors[c]) self.axesRMS.plot(feature.rootMeanSquare(np.abs(item.emg), 200), c=colors[c]) self.lines = [{}, {}] for p in item.triggers: self.lines[0][p], = self.axes.plot([p]*2, [np.min(item.emg), np.max(item.emg)], c='k', animated = True) self.lines[1][p], = self.axesRMS.plot([p]*2, [np.min(item.emg), np.max(item.emg)], c='k', animated = True) c+=1 # connect mouse position inside plots to frame index of video self.moveConnection = self.canvas.mpl_connect("motion_notify_event", self.updateVideo) self.axes.legend() self.canvas.draw() def onDraw(self, event): ''' partially updating plots see: http://wiki.scipy.org/Cookbook/Matplotlib/Animations#head-3d51654b8306b1585664e7fe060a60fc76e5aa08 ''' items = self.ui.dataView.selectedItems() if len(items)== 0: return for i in range(2): ax = [self.axes, self.axesRMS] for t in items[-1].triggers: ax[i].draw_artist(self.lines[i][t]) self.canvas.blit() def correctOffset(self, offs): self.offsetRecording = offs-150 def updateVideo(self, event): ''' set frame index according to mouse position ''' if event.xdata is not None: frameIdx = int(event.xdata/2000.0*30 + self.offsetRecording) if frameIdx < 0: frameIdx = 0 if len(self.ui.dataView.selectedItems()) > 0: if hasattr(self.ui.dataView.selectedItems()[-1], 'depth'): self.ri.setImage(self.ui.dataView.selectedItems()[-1].depth(frameIdx))
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 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 guiapp(QtGui.QMainWindow, cursortest.Ui_Dialog): def __init__(self): super(self.__class__, self).__init__() self.setupUi(self) self.setupfigure() self.populateplt() self.addcursor() self.visible = True self.horizOn = True self.vertOn = True self.useblit = True self.background = None self.needclear = False self.canvas.setVisible(True) cid2 = self.canvas.mpl_connect('button_press_event', self.onmove) def setupfigure(self): self.fig = Figure() self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self.plt2d) self.ax.set_title('title') self.ax.set_xlabel('x label') self.ax.set_ylabel('y label') # navigation toolbar self.navbar = NavigationToolbar(self.canvas, self.plt2d, coordinates=True) def populateplt(self): data = np.random.rand(200,200) self.ax.imshow(data) def addcursor(self): self.cursor = Cursor(self.ax,horizOn=True,vertOn=True,useblit=False, color='black') self.lineh = self.ax.axhline(self.ax.get_ybound()[0], visible=False) self.linev = self.ax.axvline(self.ax.get_xbound()[0], visible=False) def onmove(self, event): 'on mouse motion draw the cursor if visible' if event.inaxes != self.ax: self.linev.set_visible(False) self.lineh.set_visible(False) if self.needclear: self.canvas.draw() self.needclear = False return self.needclear = True if not self.visible: return self.linev.set_xdata((event.xdata, event.xdata)) self.lineh.set_ydata((event.ydata, event.ydata)) self.linev.set_visible(self.visible and self.vertOn) self.lineh.set_visible(self.visible and self.horizOn) self._update() print('%d, %d'%(self.linev.get_xydata()[0,0],self.lineh.get_xydata()[0,1])) def _update(self): if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) self.ax.draw_artist(self.linev) self.ax.draw_artist(self.lineh) self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() return False
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()