Esempio n. 1
0
 def __init__(self, *args, **kwargs):
     """Identical to Slider.__init__, except for the "increment" kwarg.
     "increment" specifies the step size that the slider will be discritized
     to."""
     self.inc = kwargs.pop('increment', 0.5)
     self.discrete_val = round(kwargs['valinit'] / self.inc) * self.inc
     Slider.__init__(self, *args, **kwargs)
Esempio n. 2
0
 def __init__(self, *args, **kwargs):
     """Identical to Slider.__init__, except for the "increment" kwarg.
     "increment" specifies the step size that the slider will be discritized
     to."""
     self.inc = kwargs.pop("increment", 1.0)
     Slider.__init__(self, *args, **kwargs)
     self.dval = int(self.val + 0.5)
Esempio n. 3
0
 def __init__(self, *args, **kwargs):
     """
     Identical to Slider.__init__, except for the "increment" kwarg.
     "increment" specifies the step size that the slider will be
     discretized to.
     """
     self.inc = kwargs.pop('increment', 0.5)
     Slider.__init__(self, *args, **kwargs)
 def __init__(self, *args, **kwargs):
     """
     Identical to Slider.__init__, except for the new keyword 'allowed_vals'.
     This keyword specifies the allowed positions of the slider
     """
     self.allowed_vals = kwargs.pop('allowed_vals', None)
     self.previous_val = kwargs['valinit']
     Slider.__init__(self, *args, **kwargs)
     if self.allowed_vals == None:
         self.allowed_vals = [self.valmin, self.valmax]
 def __init__(self, *args, **kwargs):
     self.inc = kwargs.pop('increment', 1.0)
     self.param = kwargs.pop('param')
     self.item  = kwargs.pop('item')
     
     self.val = None
     Slider.__init__(self,*args, **kwargs)
     self.on_changed( self.replot )
     
     return
Esempio n. 6
0
 def __init__(self, *args, **kwargs):
     self.previous_val = kwargs['valinit']
     Slider.__init__(self, *args, **kwargs)
Esempio n. 7
0
 def __init__(self, *args, **kwargs):
     """ "increment" specifies the step size that the slider will be discritized
     to."""
     self.inc = kwargs.pop('increment', 0.5)
     Slider.__init__(self, *args, **kwargs)
Esempio n. 8
0
    def __init__(
        self,
        ax,
        sliderLabel,
        depths,
        updateFunc,
        selectedDepthColor,
        fontsize=8,
        valInit=0,
        **kwargs,
    ):
        # The color of the currently displayed depth page.
        self.selectedDepthColor = selectedDepthColor
        self.nonSelectedDepthColor = "w"

        self.depths = depths

        # Make the selection depth buttons
        self.depthSelections = []
        numDepths = float(len(depths))
        rectangleBot = 0
        textYCoord = 0.5
        # startBoundaries go from zero to just below 1.
        leftBoundary = [i / numDepths for i, _depths in enumerate(depths)]
        for leftBoundary, depth in zip(leftBoundary, depths):
            # First depth (leftBoundary==0) is on, rest are off.
            if leftBoundary == 0:
                color = self.selectedDepthColor
            else:
                color = self.nonSelectedDepthColor
            depthSelectBox = matplotlib.patches.Rectangle(
                (leftBoundary, rectangleBot),
                1.0 / numDepths,
                1,
                transform=ax.transAxes,
                facecolor=color,
            )
            ax.add_artist(depthSelectBox)
            self.depthSelections.append(depthSelectBox)

            # Make text halfway into box
            textXCoord = leftBoundary + 0.5 / numDepths
            ax.text(
                textXCoord,
                textYCoord,
                "{:.1f}".format(depth),
                ha="center",
                va="center",
                transform=ax.transAxes,
                fontsize=fontsize,
            )

        # Make forward and backward button
        backwardArrow, forwardArrow = "$\u25C0$", "$\u25B6$"
        divider = axes_grid1.make_axes_locatable(ax)
        buttonWidthPercent = "5%"
        backwardAxes = divider.append_axes("right", size=buttonWidthPercent, pad=0.03)
        forwardAxes = divider.append_axes("right", size=buttonWidthPercent, pad=0.03)
        self.backButton = matplotlib.widgets.Button(
            backwardAxes,
            label=backwardArrow,
            color=self.nonSelectedDepthColor,
            hovercolor=self.selectedDepthColor,
        )
        self.backButton.label.set_fontsize(fontsize)
        self.backButton.on_clicked(self.previous)
        self.forwardButton = matplotlib.widgets.Button(
            forwardAxes,
            label=forwardArrow,
            color=self.nonSelectedDepthColor,
            hovercolor=self.selectedDepthColor,
        )
        self.forwardButton.label.set_fontsize(fontsize)
        self.forwardButton.on_clicked(self.next)

        # init at end since slider will set val to 0, and it needs to have state
        # setup before doing that
        Slider.__init__(self, ax, sliderLabel, 0, len(depths), valinit=0, **kwargs)
        self.on_changed(updateFunc)
        self.set_val(valInit)  # need to set after updateFunc is added.

        # Turn off value visibility since the buttons text shows the value
        self.valtext.set_visible(False)
 def __init__(self, *args, **kwargs):
     self.inc = kwargs.pop('increment', 1)
     self.valfmt = '%s'
     Slider.__init__(self, *args, **kwargs)
Esempio n. 10
0
 def __init__(self, *args, **kwargs):
     """Identical to Slider.__init__, except for the "increment" kwarg.
     "increment" specifies the step size that the slider will be discritized
     to."""
     self.inc = kwargs.pop('increment')
     Slider.__init__(self, *args, **kwargs)
Esempio n. 11
0
 def __init__(self, *args, **kwargs):
     """Identical to Slider.__init__, except for the "steps" kwarg.
     "steps" is a list of discretized values for the slider."""
     self.steps = kwargs.pop('steps', None)
     Slider.__init__(self, *args, **kwargs)
Esempio n. 12
0
 def __init__(self, *args, **kwargs):
     self.inc = kwargs.pop('increment', 1)
     self.valfmt = '%s'
     Slider.__init__(self, *args, **kwargs)
     self.changecallback = lambda v: ()
Esempio n. 13
0
 def __init__(self, *args, **kwargs):
     self.previous_val = kwargs['valinit']
     Slider.__init__(self, *args, **kwargs)
