class PlotWidget(Widget): """Base class for plot widgets based on matplotlib. Widget Framework ================ Fields/Properties ----------------- ax primary matplotlib.axes.Axes instance axline horizontal or vertical matplotlib.lines.Line2D instance used to indicate the current event state canvas FigureCanvas (FigureCanvasWxAgg) instance in which the *fig* is rendered cursor ( x, y ) plot location following the mouse cursorLine horizontal or vertical matplotlib.lines.Line2D instance following the mouse cursorLine2 horizontal or vertical matplotlib.lines.Line2D instance following the mouse for the non-primary axis data DataModel reference, getter is GetData() fig matplotlib.figure.Figure instance refAxis reference or non-magnitude axis, either 'y' (default) or 'x' #stateIndex #0-based state point index, getter is GetStateIndex() timeValue value of current time dataset Framework Methods ----------------- _CreateToolTipText() Should be implemented by extensions to create a tool tip based on the plot location returned in the event, ev.xdata when *refAxis* == 'x' and ev.ydata when *refAxis* == 'y'. _DoUpdatePlot() Called from _UpdatePlotImpl(), the implementation here creates a grid on *ax*, but extensions must override, calling super._DoUpdatePlot() to configure axes with labels and limits, create plots, add a legend, add a title, and/or set *axline*. _InitAxes() Called from _InitUI(), this default implementation creates a single axis, the *ax* property. Extensions should override to create axes and layout the plot as desired, calling Figure methods add_axes() or add_subplot(). _InitUI() Widget framework method implementation that creates the Figure (*fig*), calls _InitAxes(), creates the Canvas (*canvas*), and binds matplotlib and wxPython event handlers. _IsTimeReplot() True to replot on time change, False to redraw _LoadDataModel() Widget framework method implementation that calls _LoadDataModelValues() and then UpdateState(), the latter on the event UI thread. _LoadDataModelValues() Must be overridden by extensions to update event state properties and return the changes. _OnMplMouseRelease() Matplotlib event handler that checks for the right button (ev.button == 3) to pass onto wxPython event handling for the context menu. Extensions should override this method calling super._OnMplMouseRelease() and processing left button (ev.button == 1) events to update local event state properties by calling UpdateState() and firing state changes via FireStateChange(). _UpdateDataSetValues() Must be overridden by extensions to rebuild plot data arrays after a change in dataset selections UpdateState() Implements this Widget framework method by calling _UpdateStateValues(). A 'redraw' condition (currently does not occur) means the *canvas* must be redrawn via self.canvas.redraw(). A 'replot' condition means _UpdateDataSetValues() and _UpdatePlot() must be called. _UpdateStateValues() The implementation here handles 'state_index' changes, but extensions should override to handle widget-specific event state values, being sure to call super._UpdateStateValues(). Support Methods --------------- """ # -- Object Methods # -- #---------------------------------------------------------------------- # METHOD: __init__() - #---------------------------------------------------------------------- def __init__(self, container, id=-1, **kwargs): """ @param kwargs ref_axis reference axis 'x' or 'y', defaults to 'y' ref_axis2 2nd reference axis 'x' or 'y', defaults to None show_cursor toggle for showing cursor lines, defaults to True """ self.ax = None self.axline = None # axis line representing state self.canvas = None self.curSize = None self.cursor = None self.cursorLine = None # axis line following the cursor self.cursorLine2 = None # axis line following the cursor self.fig = None self.timer = None self.toolbar = None self.callbackIds = {} #self.isLoaded = False self.refAxis = kwargs.get('ref_axis', 'y') self.refAxis2 = kwargs.get('ref_axis2') self.showCursor = kwargs.get('show_cursor', True) #self.stateIndex = -1 self.timeValue = -1.0 self.titleFontSize = 16 super(PlotWidget, self).__init__(container, id) #end __init__ #---------------------------------------------------------------------- # METHOD: _CreateClipboardImage() - #---------------------------------------------------------------------- def _CreateClipboardImage(self): """Retrieves the currently-displayed bitmap. @return bitmap or None """ bmap = None fd, name = tempfile.mkstemp('.png') try: os.close(fd) if self.CreatePrintImage(name): bmap = wx.Image(name, wx.BITMAP_TYPE_PNG).ConvertToBitmap() finally: os.remove(name) return bmap #end _CreateClipboardImage #---------------------------------------------------------------------- # METHOD: CreatePrintImage() - #---------------------------------------------------------------------- def CreatePrintImage(self, file_path, bgcolor=None, hilite=False): """ """ result = None if self.fig is not None: #if wx.IsMainThread(): if not hilite: if self.cursorLine is not None: self.cursorLine.set_visible(False) if self.cursorLine2 is not None: self.cursorLine2.set_visible(False) if self.axline is not None: self.axline.set_visible(False) self._DoUpdateRedraw(False) self.canvas.draw() if bgcolor and hasattr(bgcolor, '__iter__') and len(bgcolor) >= 3: fc = tuple([bgcolor[i] / 255.0 for i in xrange(3)]) else: fc = self.fig.get_facecolor() # Sleep needed when animating to prevent matplotlib errors generating # tick marks on Mac. Some day we must figure out why. It seems to have # to do with wxPython and the MainThread. time.sleep(0.5) self.fig.savefig(file_path, dpi=216, format='png', orientation='landscape', facecolor=fc) # dpi = 144 result = file_path #if wx.IsMainThread(): if not hilite: if self.axline is not None: self.axline.set_visible(True) if self.cursorLine is not None: self.cursorLine.set_visible(True) if self.cursorLine2 is not None: self.cursorLine2.set_visible(True) self._DoUpdateRedraw() self.canvas.draw() #end if return result #end CreatePrintImage #---------------------------------------------------------------------- # METHOD: _CreateToolTipText() - #---------------------------------------------------------------------- def _CreateToolTipText(self, ev): """Create a tool tip. This implementation returns a blank string. @param ev matplotlib mouse motion event """ return '' #end _CreateToolTipText #---------------------------------------------------------------------- # METHOD: _DoUpdatePlot() - #---------------------------------------------------------------------- def _DoUpdatePlot(self, wd, ht): """Do the work of creating the plot, setting titles and labels, configuring the grid, plotting, and creating self.axline. This implementation calls self.ax.grid() and can be called by subclasses. """ self.ax.grid(True, 'both', 'both', color='#c8c8c8', linestyle=':', linewidth=1) #end _DoUpdatePlot #---------------------------------------------------------------------- # METHOD: _DoUpdateRedraw() - #---------------------------------------------------------------------- def _DoUpdateRedraw(self, hilite=True): """Update for a redraw only. """ pass #end _DoUpdateRedraw #---------------------------------------------------------------------- # METHOD: GetStateIndex() - #---------------------------------------------------------------------- def GetStateIndex(self): """@return 0-based state/time index """ return self.stateIndex #end GetStateIndex #---------------------------------------------------------------------- # METHOD: GetTimeValue() - #---------------------------------------------------------------------- def GetTimeValue(self): """@return 0-based state/time index """ return self.timeValue #end GetTimeValue #---------------------------------------------------------------------- # METHOD: PlotWidget.GetUsesScaleAndCmap() - #---------------------------------------------------------------------- def GetUsesScaleAndCmap(self): """ Returns: boolean: False """ return False #end GetUsesScaleAndCmap #---------------------------------------------------------------------- # METHOD: _InitAxes() - #---------------------------------------------------------------------- def _InitAxes(self): """Initialize axes. By default creates a single axis 'ax'. """ self.ax = self.fig.add_subplot(111) #end _InitAxes #---------------------------------------------------------------------- # METHOD: _InitUI() - #---------------------------------------------------------------------- def _InitUI(self, two_axes=False): """Builds this UI component. Obviously, must be called in the UI thread. """ dpis = wx.ScreenDC().GetPPI() size = (WIDGET_PREF_SIZE[0] / dpis[0], WIDGET_PREF_SIZE[1] / dpis[0]) self.fig = Figure(facecolor='#ececec', figsize=size, dpi=dpis[0]) self._InitAxes() # if two_axes: # self.ax = self.fig.add_axes([ 0.1, 0.1, 0.85, 0.65 ]) # self.ax2 = self.ax.twiny() # else: # self.ax = self.fig.add_subplot( 111 ) self.canvas = FigureCanvas(self, -1, self.fig) self.canvas.SetMinClientSize(wx.Size(200, 200)) self.toolbar = NavigationToolbar(self.canvas) #self.toolbar.Realize() self.toolbar.SetBackgroundColour(wx.Colour(236, 236, 236, 255)) self.toolbar.Show(False) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.toolbar, 0, wx.LEFT | wx.TOP | wx.BOTTOM | wx.EXPAND, 1) sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.BOTTOM | wx.EXPAND, 1) self.SetSizer(sizer) self.callbackIds[ 'button_release_event' ] = \ self.canvas.mpl_connect( 'button_release_event', self._OnMplMouseRelease ) self.callbackIds[ 'motion_notify_event' ] = \ self.canvas.mpl_connect( 'motion_notify_event', self._OnMplMouseMotion ) self.Bind(wx.EVT_CLOSE, self._OnClose) self.Bind(wx.EVT_CONTEXT_MENU, self._OnContextMenu) self.Bind(wx.EVT_SIZE, self._OnSize) self.timer = wx.Timer(self, TIMERID_RESIZE) self.Bind(wx.EVT_TIMER, self._OnTimer) #end _InitUI #---------------------------------------------------------------------- # METHOD: IsDataSetScaleCapable() - #---------------------------------------------------------------------- def IsDataSetScaleCapable(self): """Returns false. Returns: bool: False """ return False #end IsDataSetScaleCapable #---------------------------------------------------------------------- # METHOD: _IsTimeReplot() - #---------------------------------------------------------------------- def _IsTimeReplot(self): """Returns True if the widget replots on a time change, False it it merely redraws. Defaults to True. Should be overridden as necessary. """ return True #end _IsTimeReplot #---------------------------------------------------------------------- # METHOD: _LoadDataModel() - #---------------------------------------------------------------------- def _LoadDataModel(self, reason): """Updates the components for the current model. xxx need loaded flag set when LoadProps() is called so you don't call _LoadDataModelValues() """ if not self.isLoading: update_args = self._LoadDataModelValues(reason) if 'replot' in update_args: wx.CallAfter(self.UpdateState, replot=True) #end _LoadDataModel #---------------------------------------------------------------------- # METHOD: _LoadDataModelValues() - #---------------------------------------------------------------------- def _LoadDataModelValues(self, reason): """This noop version should be implemented in subclasses to create a dict to be passed to UpdateState(). Assume self.dmgr is valid. @return dict to be passed to UpdateState() """ return {} #end _LoadDataModelValues #---------------------------------------------------------------------- # METHOD: PlotWidget.LoadProps() - #---------------------------------------------------------------------- def LoadProps(self, props_dict): """Called to load properties. This implementation takes care of 'dataSetSelections' and 'timeValue', but subclasses must override for all other properties. @param props_dict dict object from which to deserialize properties """ for k in ('timeValue', ): if k in props_dict: setattr(self, k, props_dict[k]) for k in ('dataSetSelections', ): if k in props_dict: cur_attr = props_dict[k] for name in cur_attr.keys(): cur_value = cur_attr[name] del cur_attr[name] cur_attr[DataSetName(name)] = cur_value #end for name setattr(self, k, cur_attr) #end if k in props_dict #end for k super(PlotWidget, self).LoadProps(props_dict) self.container.dataSetMenu.UpdateAllMenus() wx.CallAfter(self.UpdateState, replot=True) #end LoadProps #---------------------------------------------------------------------- # METHOD: _OnClose() - #---------------------------------------------------------------------- def _OnClose(self, ev): """ """ if self.fig is not None: if self.logger.isEnabledFor(logging.INFO): self.logger.debug('%s: closing figure', self.GetTitle()) self.fig.close() #end _OnClose #---------------------------------------------------------------------- # METHOD: _OnContextMenu() - #---------------------------------------------------------------------- def _OnContextMenu(self, ev): """ """ ev_obj = ev.GetEventObject() if ev_obj.HasCapture(): ev_obj.ReleaseMouse() pos = ev.GetPosition() pos = self.ScreenToClient(pos) menu = self.GetPopupMenu() self.PopupMenu(menu, pos) #end _OnContextMenu #---------------------------------------------------------------------- # METHOD: _OnMplMouseMotion() - #---------------------------------------------------------------------- def _OnMplMouseMotion(self, ev): tip_str = '' if ev.inaxes is None: self.cursor = None if self.cursorLine is not None: self.cursorLine.set_visible(False) if self.cursorLine2 is not None: self.cursorLine2.set_visible(False) self.canvas.draw() #self.canvas.SetToolTipString( '' ) elif self.ax is not None: if self.cursorLine is None and self.showCursor: self.cursorLine = \ self.ax.axhline( color = 'k', linestyle = '--', linewidth = 1 ) \ if self.refAxis == 'y' else \ self.ax.axvline( color = 'k', linestyle = '--', linewidth = 1 ) \ if self.refAxis2 and self.cursorLine2 is None and self.showCursor: self.cursorLine2 = \ self.ax.axhline( color = 'k', linestyle = '--', linewidth = 1 ) \ if self.refAxis2 == 'y' else \ self.ax.axvline( color = 'k', linestyle = '--', linewidth = 1 ) \ self.cursor = (ev.xdata, ev.ydata) if self.showCursor: if self.refAxis == 'y': self.cursorLine.set_ydata(ev.ydata) else: self.cursorLine.set_xdata(ev.xdata) if self.refAxis2 == 'y': self.cursorLine2.set_ydata(ev.ydata) elif self.refAxis2 == 'x': self.cursorLine2.set_xdata(ev.xdata) self.cursorLine.set_visible(True) if self.cursorLine2: self.cursorLine2.set_visible(True) #end if self.showCursor self.canvas.draw() tip_str = self._CreateToolTipText(ev) #self.canvas.SetToolTipString( tip_str ) #end elif self.canvas.SetToolTipString(tip_str) #end _OnMplMouseMotion #---------------------------------------------------------------------- # METHOD: _OnMplMouseRelease() - #---------------------------------------------------------------------- def _OnMplMouseRelease(self, ev): """Handle click to set the state value. This implementation checks for a right-click and calls Skip() to pass the event to the wxPython window. Subclasses should call this method with super. """ if ev.button == 3: ev.guiEvent.Skip() #end _OnMplMouseRelease #---------------------------------------------------------------------- # METHOD: _OnSize() - #---------------------------------------------------------------------- def _OnSize(self, ev): """ """ if ev is None: self.curSize = None if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug('%s: forced replot', self.GetTitle()) wx.CallAfter(self.UpdateState, replot=True) else: ev.Skip() wd, ht = self.GetClientSize() if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug('%s: clientSize=%d,%d', self.GetTitle(), wd, ht) if wd > 0 and ht > 0: if self.curSize is None or \ wd != self.curSize[ 0 ] or ht != self.curSize[ 1 ]: self.curSize = (wd, ht) if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug('%s: starting timer', self.GetTitle()) self.timer.Start(500, wx.TIMER_ONE_SHOT) # wd, ht = self.GetClientSize() # if wd > 0 and ht > 0: # if self.curSize is None or \ # wd != self.curSize[ 0 ] or ht != self.curSize[ 1 ]: # self.curSize = ( wd, ht ) # if self.logger.isEnabledFor( logging.DEBUG ): # self.logger.debug( '%s: starting timer', self.GetTitle() ) # self.timer.Start( 500, wx.TIMER_ONE_SHOT ) #end else ev is not None #end _OnSize #---------------------------------------------------------------------- # METHOD: _OnSize_0() - #---------------------------------------------------------------------- ## def _OnSize_0( self, ev ): ## """ ##""" ## if ev is not None: ## ev.Skip() ## ## wd, ht = self.GetClientSize() ## ## if wd > 0 and ht > 0: ## self.UpdateState( replot = True ) ## #end _OnSize_0 #---------------------------------------------------------------------- # METHOD: _OnSize_1() - #---------------------------------------------------------------------- ## def _OnSize_1( self, ev ): ## """ ##""" ## if ev is None: ## # call wx.CallAfter( self.UpdateState, replot = True ) here? ## self.curSize = None ## else: ## ev.Skip() ## ## wd, ht = self.GetClientSize() ## ## if wd > 0 and ht > 0: ## if self.curSize is None or \ ## wd != self.curSize[ 0 ] or ht != self.curSize[ 1 ]: ## self.curSize = ( wd, ht ) ## if self.logger.isEnabledFor( logging.DEBUG ): ## self.logger.debug( '%s: calling timer', self.GetTitle() ) ## self.timer.Start( 500, wx.TIMER_ONE_SHOT ) ## #end _OnSize_1 #---------------------------------------------------------------------- # METHOD: PlotWidget._OnTimer() - #---------------------------------------------------------------------- def _OnTimer(self, ev): """ """ if ev.Timer.Id == TIMERID_RESIZE: #wd, ht = self.GetClientSize() if self.curSize is not None: if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug('%s: calling UpdateState redraw', self.GetTitle()) wx.CallAfter(self.UpdateState, redraw=True) # replot = true #end if ev.Timer.Id == TIMERID_RESIZE #end _OnTimer #---------------------------------------------------------------------- # METHOD: _OnToggleToolBar() - #---------------------------------------------------------------------- def _OnToggleToolBar(self, ev): """ """ ev.Skip() if self.toolbar.IsShown(): self.toolbar.home(False) self.toolbar.Show(False) self.callbackIds[ 'button_release_event' ] = self.\ canvas.mpl_connect( 'button_release_event', self._OnMplMouseRelease ) self.callbackIds[ 'motion_notify_event' ] = self.\ canvas.mpl_connect( 'motion_notify_event', self._OnMplMouseMotion ) else: # for k, id in self.callbackIds.iteritems(): # self.canvas.mpl_disconnect( id ) self.toolbar.Show(True) self.GetSizer().Layout() #end _OnToggleToolBar #---------------------------------------------------------------------- # METHOD: PlotWidget.SaveProps() - #---------------------------------------------------------------------- def SaveProps(self, props_dict, for_drag=False): """Called to load properties. This implementation takes care of 'dataSetSelections' and 'timeValue', but subclasses must override for all other properties. @param props_dict dict object to which to serialize properties """ super(PlotWidget, self).SaveProps(props_dict, for_drag=for_drag) for k in ('timeValue', ): props_dict[k] = getattr(self, k) for k in ('dataSetSelections', ): if hasattr(self, k): cur_attr = getattr(self, k) if isinstance(cur_attr, dict): for name in cur_attr.keys(): if isinstance(name, DataSetName): cur_value = cur_attr[name] del cur_attr[name] cur_attr[name.name] = cur_value #end for name #end if isinstance( cur_value, dict ) props_dict[k] = cur_attr #end if hasattr( self, k ) #end for k #end SaveProps #---------------------------------------------------------------------- # METHOD: _UpdateDataSetValues() - #---------------------------------------------------------------------- def _UpdateDataSetValues(self): """This noop version must be overridden by subclasses. """ pass #end _UpdateDataSetValues #---------------------------------------------------------------------- # METHOD: _UpdatePlot() - #---------------------------------------------------------------------- def _UpdatePlot(self): """ Must be called from the UI thread. """ self._BusyDoOp(self._UpdatePlotImpl) #end _UpdatePlot #---------------------------------------------------------------------- # METHOD: _UpdatePlotImpl() - #---------------------------------------------------------------------- def _UpdatePlotImpl(self): """ Must be called from the UI thread. """ if self.ax is not None: self.axline = None self.cursorLine = \ self.cursorLine2 = None # self.ax.clear() # if hasattr( self, 'ax2' ) and self.ax2 is not None: # self.ax2.clear() self.fig.clear() self._InitAxes() # -- Scale fonts # -- wd, ht = self.GetClientSize() label_font_size = 14 tick_font_size = 12 self.titleFontSize = 16 if 'wxMac' not in wx.PlatformInfo and wd < 800: decr = (800 - wd) / 50.0 label_font_size -= decr tick_font_size -= decr self.titleFontSize -= decr # self.ax.grid( # True, 'both', 'both', # color = '#c8c8c8', linestyle = ':', linewidth = 1 # ) self._DoUpdatePlot(wd, ht) self._DoUpdateRedraw() self.canvas.draw() #end if #end _UpdatePlotImpl #---------------------------------------------------------------------- # METHOD: UpdateState() - # Must be called from the UI thread. #---------------------------------------------------------------------- def UpdateState(self, **kwargs): """ Must be called from the UI thread. """ if bool(self): if 'scale_mode' in kwargs: kwargs['replot'] = True kwargs = self._UpdateStateValues(**kwargs) redraw = kwargs.get('redraw', False) replot = kwargs.get('replot', False) if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug('%s: redraw=%s, replot=%s', self.GetTitle(), str(redraw), str(replot)) if replot: self._UpdateDataSetValues() self._UpdatePlot() elif redraw: self._DoUpdateRedraw() self.canvas.draw() #end UpdateState #---------------------------------------------------------------------- # METHOD: _UpdateStateValues() - # Must be called from the UI thread. #---------------------------------------------------------------------- def _UpdateStateValues(self, **kwargs): """ Must be called from the UI thread. @return kwargs with 'redraw' and/or 'replot' """ #replot = kwargs.get( 'replot', False ) #redraw = kwargs.get( 'redraw', kwargs.get( 'force_redraw', False ) ) replot = kwargs.get('replot', kwargs.get('force_redraw', False)) redraw = kwargs.get('redraw', False) if 'data_model_mgr' in kwargs: replot = True if 'dataset_added' in kwargs: wx.CallAfter(self.container.GetDataSetMenu().UpdateAllMenus) if 'time_value' in kwargs and kwargs['time_value'] != self.timeValue: if self._IsTimeReplot(): replot = True else: redraw = True self.timeValue = kwargs['time_value'] if redraw: kwargs['redraw'] = True if replot: kwargs['replot'] = True return kwargs
class stdplot: def __init__(self,x,y, yerr, name, logx = False, logy = False, xlab = "x", leg = True): self.xaxis = list(x) self.data = list(y) self.error = list(yerr) # Plot a legend? self.leg = leg # Setup gridspec gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1]) gs.update(wspace=0.00, hspace=0.00) self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.ax1 = self.fig.add_subplot(gs[0]) self.ax2 = self.fig.add_subplot(gs[1]) # Logarithmic x axis self.logx = logx if self.logx == True: self.ax1.set_xscale('log') self.ax2.set_xscale('log') # Logarithmic y axis self.logy = logy if self.logy == True: self.ax1.set_yscale('log') # Disable plot x ticks plt.setp(self.ax1.get_xticklabels(), visible=False) # Axis formatting self.ax2.yaxis.tick_right() self.ax2.yaxis.set_major_locator(MaxNLocator(5,prune='upper')) # Axis labels self.ax2.set_xlabel(xlab) # gridlines self.ax1.xaxis.grid(True) self.ax1.yaxis.grid(True) self.ax2.xaxis.grid(True) self.ax2.yaxis.grid(True) # Plot title self.fig.text(0.13,0.94,name, fontsize=12) def addTheory(self,CV, ER, name): # Higher and Lower boundaries CVup = list(map(add, CV, ER)) CVdn = list(map(sub, CV, ER)) if len(CV) > 1: # plot CVs tmpplot1, = self.ax1.plot(self.xaxis, CV, label=name) tmpplot2, = self.ax2.plot(self.xaxis, numpy.divide(CV,self.data), label=name) # error bars self.ax1.fill_between(self.xaxis, CVdn, CVup, alpha=0.2, facecolor=tmpplot1.get_color(), linewidth=0) # error bars self.ax2.fill_between(self.xaxis, numpy.divide(CVdn,self.data), numpy.divide(CVup,self.data), alpha=0.2, facecolor=tmpplot2.get_color(), linewidth = 0) else: bar1 = self.ax1.errorbar(self.xaxis, CV, ER, fmt = 'x', markersize=4, label=name) self.ax2.errorbar(self.xaxis, numpy.divide(CV,self.data), numpy.divide(ER,self.data), fmt = 'x', markersize=4, label=name) def savePlot(self,plotname): # Plot datapoints self.ax1.errorbar(self.xaxis, self.data, yerr=self.error, fmt='.', color='black') self.ax2.errorbar(self.xaxis, [1]*len(self.data), yerr=numpy.divide(self.error,self.data),fmt='.', color='black') # Adjust axes if len(self.data) > 2: xdiff = 5.0*((self.xaxis[-1] - self.xaxis[0])/100.0) axes=self.ax1.axis() self.ax1.axis([self.xaxis[0] - xdiff, self.xaxis[-1] +xdiff, axes[2], axes[3]]) axes2=self.ax2.axis() self.ax2.axis([self.xaxis[0] - xdiff, self.xaxis[-1] +xdiff, axes2[2], axes2[3]]) #if self.leg == True: self.legend = self.ax1.legend(loc='best') self.legend.get_frame().set_alpha(0.8) self.fig.savefig(plotname+'.pdf') def close(self): self.fig.close()
class calibrate(): #fixed for now gs = gridspec.GridSpec(2, 1, height_ratios=[1, 1]) error = 1 bins = 50 def __init__(self, set1='lezynehardknott.csv', set2='tacxhardknott.csv'): self.fig = Figure() self.set1 = Csver(set1) self.set2 = Csver(set2) #force use secs and watts for now self.time = self.set1.column('secs') self.ma1 = self.set1.movingAverage('watts') self.ma2 = self.set2.movingAverage('watts') self.mdiff = np.absolute(self.ma1 - self.ma2) * 100 / ( (self.ma1 + self.ma2) / 2.0) self.maxerr = np.max(self.mdiff) self.c**k, self.balls = np.histogram(self.mdiff, bins=self.bins, range=(0, self.maxerr)) self.ax1 = self.fig.add_subplot(self.gs[0]) self.ax2 = self.fig.add_subplot(self.gs[1]) self.ax1.plot(self.time, self.ma1, label=set1) self.yerr = self.ma1 * 0.01 self.ax1.fill_between(self.time, self.ma1 - self.yerr, self.ma1 + self.yerr, alpha=0.5) self.ax1.plot(self.time, self.ma2, label=set2) self.ax1.fill_between(self.time, self.ma2 - self.yerr, self.ma2 + self.yerr, alpha=0.5) self.ax1.legend() self.ax1.margins(0) self.ax1.set_xlabel("time [s]") self.ax1.set_ylabel("power [W]") self.hor1 = self.percentile(0.5, self.c**k) self.ver1 = self.percentile(0.5, self.mdiff) self.hor2 = self.percentile(0.9, self.c**k) self.ver2 = self.percentile(0.9, self.mdiff) self.hor3 = self.percentile(0.95, self.c**k) self.ver3 = self.percentile(0.95, self.mdiff) print(self.ver3) self.ax2.hist(self.mdiff, bins=self.bins, range=(0, self.maxerr), cumulative=True, histtype='step') ein = self.ax2.hlines(y=self.ver1, xmin=0, xmax=self.hor1, colors='blue', label='50%') self.ax2.vlines(x=self.hor1, ymin=0, ymax=self.ver1, colors='blue') zwei = self.ax2.hlines(y=self.ver2, xmin=0, xmax=self.hor2, colors='orange', label='90%') self.ax2.vlines(x=self.hor2, ymin=0, ymax=self.ver2, colors='orange') drei = self.ax2.hlines(y=self.ver3, xmin=0, xmax=self.hor3, colors='pink', label='95%') self.ax2.vlines(x=self.hor3, ymin=0, ymax=self.ver3, colors='pink') self.extraticks = (self.hor1, self.hor2, self.hor3) self.ax2.set_xticks( list(self.ax2.get_xticks()) + list(self.extraticks)) self.ax2.legend((ein, zwei, drei), ('50%', '90%', '95%'), title='events percentile') self.ax2.margins(0) self.ax2.xaxis.set_minor_locator(AutoMinorLocator()) self.ax2.set_xlabel("% discrepancy between power values") self.ax2.set_ylabel("# of time events") def dotProduct(self): f1 = self.ma1 f2 = self.ma2 #measure of similarity return np.sum(f1 * f2) / np.sqrt(np.sum(f1**2) * np.sum(f2**2)) def percentile(self, n, quantity): #n between [0,1] return (np.cumsum(quantity) < np.sum(quantity) * n).sum() def draw(self): return self.fig def clear(self): self.fig.close()
def save_lasing(lasing_file): if 'Lasing_reconstruction' in lasing_file: return if os.path.basename( lasing_file ) == '2021_05_18-18_11_27_Lasing_True_SARBD02-DSCR050.h5': return try: #data_dict = h5_storage.loadH5Recursive(lasing_file) with h5py.File(lasing_file, 'r') as data_dict: for key in 'raw_data', 'pyscan_result': if key in data_dict: data_dict = data_dict[key] #data_dict = h5_storage.loadH5Recursive(lasing_snapshot)['camera1'] if 'Calibration_SARUN' in lasing_file or 'Calibration_data_SARUN' in lasing_file: x_axis = np.array(data_dict['x_axis']).squeeze()[0, 0] y_axis = np.array(data_dict['y_axis']).squeeze()[0, 0] images0 = np.array(data_dict['image']).squeeze() shape = images0.shape images = images0.reshape( [shape[0] * shape[1], shape[2], shape[3]]) else: x_axis = np.array(data_dict['x_axis']).squeeze()[0] y_axis = np.array(data_dict['y_axis']).squeeze()[0] images = np.array(data_dict['image']).squeeze() except KeyError: print('Error for file %s' % os.path.basename(lasing_file)) error_files.append(lasing_file) return extent = (x_axis[0], x_axis[-1], y_axis[-1], y_axis[0]) sp_ctr = np.inf image_ctr = 1 figs = [] #if True: # image = images # n_image = 0 for n_image, image in enumerate(images): if sp_ctr + 1 > ny * nx: fig = Figure(figsize=(20, 16)) plt.suptitle(os.path.basename(lasing_file) + ' %i' % image_ctr) fig.subplots_adjust(hspace=0.4) image_ctr += 1 sp_ctr = 1 figs.append(fig) sp0 = subplot(sp_ctr, title='Image %i raw' % n_image, xlabel='x ($\mu$m)', ylabel='y ($\mu$m)') sp_ctr += 1 sp1 = subplot(sp_ctr, title='Image %i processed' % n_image, xlabel='x ($\mu$m)', ylabel='y ($\mu$m)') sp_ctr += 1 min_index_x = np.argwhere(x_axis0 == x_axis[0]).squeeze() max_index_x = np.argwhere(x_axis0 == x_axis[-1]).squeeze() min_index_y = np.argwhere(y_axis0 == y_axis[0]).squeeze() max_index_y = np.argwhere(y_axis0 == y_axis[-1]).squeeze() image_recover = image.copy() mask_dest = image == 0 image_recover += bg[min_index_y:max_index_y + 1, min_index_x:max_index_x + 1] image_recover[mask_dest] = 0 for im, sp in [(image, sp0), (image_recover, sp1)]: image_float = im.astype(np.float64) image_floored = image_float - np.median(image_float) image_floored[image_floored < 0] = 0 image_normed = image_floored / np.max(image_floored) colors = cmap(image_normed) colors[mask_dest] = np.array([1, 0, 0, 1]) sp.imshow(colors, extent=extent, aspect='auto') ms.saveall('./album_2021-05-18/%s' % os.path.basename(lasing_file)) for fig in figs: fig.close()
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)