Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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)