Esempio n. 14
0
class guiMenu:
    fig = None

    gauss_params = [33, 7]

    logTextLabels = []
    logText = []

    axMisc = None
    axAlgorithm = None
    axSaveOptions = None
    axGauss = None
    axLog = None
    axRadio = None
    axLogLabels = None

    line_gaussian = None

    ax_window_size = None
    slider_window_size = None

    ax_sigma = None
    slider_sigma = None

    originalStdOut = None

    #------------------------------------------------------------------------------
    # Class initialization
    #
    def __init__(self):
        self.strTitle = 'Gesture Analysis Configuration - ' + settings.appVersion
        self.fig = plt.figure()
        self.fig.canvas.set_window_title(self.strTitle)
        self.fig.set_size_inches((settings.screen_cx * 0.49) / self.fig.dpi,
                                 (settings.screen_cy * 0.465) / self.fig.dpi)

        self.fig.canvas.mpl_connect('resize_event', self.onresize)
        self.fig.canvas.mpl_connect('close_event', self.onclose)

        self.fig.edgecolor = 'blue'
        self.fig.set_facecolor('0.90')

        left = 0.03  # the left side of the subplots of the figure
        right = 0.97  # the right side of the subplots of the figure
        bottom = 0.04  # the bottom of the subplots of the figure
        top = 0.92  # the top of the subplots of the figure
        wspace = 0.3  # the amount of width reserved for blank space between subplots
        hspace = 0.7  # the amount of height reserved for white space between subplots

        plt.subplots_adjust(left=left,
                            top=top,
                            right=right,
                            bottom=bottom,
                            wspace=wspace,
                            hspace=hspace)

        self.axMisc = plt.subplot2grid((6, 4), (0, 0),
                                       rowspan=1,
                                       colspan=1,
                                       aspect='auto',
                                       anchor='NW')
        self.axAlgorithm = plt.subplot2grid((6, 4), (1, 0),
                                            rowspan=2,
                                            colspan=1,
                                            aspect='auto',
                                            anchor='NW')
        self.axSaveOptions = plt.subplot2grid((6, 4), (3, 0),
                                              rowspan=3,
                                              colspan=1,
                                              aspect='equal',
                                              anchor='NW')
        self.axGauss = plt.subplot2grid((6, 4), (0, 1), rowspan=4, colspan=3)
        self.axLog = plt.subplot2grid((6, 4), (5, 1),
                                      rowspan=1,
                                      colspan=3,
                                      aspect='auto',
                                      anchor='NW')

        # create the various groups of UI controls
        self.createUIMiscellaneous()
        self.createUILog()

        self.createUIAlgorithm()
        self.createUIGaussianFilterControls()
        self.createUIMiscellaneousControls()

        # set settings.tkGuiCanvas for use for modal dialogs
        try:
            if (self.fig is not None) and (self.fig.canvas is not None) and (
                    self.fig.canvas._tkcanvas is not None):
                settings.tkGuiCanvas = self.fig.canvas._tkcanvas
        except Exception as e:
            pass

    #------------------------------------------------------------------------------
    #
    def get_aspect(self, ax):
        # Total figure size
        figW, figH = ax.get_figure().get_size_inches()
        # Axis size on figure
        _, _, w, h = ax.get_position().bounds
        # Ratio of display units
        disp_ratio = (figH * h) / (figW * w)
        # Ratio of data units
        # Negative over negative because of the order of subtraction
        data_ratio = sub(*ax.get_ylim()) / sub(*ax.get_xlim())

        return disp_ratio / data_ratio

    #------------------------------------------------------------------------------
    #
    def createUIAlgorithm(self):
        algorithm = settings.application.algorithm.lower()
        defaultAlgoritmIdx = 0
        if (algorithm == 'total'):
            defaultAlgoritmIdx = 0
        elif (algorithm == 'parallel'):
            defaultAlgoritmIdx = 1
        elif (algorithm == 'naive'):
            defaultAlgoritmIdx = 2

        self.axAlgorithm.set_title('Algorithm',
                                   x=0,
                                   horizontalalignment='left')

        # create an axis to host to radio buttons. make its aspect ratio
        # equal so the radiobuttons stay round
        aspect = self.get_aspect(self.axAlgorithm)
        rect = [0, 0, 1.0 * aspect, 1.0]
        ip = InsetPosition(self.axAlgorithm, rect)
        self.axRadio = plt.axes(rect)
        self.axRadio.set_axes_locator(ip)
        self.axRadio.axis('off')

        self.radioAlgorithm = RadioButtons(
            self.axRadio,
            ('Total Energy', 'Parallel Energy', 'Naive (no filter)'),
            active=defaultAlgoritmIdx)
        self.radioAlgorithm.on_clicked(self.onClickAlgorithm)

    #------------------------------------------------------------------------------
    #
    def createUIGaussianFilterControls(self):
        axcolor = 'lightgoldenrodyellow'

        rect = [0.82, -0.158, 0.14, 0.07]
        ax_btnapply = plt.axes(rect)
        ip = InsetPosition(self.axGauss, rect)  #posx, posy, width, height
        ax_btnapply.set_axes_locator(ip)
        self.btnApply = Button(ax_btnapply, 'Apply')
        self.btnApply.on_clicked(self.onclickApply)

        rect = [0.82, -0.245, 0.14, 0.07]
        ax_btnreset = plt.axes(rect)
        ip = InsetPosition(self.axGauss, rect)  #posx, posy, width, height
        ax_btnreset.set_axes_locator(ip)
        self.btnReset = Button(ax_btnreset,
                               'Reset',
                               color='0.950',
                               hovercolor='0.975')
        self.btnReset.on_clicked(self.onclickReset)

        rect = [0.1, -0.155, 0.55, 0.04]
        self.ax_window_size = plt.axes(rect, facecolor=axcolor)
        ip = InsetPosition(self.axGauss, rect)  #posx, posy, width, height
        self.ax_window_size.set_axes_locator(ip)
        self.slider_window_size = Slider(self.ax_window_size,
                                         'Window Size',
                                         1, (self.gauss_params[0] + 1) * 2 + 1,
                                         valinit=self.gauss_params[0],
                                         valstep=2)
        self.slider_window_size.on_changed(self.updateGaussianFilter)

        rect = [0.1, -0.235, 0.55, 0.04]
        self.ax_sigma = plt.axes(rect, facecolor=axcolor)
        ip = InsetPosition(self.axGauss, rect)  #posx, posy, width, height
        self.ax_sigma.set_axes_locator(ip)
        self.slider_sigma = Slider(self.ax_sigma,
                                   'Sigma',
                                   1, (self.gauss_params[1] + 1) * 2,
                                   valinit=self.gauss_params[1],
                                   valstep=1)
        self.slider_sigma.on_changed(self.updateGaussianFilter)

        self.updateGaussianFilter()

    #------------------------------------------------------------------------------
    #
    def createUIMiscellaneous(self):
        self.axMisc.set_title('', x=0, horizontalalignment='left')

        # removing top and right borders
        self.axMisc.xaxis.set_visible(False)
        self.axMisc.yaxis.set_visible(False)

        # remove ticks
        self.axSaveOptions.set_xticks([])
        self.axSaveOptions.set_yticks([])

        # remove ticks
        self.axAlgorithm.set_xticks([])
        self.axAlgorithm.set_yticks([])

        bbox = self.axMisc.get_window_extent()
        self.axMisc.set_xlim(0, bbox.width)
        self.axMisc.set_ylim(0, bbox.height)

    #------------------------------------------------------------------------------
    #
    def createUILog(self):
        self.axLog.set_title('Console Log', x=0, horizontalalignment='left')

        # removing top and right borders
        self.axLog.xaxis.set_visible(False)
        self.axLog.yaxis.set_visible(False)

        self.resetLogLabels()

        # redirect console messages to gui's log
        self.originalStdOut = sys.stdout
        sys.stdout = CaptureOutput()

    # -----------------------------------------------------------------------------
    #
    def resetLogLabels(self):
        cnt = len(self.logTextLabels)
        for i in range(0, cnt):
            self.logTextLabels[i].remove()

        self.logTextLabels = []

        bbox = self.axLog.get_window_extent()
        self.axLog.set_xlim(0, bbox.width)
        self.axLog.set_ylim(0, bbox.height)

        aspect = self.get_aspect(self.axLog)
        rect = [0, 0, 1.0 * aspect, 1.0]
        ip = InsetPosition(self.axLog, rect)

        if (self.axLogLabels is None):
            self.axLogLabels = plt.axes(rect)
        else:
            self.axLogLabels.set_position(rect)

        self.axLogLabels.set_axes_locator(ip)
        self.axLogLabels.axis('off')

        aspectLog = 1.0 / self.get_aspect(self.axLog)
        strText = 'Tyg'
        tmp, self.logTextHeight = settings.getTextExtent(self.axLog, strText)
        self.logTextHeight = self.logTextHeight * aspectLog  # * self.fig.dpi

        # pre-create empty log label placeholders
        self.logTextLabels = []
        y = (self.logTextHeight / 4.0)
        cy = bbox.height
        idx = len(self.logText) - 1
        while (y < cy):
            str = self.logText[idx] if (idx >= 0) else ''
            idx = idx - 1

            lbl = self.axLogLabels.text(
                8.0,
                y,
                str,
                horizontalalignment='left',
                verticalalignment='bottom',
                color='dimgray',
                clip_on=True,
                transform=self.axLog.transData
            )  #, bbox={'facecolor':'lightgray', 'alpha':0.7, 'pad':0.0})

            self.logTextLabels.append(lbl)
            y += self.logTextHeight

    # -----------------------------------------------------------------------------
    #
    def createUIMiscellaneousControls(self):
        rect = [0.06, 0.30, 0.70, 0.40]
        ip = InsetPosition(self.axMisc, rect)  #posx, posy, width, height
        ax_btnbrowse = plt.axes(rect)
        ax_btnbrowse.set_axes_locator(ip)
        self.btnBrowse = Button(ax_btnbrowse, 'Input File')
        self.btnBrowse.on_clicked(self.onclickBrowse)

        self.axSaveOptions.set_title('Output Files',
                                     x=0,
                                     horizontalalignment='left')

        x = 0.06
        dx = 0.70  # 0.80
        dy = 0.10  # 0.17
        cy = 0.14  # 0.24
        y = 0.80
        rect = [x, y, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSaveJSON = Button(ax_btn, 'JSON')
        self.btnSaveJSON.on_clicked(self.onclickSaveJSON)

        rect = [x, y - 1 * cy, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSaveTXT = Button(ax_btn, 'Text')
        self.btnSaveTXT.on_clicked(self.onclickSaveTXT)

        rect = [x, y - 2 * cy, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSaveFilterView = Button(ax_btn, 'Filter Graph')
        self.btnSaveFilterView.on_clicked(self.onclickSaveFilterView)

        rect = [x, y - 3 * cy, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSaveSkeletonView = Button(ax_btn, '3D Joint Data')
        self.btnSaveSkeletonView.on_clicked(self.onclickSaveSkeletonView)

        rect = [x, y - 4 * cy, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSaveScoreView = Button(ax_btn, 'Score View')
        self.btnSaveScoreView.on_clicked(self.onclickSaveScoreView)

        rect = [x, y - 5 * cy, dx, dy]
        ip = InsetPosition(self.axSaveOptions,
                           rect)  #posx, posy, width, height
        ax_btn = plt.axes(rect)
        ax_btn.set_axes_locator(ip)
        self.btnSavePNG = Button(ax_btn, 'Full Score')
        self.btnSavePNG.on_clicked(self.onclickSaveImage)

    #------------------------------------------------------------------------------
    # canvas resize event
    #
    def onresize(self, event):
        # plt.tight_layout()

        # keep tha radio buttons round...
        if (self.axRadio is not None) and (self.axAlgorithm is not None):
            aspect = self.get_aspect(self.axAlgorithm)
            rect = [0, 0, 1.0 * aspect, 1.0]

            ip = InsetPosition(self.axAlgorithm, rect)
            self.axRadio.set_axes_locator(ip)
            self.axRadio.set_position(rect)

        self.resetLogLabels()

    # -----------------------------------------------------------------------------
    # canvas close event
    #
    def onclose(self, event):
        self.fig = None
        # if user closes this figure, let the main application know and to exit
        settings.application.close()

    # -----------------------------------------------------------------------------
    #
    def updateUIControls(self, algorithm):
        algorithm = algorithm.lower()
        fEnable = False if (algorithm == 'naive') else True
        alpha = 0.2 if (algorithm == 'naive') else 1.0

        self.btnApply.set_active(fEnable)
        self.btnReset.set_active(fEnable)

        self.btnApply.label.set_alpha(alpha)
        self.btnReset.label.set_alpha(alpha)

        fUpdateGaussianPlot = False
        if (self.gauss_params[0] != self.slider_window_size.val):
            self.ax_window_size.clear()
            self.slider_window_size.__init__(
                self.ax_window_size,
                'Window Size',
                valmin=1,
                valmax=(self.gauss_params[0] + 1) * 2 + 1,
                valinit=self.gauss_params[0],
                valstep=2)
            self.slider_window_size.on_changed(self.updateGaussianFilter)
            fUpdateGaussianPlot = True

        if (self.gauss_params[1] != self.slider_sigma.val):
            self.ax_sigma.clear()
            self.slider_sigma.__init__(self.ax_sigma,
                                       'Sigma',
                                       valmin=1,
                                       valmax=(self.gauss_params[1] + 1) * 2,
                                       valinit=self.gauss_params[1],
                                       valstep=1)
            self.slider_sigma.on_changed(self.updateGaussianFilter)
            fUpdateGaussianPlot = True

        if (fUpdateGaussianPlot):
            self.updateGaussianFilter()

        self.line_gaussian.set_alpha(alpha)

        self.ax_window_size.patch.set_alpha(alpha)
        if (self.slider_window_size.poly):
            self.slider_window_size.poly.set_alpha(alpha)
        self.slider_window_size.set_active(fEnable)
        for r in self.slider_window_size.ax.texts:
            r.set_alpha(alpha)

        self.ax_sigma.patch.set_alpha(alpha)
        if (self.slider_sigma.poly):
            self.slider_sigma.poly.set_alpha(alpha)
        self.slider_sigma.set_active(fEnable)
        for r in self.slider_sigma.ax.texts:
            r.set_alpha(alpha)

        self.fig.canvas.draw_idle()

    # -----------------------------------------------------------------------------
    #
    def updateInputName(self):
        self.fig.canvas.set_window_title(
            self.strTitle + ' - [' +
            settings.application.strBeautifiedInputFile + ']')

    # -----------------------------------------------------------------------------
    #
    def getAlgorithmSelection(self):
        if (self.radioAlgorithm.value_selected == 'Total Energy'):
            return 'Total'
        elif (self.radioAlgorithm.value_selected == 'Parallel Energy'):
            return 'Parallel'

        return 'Naive'

    # -----------------------------------------------------------------------------
    #
    def onClickAlgorithm(self, label):
        algorithm = self.getAlgorithmSelection()

        if (settings.application.labanotation is not None):
            self.gauss_params = settings.application.labanotation.getGaussianParameters(
                algorithm)

        self.updateUIControls(algorithm)
        settings.application.applyAlgoritm(algorithm)

    #------------------------------------------------------------------------------
    # updateGaussianFilter() has an unused parameter, though needs it because the
    # sliders use this function as their update callback...
    def updateGaussianFilter(self, val=0):
        # remove current gaussian lines
        if (self.line_gaussian is not None):
            self.line_gaussian.remove()
            del self.line_gaussian
            self.line_gaussian = None

        gauss_params = (int(self.slider_window_size.val),
                        int(self.slider_sigma.val))

        self._t = np.arange(-gauss_params[0] / 2.0 + 0.5,
                            gauss_params[0] / 2.0 + 0.5, 1.0)
        s = wf.gaussFilter(gauss_params[0], gauss_params[1])
        self.line_gaussian, = self.axGauss.plot(self._t,
                                                s,
                                                marker="o",
                                                linestyle='-',
                                                color='red',
                                                lw=1)

        # for i, txt in enumerate(s):
        #    self.axGauss.annotate("{:0.2f}".format(txt), (self._t[i], s[i]))

        wnd = int(gauss_params[0] / 2) + 1
        self.axGauss.set_xlim(-wnd, wnd)
        self.axGauss.set_ylim(0, 0.42)  # np.max(s))
        self.fig.canvas.draw_idle()

    #------------------------------------------------------------------------------
    #
    def onclickApply(self, event):
        algorithm = self.getAlgorithmSelection()

        self.gauss_params = (int(self.slider_window_size.val),
                             int(self.slider_sigma.val))

        if (settings.application.labanotation is not None):
            settings.application.labanotation.setGaussianParameters(
                algorithm, self.gauss_params)

        settings.application.applyAlgoritm(algorithm)

    #------------------------------------------------------------------------------
    #
    def onclickSaveJSON(self, event):
        settings.application.saveJSON()

    #------------------------------------------------------------------------------
    #
    def onclickSaveTXT(self, event):
        settings.application.saveTXT()

    #------------------------------------------------------------------------------
    #
    def onclickSaveImage(self, event):
        settings.application.saveImage()

    #------------------------------------------------------------------------------
    #
    def onclickSaveFilterView(self, event):
        settings.application.saveFilterView()

    #------------------------------------------------------------------------------
    #
    def onclickSaveSkeletonView(self, event):
        settings.application.saveSkeletonView()

    #------------------------------------------------------------------------------
    #
    def onclickSaveScoreView(self, event):
        settings.application.saveScoreView()

    #------------------------------------------------------------------------------
    #
    def onclickReset(self, event):
        self.slider_window_size.reset()
        self.slider_sigma.reset()

    #------------------------------------------------------------------------------
    #
    def onclickBrowse(self, event):
        file = self.selectInputFile()
        if (file is None):
            return

        settings.application.openAndProcessInputfile(file)

    #------------------------------------------------------------------------------
    #
    def selectInputFile(self):
        fTyp = [("Kinect Joint Data File", "*.csv")]

        splitInput = os.path.split(
            os.path.abspath(settings.application.inputFilePath))

        options = {}
        options['filetypes'] = fTyp
        options['initialdir'] = splitInput[0].replace('/', os.sep)

        if (settings.tkGuiCanvas is not None):
            options['parent'] = settings.tkGuiCanvas

        file = tkFileDialog.askopenfilename(**options)

        if not file:
            return None

        return file

    #------------------------------------------------------------------------------
    #
    def logMessage(self, str, ioRedirect=False):
        # also write message to console
        if (self.originalStdOut is not None):
            extra = "\r\n" if (ioRedirect is False) else ""
            self.originalStdOut.write(str + extra)

        self.logText.append(str)
        cnt = len(self.logTextLabels)
        if (cnt > 0):
            for i in range(cnt - 1, 0, -1):
                self.logTextLabels[i].set_text(
                    self.logTextLabels[i - 1].get_text())

            self.logTextLabels[0].set_text(str)

            self.fig.canvas.draw_idle()
Esempio n. 15
0
class DGILibPlot(object):
    """DGILibPlot
   
    The `DGILibPlot` class is responsible with plotting the electrical current
    (Amperes) data and gpio state data (values of `1`/`0`) obtained from an
    Atmel board.

    The X axis represents time in seconds, while the Y axis represents
    the electrical current in Amperes.

    There are two ways that the gpio pins state can be shown along with the
    electrical current. One is the `line` method and one is the `highlight`
    method. The `line` method shows a square waveform, a typical byproduct of
    the digital signal that gpio pins usually have. The `highlight` method
    highlights only particular parts of the plot with semi-transparent
    coloured areas, where the pins have a value of interest (set using the
    ``plot_pins_values`` argument of the class).

    Below are shown some examples of `DGILibPlot` and the two methods of
    drawing the gpio pins  (`line`/`highlight`).

    **Example plots using "line" method:**

    .. figure:: images/plot_line_1.png
       :scale: 60%

       Figure 1: Example of plot with the 'line' method chosen for the drawing of
       pins. All of the pins are being plotted, so you can always see their
       `True`/`False` values.

    .. figure:: images/plot_line_2.png
       :scale: 60%

       Figure 2: The same plot with the same data as figure 1, only zoomed in.

    .. figure:: images/plot_line_3.png
       :scale: 60%

       Figure 3: The same plot with the same data as figure 1 and 2, only even
       more zoomed in. Here we can clearly see that gpio pins 0, 2 and 3 have
       defaulted on a single value all along the program's execution on the
       board. We can however clearly see the toggling of pin 1,
       represented in orange.

    **Example plots using "highlight" method:**

    .. figure:: images/plot_highlight_1.png
       :scale: 60%

       Figure 4: Example of plot with the 'highlight' method chosen for the
       drawing of pins. The time the pins are holding the value of interest (in
       this case, `True` value) is small every time. This is why we can see the
       highlighted areas looking more like vertical lines when zoomed
       out. The only pin being toggled by the program on the board is
       pin 1, hence it's why we only see one color of highlighted areas.

    .. figure:: images/plot_highlight_2.png
       :scale: 60%

       Figure 5: The same plot with the same data as figure 1, only zoomed in.

    .. figure:: images/plot_highlight_3.png
       :scale: 60%

       Figure 6: The same plot with the same data as figure 1 and 2, only even
       more zoomed in. Now we can see one of the the  highlight
       area in its proper form.

    **Parameters**

    The parameters listed in the section below can be passed as arguments when
    initializing the `DGILibPlot` object, or as arguments to a `DGILibExtra`
    object. They can be included in a configuration dictionary
    (:py:class:`dict` object) and then unwrapped in the initialization function
    call of either the `DGILibPlot` object or the `DGILibExtra` object (by
    doing: ``dgilib_plot = DGILibPlot(**config_dict)`` or ``with
    DGILibExtra(**config_dict) as dgilib: ...``).

    Parameters
    ----------
    dgilib_extra : DGILibExtra
        A `DGILibExtra` object can be specified, from where the plot can obtain
        the electrical current and gpio state data. If a `DGILibExtra` object
        is not desired to be specified however, then it should be set to
        `None`.

        (the default is `None`, meaning that measurements data (in the form
        of a :class:`DGILibData` should be manually called as 
        function updates))

    fig : matplotlib.pyplot.figure, optional
        If it is wanted so that the data is to be plotted on an already
        existing `matplotlib` figure, then the object representing the already
        instantiated figure can be specified for this parameter. For example,
        the electrical current data and gpio state data can be plotted
        in a subplot of a figure window that holds other plots as well.

        (the default is `None`, meaning that a new figure object will be
        created internally)

    ax : matplotlib.pyplot.axes, optional
        If it is wanted so that the data is to be plotted on an already
        existing `matplotlib` axes, then the object representing the already
        instantiated axes can be specified for this parameter.

        (the default is `None`, meaning that a new axes object will be
        created internally)

    ln : matplotlib.pyplot.lines2d, optional
        If it is wanted so that the data is to be plotted on an already
        existing `matplotlib` `Lines2D` object, then the object representing
        the already instantiated `Lines2D` object can be specified for this
        parameter.

        (the default is `None`, meaning that a new `Lines2D` object will be
        created internally)

    window_title : str, optional
        If another window title than the default is desired to be used, it can
        be specified here.

        (the default is ``Plot of current (in amperes) and gpio pins``)

    plot_xmax : int, optional
        This *initializes* the figure view to a maximum of `plot_xmax` on
        the X axis, where the data to be plotted. Later, the user can change
        the figure view using the bottom sliders of the plot figure.

        (the default is an arbitrary `10`)

    plot_ymax : int, optional
        This *initializes* the figure view to a maximum of `plot_xmax` on
        the Y axis, where the data to be plotted. Later, the user can change
        the figure view using the bottom sliders of the plot figure.

        (the default is `0.005`, meaning 5 mA, so that something as
        energy consuming as a blinking LED can be shown by a `DGILibPlot`
        with default settings)

    plot_pins : list(bool, bool, bool, bool), optional
        Set the pins to be drawn in the plot, out of the 4 GPIO available pins
        that the Atmel board gives data about to be sent through the computer
        through the Atmel Embedded Debugger (EDBG) Data Gateway Interface
        (DGI).

        (the default is `[True, True, True, True]`, meaning all pins are
        drawn)

    plot_pins_method : str, optional
        Set the *method* of drawing the pin. The values can be either
        ``"line"`` or ``"highlight"``. Refer to the above figures to see
        the differences.

        (the default is `"highlight"`)

    plot_pins_colors : list(str, str, str, str), optional
        Set the colors of the semi-transparent highlight areas drawn when using
        the `highlight` method, or the lines when using the `lines` method of
        drawing pins. 
        
        (the default is `["red", "orange", "blue", "green"]`,
        meaning that pin 0 will have a `red` semi-transparent highlight area or
        line, pin 1 will have `orange` ones, and so on)

    automove_method : str, optional
        When the plot is receiving data live from the measurements taken in
        real-time from the board (as opposed to receiving all the data to be
        plotted at once, say, when reading the data from saved csv files), and
        `plot_xmax` is smaller than the expected size of the data in the end,
        then at some point the data will update past the figure view.
        `DGILibPlot` automatically moves the figure view to the last timestamp
        of the latest data received, and it can do so in two ways, depending on
        the value of ``automove_method``.

        The `page` method changes the figure view in increments of
        ``plot_ymax``, when the data updates past the figure view, as if the
        plot is turning one "page" at a time. The `latest_data` method makes
        the figure view always have the latest data in view, meaning that it
        moves in small increments so that it always keeps the latest data point
        `0.15` seconds away from the right edge of the figure view. The `0.15`
        seconds value is an arbitrary hard-coded value, chosen after some
        experiments.

        (the default is 'latest_data', meaning the plot's figure view will
        follow the latest data in smaller increments, keeping the latest data
        always on the right side of the plot)

    verbose : int
        Specifies verbosity:

        - 0: Silent

        - 1: Currently prints if data is missing, either as an object or as \
        values, when :func:`update_plot` is being called.

        (the default is `0`)

    Attributes
    ----------
    axvspans : list(4 x list(matplotlib.pyplot.axvspan))
        The way the semi-transparent coloured areas for the gpio pins are drawn
        on the plot is by using ``matplotlib.pyplot.axvspan`` objects. The
        `axvspan` objects are collected in a list for potential use for later.
        (e.g.: such as to delete them, using the :func:`clear_pins` method).

        (the default is 4 empty lists, meaning no highlighting of areas of
        interest has occured yet)

    annotations : list(4 x list(str))
        As we have seen in figures 4, 5, 6, for the `highlight` method of
        drawing pins, the counting or iteration of the highlighted areas are
        also showed on the plot. There are 4 pins, so therefore 4 lists of the
        counting/iteration stored as strings are saved by the `DGILibPlot` for
        later use by developers (e.g.: to replace from numbers to actual
        descriptions and then call the redrawing of pins).

        (the default is 4 empty lists, meaning no annotations for the
        highlighted areas of interest were placed yet)

    preprocessed_averages_data : list(4 x list(tuple(int, \
    tuple(float, float), int, float)))
        As the `highlight` method draws the pins on the plot with the help of
        the :class:`HoldTimes` class, that means the plot knows afterwards the
        time intervals in which the pins have values of interest. This can be
        valuable for a subsequent averaging function that wants to calculate
        faster the average current or energy consumption of the the board
        activity only where it was highlighted on the plot. As such,
        `DGILibPlot` prepares a list of 4 lists of tuples, each for every pin,
        the tuple containing the iteration index of the highlighted area of
        interest the pins kept the same value consecutively (called a `hold`
        time), another tuple containing the timestamps with respect to the
        electrical current data, which says the beginning and end times of that
        `hold` interval,  an index in the list of the electrical current data
        points where the respective hold time starts and, lastly, a `None`
        value, which then should be replaced with a :py:class:`float` value for
        the the average current or charge during that hold time.

        (the default is 4 empty lists, meaning no gathered preprocessed
        averages data yet)

    iterations : list(4 x int)
        As the plot is being updated live, the `iterations` list holds the
        number of highlighed areas that have been drawn already for for each
        pin. As the whole measurement data gets plotted, the iterations list
        practically holds the number of iterations the of all the areas
        of interest for each pin (which can be, for example, the number
        of `for` loops that took place in the program itself running on the
        board).

        (the default are 4 ``0`` values, meaning no areas of interest
        on the gpio data has been identified)

    last_xpos : float
        As the plot is being updated live, the figure view moves along with the
        latest data to be shown on the plot. If the user desires to stop this
        automatic movement and focus on some specific area of the plot, while
        the data is still being updated in real-time, the figure needs to
        detect that it should stop following the data to not disturb the user.
        It does so by comparing the current x-axis position value of the x axis
        shown, with the last x-axis position value saved, before doing any
        automatic movement of the figure view. If they are different, no
        automatic movement is being done from now on.
        
    xylim_mutex : Lock
        As there are many ways to move, zoom and otherwise manipulate the
        figure view to different sections of the plot, using the `matplotlib`'s
        implicit movement and zoom controls, or the freshly built `DGILibPlot`
        sliders appearing at the bottom (See figures), or the automatic
        following of the latest data feature, there is a multi-threading aspect
        involved and therefore a mutex should be involved, in the form
        of a `Lock` object, to prevent anomalies.

    hold_times_obj: HoldTimes
        Only instantiated when `highlight` method is being used for drawing
        pins information, the :class:`HoldTimes` class holds the algorithm that
        works on the live data to obtain the timestamps of areas of interest of
        the gpio data, in order to be highlighted on the plot. As the algorithm
        works on live updating data, the areas of interst can be cut off
        between updates of data. As such, the :class:`HoldTimes` keeps
        information for the algorithm to work with, in order to advert this.    
    """
    def __init__(self, dgilib_extra=None, *args, **kwargs):
        self.dgilib_extra = dgilib_extra

        # Maybe the user wants to put the power figure along with
        # other figures he wants
        self.fig = kwargs.get("fig")
        if self.fig is None:
            self.fig = plt.figure(figsize=(8, 6))

        # Set window title if supplied, if not set a default
        self.window_title = kwargs.get(
            "window_title", "Plot of current (in amperes) and" + "gpio pins")
        self.fig.canvas.set_window_title(self.window_title)

        self.ax = kwargs.get("ax")
        if self.ax is None:
            self.ax = self.fig.add_subplot(1, 1, 1)
            self.ax.set_xlabel('Time [s]')
            self.ax.set_ylabel('Current [A]')

        # We need the Line2D object as well, to update it
        if (len(self.ax.lines) == 0):
            self.ln, = self.ax.plot([], [], 'r-', label="Power")
        else:
            self.ln = self.ax.lines[0]
            self.ln.set_xdata([])
            self.ln.set_ydata([])

        # Initialize xmax, ymax of plot initially
        self.plot_xmax = kwargs.get("plot_xmax", None)
        if self.plot_xmax is None:
            self.plot_xauto = True
            self.plot_xmax = 10
        else:
            self.plot_xauto = False

        self.plot_ymax = kwargs.get("plot_ymax", None)
        if self.plot_ymax is None:
            self.plot_yauto = True
            self.plot_ymax = 0.005
        else:
            self.plot_yauto = False

        self.plot_pins = kwargs.get("plot_pins", [True, True, True, True])
        self.plot_pins_values = kwargs.get("plot_pins_values",
                                           [True, True, True, True])
        self.plot_pins_method = kwargs.get("plot_pins_method",
                                           "highlight")  # or "line"
        self.plot_pins_colors = kwargs.get("plot_pins_colors",
                                           ["red", "orange", "blue", "green"])
        self.automove_method = kwargs.get("automove_method",
                                          "latest_data")  # or "page"
        self.axvspans = [[], [], [], []]
        self.annotations = [[], [], [], []]
        self.preprocessed_averages_data = [[], [], [], []]
        #self.total_average = [0,0,0,0]
        self.iterations = [0, 0, 0, 0]
        self.last_xpos = 0.0
        self.xylim_mutex = Lock()

        #self.refresh_plot_pause_secs = kwargs.get("refresh_plot_pause_secs", 0.00000001)

        if self.plot_pins_method == "highlight":
            self.hold_times_obj = HoldTimes()

        if self.plot_pins_method == "line":
            self.ax_pins = self.ax.twinx()
            self.ax_pins.set_ylabel('Pin Value')
            self.ax_pins.set_ylim([-0.1, 1.1])
            self.ax_pins.set_yticks([0, 1])
            self.ax_pins.set_yticklabels(["Low", "High"])
            self.ln_pins = list(
                self.plot_pins)  # Instantiate as copy of plot_pins
            for pin, plot_pin in enumerate(self.plot_pins):
                if plot_pin:
                    self.ln_pins[pin] = self.ax_pins.plot(
                        [], [], label=f"gpio{pin}")[0]
            self.ax_pins.legend(handles=[
                ln_pin
                for ln_pin in self.ln_pins if not isinstance(ln_pin, bool)
            ] + [self.ln])  # Should actually check if it is a lines instance

        # Hardwiring these values to 0
        self.plot_xmin = 0
        self.plot_ymin = 0

        # We need these values from the user (or from the superior class),
        #   hence no default values
        # TODO: Are they really needed though?
        self.plot_xdiv = kwargs.get("plot_xdiv", min(5, self.plot_xmax))
        self.plot_xstep = kwargs.get("plot_xstep", 0.5)
        self.plot_xstep_default = self.plot_xstep
        # self.duration = kwargs.get("duration", max(self.plot_xmax, 5))

        # We'll have some sliders to zoom in and out of the plot, as well as a cursor to move around when zoomed in
        # Leave space for sliders at the bottom
        plt.subplots_adjust(bottom=0.3)
        # Show grid
        self.ax.grid()

        # Slider color
        self.axcolor = 'lightgoldenrodyellow'
        # Title format
        # Should use this https://matplotlib.org/gallery/recipes/placing_text_boxes.html
        self.title_avg = "Total avg curr: %1"
        self.title_vis = "Visible avg curr: %1"
        self.title_pin = "Avg curr in %1: %2"  # "calculate_average(data[INTERFACE_POWER])*1e3:.4} mA"
        self.title_time = "Time spent in %1: %2"
        self.ax.set_title("Waiting for data...")

        # Hold times for pins list, we're going to collect them
        self.hold_times_sum = 0.00
        self.hold_times_next_index = 0  # Will be incremented to 0 later
        self.hold_times_already_drawn = []

        self.verbose = kwargs.get("verbose", 0)

        self.initialize_sliders()

    def initialize_sliders(self):
        self.axpos = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor)
        self.axwidth = plt.axes([0.25, 0.15, 0.65, 0.03],
                                facecolor=self.axcolor)
        self.resetax = plt.axes([0.8, 0.025, 0.1, 0.04])

        self.spos = Slider(self.axpos,
                           'x',
                           0,
                           self.plot_xmax,
                           valinit=0,
                           valstep=self.plot_xstep)
        self.swidth = Slider(self.axwidth,
                             'xmax',
                             0,
                             self.plot_xmax,
                             valinit=self.plot_xdiv,
                             valstep=self.plot_xstep)
        self.resetbtn = Button(self.resetax,
                               'Reset',
                               color=self.axcolor,
                               hovercolor='0.975')

        #TODO: Change to pixel sizes
        self.xleftax = plt.axes([0.4, 0.025, 0.095,
                                 0.04])  # x_pos, y_pos, width, height
        self.xrightax = plt.axes([0.5, 0.025, 0.095, 0.04])
        self.xmaxleftax = plt.axes([0.6, 0.025, 0.095, 0.04])
        self.xmaxrightax = plt.axes([0.7, 0.025, 0.095, 0.04])
        self.xstepupax = plt.axes([0.3, 0.025, 0.095, 0.04])
        self.xstepdownax = plt.axes([0.2, 0.025, 0.095, 0.04])

        self.xleftbtn = Button(self.xleftax,
                               '<x',
                               color=self.axcolor,
                               hovercolor='0.975')
        self.xrightbtn = Button(self.xrightax,
                                'x>',
                                color=self.axcolor,
                                hovercolor='0.975')
        self.xmaxleftbtn = Button(self.xmaxleftax,
                                  'xmax-',
                                  color=self.axcolor,
                                  hovercolor='0.975')
        self.xmaxrightbtn = Button(self.xmaxrightax,
                                   'xmax+',
                                   color=self.axcolor,
                                   hovercolor='0.975')
        self.xstepupbtn = Button(self.xstepupax,
                                 'xstep+',
                                 color=self.axcolor,
                                 hovercolor='0.975')
        self.xstepdownbtn = Button(self.xstepdownax,
                                   'xstep-',
                                   color=self.axcolor,
                                   hovercolor='0.975')

        self.xstepax = plt.axes([0.1, 0.025, 0.095, 0.04])
        self.xsteptb = TextBox(self.xstepax,
                               'xstep',
                               initial=str(self.plot_xstep))

        def xstep_submit(text):
            self.plot_xstep = float(text)

        self.xsteptb.on_submit(xstep_submit)

        def increase_x(event):
            #if ((self.spos.val + self.plot_xstep) <= (self.plot_xmax - self.swidth.val)):
            self.spos.set_val(self.spos.val + self.plot_xstep)
            update_pos(self.spos.val)

        def decrease_x(event):
            #if ((self.spos.val - self.plot_xstep) >= (self.plot_xmin)):
            self.spos.set_val(self.spos.val - self.plot_xstep)
            update_pos(self.spos.val)

        def increase_xmax(event):
            #if ((self.swidth.val + self.plot_xstep) <= self.plot_xmax):
            self.swidth.set_val(self.swidth.val + self.plot_xstep)
            update_width(self.swidth.val)

        def decrease_xmax(event):
            if ((self.swidth.val - self.plot_xstep) >=
                (self.plot_xmin + 0.000001)):
                self.swidth.set_val(self.swidth.val - self.plot_xstep)
                update_width(self.swidth.val)

        def increase_xstep(event):
            val = float(self.xsteptb.text)
            if ((val + 0.05) < (self.swidth.val - 0.000001)):
                self.xsteptb.set_val("{0:.2f}".format(val + 0.05))
                xstep_submit(self.xsteptb.text)

        def decrease_xstep(event):
            val = float(self.xsteptb.text)
            if ((val - 0.05) >= (0.05)):
                self.xsteptb.set_val("{0:.2f}".format(val - 0.05))
                xstep_submit(self.xsteptb.text)

        self.xleftbtn.on_clicked(decrease_x)
        self.xrightbtn.on_clicked(increase_x)
        self.xmaxleftbtn.on_clicked(decrease_xmax)
        self.xmaxrightbtn.on_clicked(increase_xmax)
        self.xstepupbtn.on_clicked(increase_xstep)
        self.xstepdownbtn.on_clicked(decrease_xstep)

        # I'm not sure how to detach these without them forgetting their parents (sliders)
        def update_pos(val):
            if self.xylim_mutex.acquire(False):
                pos = self.spos.val
                width = self.swidth.val

                #self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_pos function")
                self.ax.axis(
                    [pos, pos + width, self.plot_ymin, self.plot_ymax])

                self.xylim_mutex.release()

        def update_width(val):
            if self.xylim_mutex.acquire(False):
                pos = self.spos.val
                width = self.swidth.val

                self.axpos.clear()
                self.spos.__init__(self.axpos,
                                   'x',
                                   0,
                                   width,
                                   valinit=pos,
                                   valstep=self.plot_xstep)
                self.spos.on_changed(update_pos)
                self.spos.set_val(pos)

                #self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_width function")
                self.ax.axis(
                    [pos, pos + width, self.plot_ymin, self.plot_ymax])

                self.xylim_mutex.release()

        self.spos.on_changed(update_pos)
        self.swidth.on_changed(update_width)

        # TODO: This
        def see_all(event):
            if self.xylim_mutex.acquire(False):

                #self.set_axis(0, self.last_timestamp, self.plot_ymin, self.plot_ymax, "see_all function")
                self.ax.axis(
                    [0, self.last_timestamp, self.plot_ymin, self.plot_ymax])
                self.last_xpos = -1

                self.xylim_mutex.release()

        def reset(event):
            if self.xylim_mutex.acquire(False):
                self.swidth.set_val(self.plot_xmax)

                self.axpos.clear()
                self.spos.__init__(self.axpos,
                                   'x',
                                   0,
                                   self.plot_xmax,
                                   valinit=0,
                                   valstep=self.plot_xstep_default)
                self.spos.on_changed(update_pos)

                self.xsteptb.set_val(str(self.plot_xstep_default))

                #self.set_axis(self.plot_xmin, self.plot_xmax, self.plot_ymin, self.plot_ymax, "reset function")
                self.ax.axis([
                    self.plot_xmin, self.plot_xmax, self.plot_ymin,
                    self.plot_ymax
                ])
                self.last_xpos = -1

                self.xylim_mutex.release()

        self.resetbtn.on_clicked(reset)

        self.spos.set_val(0)
        self.swidth.set_val(self.plot_xmax)

        # Auto-change the sliders/buttons when using plot tools
        def on_xlims_change(axes):
            if self.xylim_mutex.acquire(False):  # Non-blocking
                xlim_left = self.ax.get_xlim()[0]
                xlim_right = self.ax.get_xlim()[1]

                pos = xlim_left
                width = xlim_right - xlim_left

                self.spos.set_val(pos)
                self.swidth.set_val(width)
                self.last_xpos = -1

                self.xylim_mutex.release()

        def on_ylims_change(axes):
            print(self.ax.get_ylim())

        self.ax.callbacks.connect('xlim_changed', on_xlims_change)
        #self.ax.callbacks.connect('ylim_changed', on_ylims_change)

    def update_plot(self, data):
        verbose = self.verbose

        if data is None:
            data = self.dgilib_extra.data

        if (not data):
            if verbose:
                print(
                    "dgilib_plot.update_plot: Expected 'data' containing interfaces. Got 'data' with no interfaces. Returning from call with no work done."
                )
            return
        if (not data.power):
            if verbose:
                print(
                    "dgilib_plot.update_plot: Expected 'data' containing power data. Got 'data' with interfaces but no power timestamp & value pairs. Returning from call with no work done."
                )
            return
        if (not data.gpio):
            if verbose:
                print(
                    "dgilib_plot.update_plot: Expected 'data' containing gpio data. Got 'data' with interfaces but no gpio timestamp & value pairs."
                )
            return

        if not plt.fignum_exists(self.fig.number):
            plt.show()
        else:
            plt.draw()
        self.refresh_plot()

        #TODO: ln might have an update_callback and then it can listen to the data being updated instead of updating data here
        self.ln.set_xdata(data.power.timestamps)
        self.ln.set_ydata(data.power.values)

        automove = True
        current_xpos = self.ax.get_xlim()[0]

        if self.last_xpos != current_xpos:
            automove = False

        if automove and self.xylim_mutex.acquire(False):
            last_timestamp = data.power.timestamps[-1]

            pos = self.spos.val
            width = self.swidth.val

            if (last_timestamp > (pos + width)):
                if self.automove_method == "page":
                    self.spos.set_val(pos + width)
                elif self.automove_method == "latest_data":
                    arbitrary_amount = 0.15
                    if last_timestamp > width:
                        self.spos.set_val(last_timestamp + arbitrary_amount -
                                          width)

            pos = self.spos.val
            width = self.swidth.val

            self.ax.axis([pos, pos + width, self.plot_ymin, self.plot_ymax])
            self.last_xpos = pos

            self.xylim_mutex.release()

        self.refresh_plot()

        self.draw_pins(data)

    def clear_pins(self):
        """
        Clears the highlighted areas on the plot that represent the state of the gpio pins
        (as seen in figures 4, 5, 6). Using this method only makes sense if the `highlight`
        method of drawing pins was used.
        """
        if self.axvspans is None:
            return

        for axvsp in self.axvspans:
            axvsp.remove()

    def draw_pins(self, data):
        """draw_pins [summary]
        
        Raises
        ------
        ValueError
            Raised when `plot_pins_method` member of class has another 
            string value than the ones available (`highlight`, `line`)

        Parameters
        ----------
        data : DGILibData
            :class:`DGILibData` object that contains the GPIO data to be drawn
            on the plot.
        """
        # Here we set defaults (with 'or' keyword ...)
        ax = self.ax
        plot_pins = self.plot_pins
        plot_pins_values = self.plot_pins_values
        #plot_pins_method = self.plot_pins_method or "highlight"
        plot_pins_colors = self.plot_pins_colors

        # Here we do checks and stop drawing pins if something is unset
        if ax is None: return
        if plot_pins is None: return

        verbose = self.verbose

        no_of_pins = len(self.plot_pins)

        if self.plot_pins_method == "highlight":

            for pin_idx in range(no_of_pins):  # For every pin number (0,1,2,3)

                if plot_pins[pin_idx] == True:  # If we want them plotted

                    hold_times = self.hold_times_obj.identify_hold_times(
                        pin_idx, plot_pins_values[pin_idx], data.gpio)

                    if hold_times is not None:
                        for ht in hold_times:
                            axvsp = ax.axvspan(ht[0],
                                               ht[1],
                                               color=plot_pins_colors[pin_idx],
                                               alpha=0.25)
                            self.axvspans[pin_idx].append(axvsp)

                            x_halfway = (ht[1] - ht[0]) / 4 + ht[0]
                            y_halfway = (self.plot_ymax -
                                         self.plot_ymin) / 2 + self.plot_ymin
                            annon = ax.annotate(str(self.iterations[pin_idx] +
                                                    1),
                                                xy=(x_halfway, y_halfway))
                            self.annotations[pin_idx].append(annon)

                            self.iterations[pin_idx] += 1

                            # TODO:  The start and stop indexes of the data points that are area of interest
                            # might be more useful for an averaging function, but currently the plot uses
                            # the coordinates of the X axis(the start/stop timestamps) in order to highlight
                            # the areas of interest.
                            self.preprocessed_averages_data[pin_idx].append(
                                (self.iterations[pin_idx], ht, 0, None))

            # This should be in update_plot()
            self.ax.set_title(
                f"Logging. Collected {len(data.power)} power samples and {len(data.gpio)} gpio samples."
            )

        elif self.plot_pins_method == "line":
            extend_gpio = data.gpio.timestamps[-1] < data.power.timestamps[-1]
            for pin, plot_pin in enumerate(self.plot_pins):
                if plot_pin:
                    self.ln_pins[pin].set_xdata(data.gpio.timestamps +
                                                extend_gpio *
                                                [data.power.timestamps[-1]])
                    self.ln_pins[pin].set_ydata(
                        data.gpio.get_select_in_value(pin) +
                        extend_gpio * [data.gpio.values[-1][pin]])
            self.ax.set_title(
                f"Logging. Collected {len(data.power)} power samples and {len(data.gpio)} gpio samples."
            )
            self.fig.show()
        else:
            raise ValueError(
                f"Unrecognized plot_pins_method: {self.plot_pins_method}")

    def plot_still_exists(self):
        """plot_still_exists 
        
        Can be used in a boolean (e.g.: `if`, `while`) clause to check if the
        plot is still shown inside a window (e.g.: to unpause program
        functionality if the plot is closed).

        Also used in the :func:`keep_plot_alive` member function of the
        class.

        Returns
        -------
        bool
            Returns `True` if the plot still exists and `False` otherwise.
        """
        return plt.fignum_exists(self.fig.number)

    def refresh_plot(self):
        """refresh_plot

        Makes a few `matplotlib` specific calls to refresh and redraw the plot
        (especially when new data is to be drawn). 
        
        Is used in :func:`update_plot` member function of the class.
        """
        self.ax.relim()  # recompute the data limits
        self.ax.autoscale_view()  # automatic axis scaling
        self.fig.canvas.flush_events()

    def keep_plot_alive(self):
        while self.plot_still_exists():
            self.refresh_plot()
        """keep_plot_alive
        
        Pauses program functionality until the plot is closed. Does so
        by refreshing the plot using :func:`refresh_plot`.
        """

    def pause(self, time=0.000001):
        """pause

        Calls `matplotlib's` `pause` function for an amount of seconds.

        Parameters
        ----------
        time : float
            The number of seconds the plot should have time to refresh itself
            and pause program functionality.
        """
        plt.pause(time)
Esempio n. 16
0
 def __init__(self, *args, **kwargs):
     Slider.__init__(self, *args, **kwargs)