Beispiel #1
0
def multipanelplot(self,
                   trials="all",
                   channels="all",
                   toilim=None,
                   avg_channels=False,
                   avg_trials=True,
                   title=None,
                   grid=None,
                   fig=None,
                   **kwargs):
    """
    Plot contents of :class:`~syncopy.AnalogData` objects using multi-panel figure(s)
    
    Please refer to :func:`syncopy.multipanelplot` for detailed usage information. 
    
    Examples
    --------
    Use :func:`~syncopy.tests.misc.generate_artificial_data` to create two synthetic 
    :class:`~syncopy.AnalogData` objects. 
    
    >>> from syncopy.tests.misc import generate_artificial_data
    >>> adata = generate_artificial_data(nTrials=10, nChannels=32) 
    >>> bdata = generate_artificial_data(nTrials=5, nChannels=16) 
    
    Show overview of first 5 channels, averaged across trials 2, 4, and 6:
    
    >>> fig = spy.multipanelplot(adata, channels=range(5), trials=[2, 4, 6])
   
    Overlay last 5 channels, averaged across trials 1, 3, 5: 

    >>> fig = spy.multipanelplot(adata, channels=range(27, 32), trials=[1, 3, 5], fig=fig)
    
    Do not average trials:
    
    >>> fig = spy.multipanelplot(adata, channels=range(27, 32), trials=[1, 3, 5], avg_trials=False)
    
    Plot `adata` and `bdata` simultaneously in two separate figures:
    
    >>> fig1, fig2 = spy.multipanelplot(adata, bdata, channels=range(5), overlay=False)
    
    Overlay `adata` and `bdata`; use channel and trial selections that are valid 
    for both datasets:
    
    >>> fig3 = spy.multipanelplot(adata, bdata, channels=range(5), trials=[1, 2, 3], avg_trials=False)
    
    See also
    --------
    syncopy.multipanelplot : visualize Syncopy data objects using multi-panel plots
    """

    # Collect input arguments in dict `inputArgs` and process them
    inputArgs = locals()
    inputArgs.pop("self")
    dimArrs, dimCounts, idx, timeIdx, chanIdx = _prep_analog_plots(
        self, "singlepanelplot", **inputArgs)
    (nTrials, nChan) = dimCounts
    (trList, chArr) = dimArrs

    # Get trial/channel count ("raw" plotting constitutes a special case)
    if trials is None:
        nTrials = 0
        if avg_trials:
            msg = "`trials` is `None` but `avg_trials` is `True`. " +\
                "Cannot perform trial averaging without trial specification - " +\
                "setting ``avg_trials = False``. "
            SPYWarning(msg)
            avg_trials = False
        if avg_channels:
            msg = "Averaging across channels w/o trial specifications results in " +\
                "single-panel plot. Please use `singlepanelplot` instead"
            SPYWarning(msg)
            return

    # If we're overlaying, ensure settings match up
    if hasattr(fig, "singlepanelplot"):
        lgl = "overlay-figure generated by `multipanelplot`"
        act = "figure generated by `singlepanelplot`"
        raise SPYValueError(legal=lgl,
                            varname="fig/singlepanelplot",
                            actual=act)
    if hasattr(fig, "nTrialPanels"):
        if nTrials != fig.nTrialPanels:
            lgl = "number of trials to plot matching existing panels in figure"
            act = "{} panels but {} trials for plotting".format(
                fig.nTrialPanels, nTrials)
            raise SPYValueError(legal=lgl,
                                varname="trials/figure panels",
                                actual=act)
        if avg_trials:
            lgl = "overlay of multi-trial plot"
            act = "trial averaging was requested for multi-trial plot overlay"
            raise SPYValueError(legal=lgl,
                                varname="trials/avg_trials",
                                actual=act)
        if trials is None:
            lgl = "`trials` to be not `None` to append to multi-trial plot"
            act = "multi-trial plot overlay was requested but `trials` is `None`"
            raise SPYValueError(legal=lgl,
                                varname="trials/overlay",
                                actual=act)
        if not avg_channels and not hasattr(fig, "chanOffsets"):
            lgl = "single-channel or channel-averages for appending to multi-trial plot"
            act = "multi-trial multi-channel plot overlay was requested"
            raise SPYValueError(legal=lgl,
                                varname="avg_channels/overlay",
                                actual=act)
    if hasattr(fig, "nChanPanels"):
        if nChan != fig.nChanPanels:
            lgl = "number of channels to plot matching existing panels in figure"
            act = "{} panels but {} channels for plotting".format(
                fig.nChanPanels, nChan)
            raise SPYValueError(legal=lgl,
                                varname="channels/figure panels",
                                actual=act)
        if avg_channels:
            lgl = "overlay of multi-channel plot"
            act = "channel averaging was requested for multi-channel plot overlay"
            raise SPYValueError(legal=lgl,
                                varname="channels/avg_channels",
                                actual=act)
        if not avg_trials:
            lgl = "overlay of multi-channel plot"
            act = "mulit-trial plot was requested for multi-channel plot overlay"
            raise SPYValueError(legal=lgl,
                                varname="channels/avg_trials",
                                actual=act)
    if hasattr(fig, "chanOffsets"):
        if avg_channels:
            lgl = "multi-channel plot"
            act = "channel averaging was requested for multi-channel plot overlay"
            raise SPYValueError(legal=lgl,
                                varname="channels/avg_channels",
                                actual=act)
        if nChan != len(fig.chanOffsets):
            lgl = "channel-count matching existing multi-channel panels in figure"
            act = "{} channels per panel but {} channels for plotting".format(
                len(fig.chanOffsets), nChan)
            raise SPYValueError(legal=lgl,
                                varname="channels/channels per panel",
                                actual=act)

    # Generic title for overlay figures
    overlayTitle = "Overlay of {} datasets"

    # Either construct subplot panel layout/vet provided layout or fetch existing
    if fig is None:

        # Determine no. of required panels
        if avg_trials and not avg_channels:
            npanels = nChan
        elif not avg_trials and avg_channels:
            npanels = nTrials
        elif not avg_trials and not avg_channels:
            npanels = int(nTrials == 0) * nChan + nTrials
        else:
            msg = "Averaging across both trials and channels results in " +\
                "single-panel plot. Please use `singlepanelplot` instead"
            SPYWarning(msg)
            return

        # Although, `_setup_figure` can call `_layout_subplot_panels` for us, we
        # need `nrow` and `ncol` below, so do it here
        if nTrials > 0:
            xLabel = "Time [s]"
        else:
            xLabel = "Samples"
        nrow = kwargs.get("nrow", None)
        ncol = kwargs.get("ncol", None)
        nrow, ncol = _layout_subplot_panels(npanels, nrow=nrow, ncol=ncol)
        fig, ax_arr = _setup_figure(npanels,
                                    nrow=nrow,
                                    ncol=ncol,
                                    xLabel=xLabel,
                                    grid=grid)
        fig.analogPlot = True

    # Get existing layout
    else:
        ax_arr = fig.get_axes()
        nrow, ncol = ax_arr[0].numRows, ax_arr[0].numCols

    # Panels correspond to channels
    if avg_trials and not avg_channels:

        # Ensure provided timing selection can actually be averaged (leverage
        # the fact that `toilim` selections exclusively generate slices)
        tLengths = _prep_toilim_avg(self)

        # Compute trial-averaged time-courses: 2D array with slice/list
        # selection does not require fancy indexing - no need to check this here
        pltArr = np.zeros((tLengths[0], nChan), dtype=self.data.dtype)
        for k, trlno in enumerate(trList):
            idx[timeIdx] = self._selection.time[k]
            pltArr += np.swapaxes(
                self._get_trial(trlno)[tuple(idx)], timeIdx, 0)
        pltArr /= nTrials

        # Cycle through channels and plot trial-averaged time-courses (time-
        # axis must be identical for all channels, set up `idx` just once)
        idx[timeIdx] = self._selection.time[0]
        time = self.time[trList[k]][self._selection.time[0]]
        for k, chan in enumerate(chArr):
            ax_arr[k].plot(time,
                           pltArr[:, k],
                           label=os.path.basename(self.filename))

        # If we're overlaying datasets, adjust panel- and sup-titles: include
        # legend in top-right axis (note: `ax_arr` is row-major flattened)
        if fig.objCount == 0:
            for k, chan in enumerate(chArr):
                ax_arr[k].set_title(chan, size=pltConfig["multiTitleSize"])
            fig.nChanPanels = nChan
            if title is None:
                if nTrials > 1:
                    title = "Average of {} trials".format(nTrials)
                else:
                    title = "Trial #{}".format(trList[0])
            fig.suptitle(title, size=pltConfig["singleTitleSize"])
        else:
            for k, chan in enumerate(chArr):
                ax_arr[k].set_title("{0}/{1}".format(ax_arr[k].get_title(),
                                                     chan))
            ax = ax_arr[ncol - 1]
            handles, labels = ax.get_legend_handles_labels()
            ax.legend(handles, labels)
            if title is None:
                title = overlayTitle.format(len(handles))
            fig.suptitle(title, size=pltConfig["singleTitleSize"])

    # Panels correspond to trials
    elif not avg_trials and avg_channels:

        # Cycle through panels to plot by-trial channel-averages
        for k, trlno in enumerate(trList):
            idx[timeIdx] = self._selection.time[k]
            time = self.time[trList[k]][self._selection.time[k]]
            ax_arr[k].plot(time,
                           self._get_trial(trlno)[tuple(idx)].mean(
                               axis=chanIdx).squeeze(),
                           label=os.path.basename(self.filename))

        # If we're overlaying datasets, adjust panel- and sup-titles: include
        # legend in top-right axis (note: `ax_arr` is row-major flattened)
        if fig.objCount == 0:
            for k, trlno in enumerate(trList):
                ax_arr[k].set_title("Trial #{}".format(trlno),
                                    size=pltConfig["multiTitleSize"])
            fig.nTrialPanels = nTrials
            if title is None:
                if nChan > 1:
                    title = "Average of {} channels".format(nChan)
                else:
                    title = chArr[0]
            fig.suptitle(title, size=pltConfig["singleTitleSize"])
        else:
            for k, trlno in enumerate(trList):
                ax_arr[k].set_title("{0}/#{1}".format(ax_arr[k].get_title(),
                                                      trlno))
            ax = ax_arr[ncol - 1]
            handles, labels = ax.get_legend_handles_labels()
            ax.legend(handles, labels)
            if title is None:
                title = overlayTitle.format(len(handles))
            fig.suptitle(title, size=pltConfig["singleTitleSize"])

    # Panels correspond to channels (if `trials` is `None`) otherwise trials
    elif not avg_trials and not avg_channels:

        # Plot each channel in separate panel
        if nTrials == 0:
            chanSec = np.arange(self.channel.size)[self._selection.channel]
            for k, chan in enumerate(chanSec):
                idx[chanIdx] = chan
                ax_arr[k].plot(self.data[tuple(idx)].squeeze(),
                               label=os.path.basename(self.filename))

            # If we're overlaying datasets, adjust panel- and sup-titles: include
            # legend in top-right axis (note: `ax_arr` is row-major flattened)
            if fig.objCount == 0:
                for k, chan in enumerate(chArr):
                    ax_arr[k].set_title(chan, size=pltConfig["multiTitleSize"])
                fig.nChanPanels = nChan
                if title is None:
                    title = "Entire Data Timecourse"
                fig.suptitle(title, size=pltConfig["singleTitleSize"])
            else:
                for k, chan in enumerate(chArr):
                    ax_arr[k].set_title("{0}/{1}".format(
                        ax_arr[k].get_title(), chan))
                ax = ax_arr[ncol - 1]
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles, labels)
                if title is None:
                    title = overlayTitle.format(len(handles))
                fig.suptitle(title, size=pltConfig["singleTitleSize"])

        # Each trial gets its own panel w/multiple channels per panel
        else:

            # If required, compute max amplitude across provided trials + channels
            if not hasattr(fig, "chanOffsets"):
                maxAmps = np.zeros((nTrials, ), dtype=self.data.dtype)
                tickOffsets = maxAmps.copy()
                for k, trlno in enumerate(trList):
                    idx[timeIdx] = self._selection.time[k]
                    pltArr = np.abs(self._get_trial(trlno)[tuple(idx)])
                    maxAmps[k] = pltArr.max()
                    tickOffsets[k] = pltArr.mean()
                fig.chanOffsets = np.cumsum([0] + [maxAmps.max()] *
                                            (nChan - 1))
                fig.tickOffsets = fig.chanOffsets + tickOffsets.mean()

            # Cycle through panels to plot by-trial multi-channel time-courses
            for k, trlno in enumerate(trList):
                idx[timeIdx] = self._selection.time[k]
                time = self.time[trList[k]][self._selection.time[k]]
                pltArr = np.swapaxes(
                    self._get_trial(trlno)[tuple(idx)], timeIdx, 0)
                ax_arr[k].plot(
                    time, (pltArr + fig.chanOffsets.reshape(1, nChan)).reshape(
                        time.size, nChan),
                    color=plt.rcParams["axes.prop_cycle"].by_key()["color"][
                        fig.objCount],
                    label=os.path.basename(self.filename))

            # If we're overlaying datasets, adjust panel- and sup-titles: include
            # legend in top-right axis (note: `ax_arr` is row-major flattened)
            # Note: y-axis is shared across panels, so `yticks` need only be set once
            if fig.objCount == 0:
                for k, trlno in enumerate(trList):
                    ax_arr[k].set_title("Trial #{}".format(trlno),
                                        size=pltConfig["multiTitleSize"])
                ax_arr[0].set_yticks(fig.tickOffsets)
                ax_arr[0].set_yticklabels(chArr)
                fig.nTrialPanels = nTrials
                if title is None:
                    if nChan > 1:
                        title = "{} channels".format(nChan)
                    else:
                        title = chArr[0]
                fig.suptitle(title, size=pltConfig["singleTitleSize"])
            else:
                for k, trlno in enumerate(trList):
                    ax_arr[k].set_title("{0}/#{1}".format(
                        ax_arr[k].get_title(), trlno))
                ax_arr[0].set_yticklabels([" "] * chArr.size)
                ax = ax_arr[ncol - 1]
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles[::(nChan + 1)], labels[::(nChan + 1)])
                if title is None:
                    title = overlayTitle.format(len(handles))
                fig.suptitle(title, size=pltConfig["singleTitleSize"])

    # Increment overlay-counter, draw figure and wipe data-selection slot
    fig.objCount += 1
    plt.draw()
    self._selection = None
    return fig
Beispiel #2
0
def singlepanelplot(self,
                    trials="all",
                    channels="all",
                    tapers="all",
                    toilim=None,
                    foilim=None,
                    avg_channels=True,
                    avg_tapers=True,
                    interp="spline36",
                    cmap="plasma",
                    vmin=None,
                    vmax=None,
                    title=None,
                    grid=None,
                    fig=None,
                    **kwargs):
    """
    Plot contents of :class:`~syncopy.SpectralData` objects using single-panel figure(s)
    
    Please refer to :func:`syncopy.singlepanelplot` for detailed usage information. 
    
    Examples
    --------
    Show frequency range 30-80 Hz of channel `'ecog_mua2'` averaged across 
    trials 2, 4, and 6:
    
    >>> fig = spy.singlepanelplot(freqData, trials=[2, 4, 6], channels=["ecog_mua2"],
                                  foilim=[30, 80])
                                  
    Overlay channel `'ecog_mua3'` with same settings:
    
    >>> fig2 = spy.singlepanelplot(freqData, trials=[2, 4, 6], channels=['ecog_mua3'],
                                   foilim=[30, 80], fig=fig)
                                   
    Plot time-frequency contents of channel `'ecog_mua1'` present in both objects 
    `tfData1` and `tfData2` using the 'viridis' colormap, a plot grid, manually 
    defined lower and upper color value limits and no interpolation
    
    >>> fig1, fig2 = spy.singlepanelplot(tfData1, tfData2, channels=['ecog_mua1'],
                                         cmap="viridis", vmin=0.25, vmax=0.95, 
                                         interp=None, grid=True, overlay=False)
    
    Note that overlay plotting is **not** supported for time-frequency objects. 
 
    See also
    --------
    syncopy.singlepanelplot : visualize Syncopy data objects using single-panel plots
    """

    # Collect input arguments in dict `inputArgs` and process them
    inputArgs = locals()
    inputArgs.pop("self")
    (dimArrs, dimCounts, isTimeFrequency, complexConversion, pltDtype,
     dataLbl) = _prep_spectral_plots(self, "singlepanelplot", **inputArgs)
    (nTrials, nChan, nFreq, nTap) = dimCounts
    (trList, chArr, freqArr, tpArr) = dimArrs

    # If we're overlaying, ensure data and plot type match up
    if hasattr(fig, "objCount"):
        if isTimeFrequency:
            msg = "Overlay plotting not supported for time-frequency data"
            raise SPYError(msg)
        if not hasattr(fig, "spectralPlot"):
            lgl = "figure visualizing data from a Syncopy `SpectralData` object"
            act = "visualization of other Syncopy data"
            raise SPYValueError(legal=lgl, varname="fig", actual=act)
        if hasattr(fig, "multipanelplot"):
            lgl = "single-panel figure generated by `singleplot`"
            act = "multi-panel figure generated by `multipanelplot`"
            raise SPYValueError(legal=lgl, varname="fig", actual=act)

    # No time-frequency shenanigans: this is a simple power-spectrum (line-plot)
    if not isTimeFrequency:

        # Generic titles for figures
        overlayTitle = "Overlay of {} datasets"

        # Either create new figure or fetch existing
        if fig is None:
            fig, ax = _setup_figure(1,
                                    xLabel="Frequency [Hz]",
                                    yLabel=dataLbl,
                                    grid=grid)
            fig.spectralPlot = True
        else:
            ax, = fig.get_axes()

        # Average across channels, tapers or both using local helper func
        nTime = 1
        if not avg_channels and not avg_tapers and nTap > 1:
            msg = "Either channels or trials need to be averaged for single-panel plot"
            SPYWarning(msg)
            return
        if avg_channels and not avg_tapers:
            panelTitle = "{} tapers averaged across {} channels and {} trials".format(
                nTap, nChan, nTrials)
            pltArr = _compute_pltArr(self,
                                     nFreq,
                                     nTap,
                                     nTime,
                                     complexConversion,
                                     pltDtype,
                                     avg1="channel")
        if avg_tapers and not avg_channels:
            panelTitle = "{} channels averaged across {} tapers and {} trials".format(
                nChan, nTap, nTrials)
            pltArr = _compute_pltArr(self,
                                     nFreq,
                                     nChan,
                                     nTime,
                                     complexConversion,
                                     pltDtype,
                                     avg1="taper")
        if avg_tapers and avg_channels:
            panelTitle = "Average of {} channels, {} tapers and {} trials".format(
                nChan, nTap, nTrials)
            pltArr = _compute_pltArr(self,
                                     nFreq,
                                     1,
                                     nTime,
                                     complexConversion,
                                     pltDtype,
                                     avg1="taper",
                                     avg2="channel")

        # Perform the actual plotting
        ax.plot(freqArr,
                np.log10(pltArr),
                label=os.path.basename(self.filename))
        ax.set_xlim([freqArr[0], freqArr[-1]])

        # Set plot title depending on dataset overlay
        if fig.objCount == 0:
            if title is None:
                title = panelTitle
            ax.set_title(title, size=pltConfig["singleTitleSize"])
        else:
            handles, labels = ax.get_legend_handles_labels()
            ax.legend(handles, labels)
            if title is None:
                title = overlayTitle.format(len(handles))
            ax.set_title(title, size=pltConfig["singleTitleSize"])

    else:

        # For a single-panel TF visualization, we need to average across both tapers + channels
        if not avg_channels and (not avg_tapers and nTap > 1):
            msg = "Single-panel time-frequency visualization requires averaging " +\
                "across both tapers and channels"
            SPYWarning(msg)
            return

        # Compute (and verify) length of selected time intervals and assemble array for plotting
        panelTitle = "Average of {} channels, {} tapers and {} trials".format(
            nChan, nTap, nTrials)
        tLengths = _prep_toilim_avg(self)
        nTime = tLengths[0]
        pltArr = _compute_pltArr(self,
                                 nFreq,
                                 1,
                                 nTime,
                                 complexConversion,
                                 pltDtype,
                                 avg1="taper",
                                 avg2="channel")

        # Prepare figure
        fig, ax, cax = _setup_figure(1,
                                     xLabel="Time [s]",
                                     yLabel="Frequency [Hz]",
                                     include_colorbar=True,
                                     grid=grid)
        fig.spectralPlot = True

        # Use `imshow` to render array as image
        time = self.time[trList[0]][self._selection.time[0]]
        ax.imshow(pltArr,
                  origin="lower",
                  interpolation=interp,
                  cmap=cmap,
                  vmin=vmin,
                  vmax=vmax,
                  extent=(time[0], time[-1], freqArr[0], freqArr[-1]),
                  aspect="auto")
        cbar = _setup_colorbar(fig,
                               ax,
                               cax,
                               label=dataLbl.replace(" [dB]", ""))
        if title is None:
            title = panelTitle
        ax.set_title(title, size=pltConfig["singleTitleSize"])

    # Increment overlay-counter and draw figure
    fig.objCount += 1
    plt.draw()
    self._selection = None
    return fig
Beispiel #3
0
def singlepanelplot(self,
                    trials="all",
                    channels="all",
                    toilim=None,
                    avg_channels=True,
                    title=None,
                    grid=None,
                    fig=None,
                    **kwargs):
    """
    Plot contents of :class:`~syncopy.AnalogData` objects using single-panel figure(s)
    
    Please refer to :func:`syncopy.singlepanelplot` for detailed usage information. 
    
    Examples
    --------
    Use :func:`~syncopy.tests.misc.generate_artificial_data` to create two synthetic 
    :class:`~syncopy.AnalogData` objects. 
    
    >>> from syncopy.tests.misc import generate_artificial_data
    >>> adata = generate_artificial_data(nTrials=10, nChannels=32) 
    >>> bdata = generate_artificial_data(nTrials=5, nChannels=16) 
    
    Plot an average of the first 16 channels, averaged across trials 2, 4, and 6:
    
    >>> fig = spy.singlepanelplot(adata, channels=range(16), trials=[2, 4, 6])
   
    Overlay average of latter half of channels, averaged across trials 1, 3, 5: 

    >>> fig = spy.singlepanelplot(adata, channels=range(16,32), trials=[1, 3, 5], fig=fig)
    
    Do not average channels:
    
    >>> fig = spy.singlepanelplot(adata, channels=range(16,32), trials=[1, 3, 5], avg_channels=False)
    
    Plot `adata` and `bdata` simultaneously in two separate figures:
    
    >>> fig1, fig2 = spy.singlepanelplot(adata, bdata, overlay=False)
    
    Overlay `adata` and `bdata`; use channel and trial selections that are valid 
    for both datasets:
    
    >>> fig3 = spy.singlepanelplot(adata, bdata, channels=range(16), trials=[1, 2, 3])
    
    See also
    --------
    syncopy.singlepanelplot : visualize Syncopy data objects using single-panel plots
    """

    # Collect input arguments in dict `inputArgs` and process them
    inputArgs = locals()
    inputArgs.pop("self")
    dimArrs, dimCounts, idx, timeIdx, chanIdx = _prep_analog_plots(
        self, "singlepanelplot", **inputArgs)
    (nTrials, nChan) = dimCounts
    (trList, chArr) = dimArrs

    # If we're overlaying a multi-channel plot, ensure settings match up; also,
    # do not try to overlay on top of multi-panel plots
    if hasattr(fig, "multipanelplot"):
        lgl = "single-panel figure generated by `singleplot`"
        act = "multi-panel figure generated by `multipanelplot`"
        raise SPYValueError(legal=lgl, varname="fig", actual=act)
    if hasattr(fig, "chanOffsets"):
        if avg_channels:
            lgl = "multi-channel plot"
            act = "channel averaging was requested for multi-channel plot overlay"
            raise SPYValueError(legal=lgl,
                                varname="channels/avg_channels",
                                actual=act)
        if nChan != len(fig.chanOffsets):
            lgl = "channel-count matching existing multi-channel panels in figure"
            act = "{} channels per panel but {} channels for plotting".format(
                len(fig.chanOffsets), nChan)
            raise SPYValueError(legal=lgl,
                                varname="channels/channels per panel",
                                actual=act)

    # Ensure provided timing selection can actually be averaged (leverage
    # the fact that `toilim` selections exclusively generate slices)
    if nTrials > 0:
        tLengths = _prep_toilim_avg(self)

    # Generic titles for figures
    overlayTitle = "Overlay of {} datasets"

    # Either create new figure or fetch existing
    if fig is None:
        if nTrials > 0:
            xLabel = "Time [s]"
        else:
            xLabel = "Samples"
        fig, ax = _setup_figure(1, xLabel=xLabel, grid=grid)
        fig.analogPlot = True
    else:
        ax, = fig.get_axes()

    # Single-channel panel
    if avg_channels:

        # Set up pieces of generic figure titles
        if nChan > 1:
            chanTitle = "Average of {} channels".format(nChan)
        else:
            chanTitle = chArr[0]

        # Plot entire timecourse
        if nTrials == 0:

            # Do not fetch entire dataset at once, but channel by channel
            chanSec = np.arange(self.channel.size)[self._selection.channel]
            pltArr = np.zeros((self.data.shape[timeIdx], ),
                              dtype=self.data.dtype)
            for chan in chanSec:
                idx[chanIdx] = chan
                pltArr += self.data[tuple(idx)].squeeze()
            pltArr /= nChan

            # The actual plotting command...
            ax.plot(pltArr)

            # Set plot title depending on dataset overlay
            if fig.objCount == 0:
                if title is None:
                    title = chanTitle
                ax.set_title(title, size=pltConfig["singleTitleSize"])
            else:
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles, labels)
                if title is None:
                    title = overlayTitle.format(len(handles))
                ax.set_title(title, size=pltConfig["singleTitleSize"])

        # Average across trials
        else:

            # Compute channel-/trial-average time-course: 2D array with slice/list
            # selection does not require fancy indexing - no need to check this here
            pltArr = np.zeros((tLengths[0], ), dtype=self.data.dtype)
            for k, trlno in enumerate(trList):
                idx[timeIdx] = self._selection.time[k]
                pltArr += self._get_trial(trlno)[tuple(idx)].mean(
                    axis=chanIdx).squeeze()
            pltArr /= nTrials

            # The actual plotting command is literally one line...
            time = self.time[trList[0]][self._selection.time[0]]
            ax.plot(time, pltArr, label=os.path.basename(self.filename))
            ax.set_xlim([time[0], time[-1]])

            # Set plot title depending on dataset overlay
            if fig.objCount == 0:
                if title is None:
                    if nTrials > 1:
                        trTitle = "{0}across {1} trials".format(
                            "averaged " if nChan == 1 else "", nTrials)
                    else:
                        trTitle = "Trial #{}".format(trList[0])
                    title = "{}, {}".format(chanTitle, trTitle)
                ax.set_title(title, size=pltConfig["singleTitleSize"])
            else:
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles, labels)
                if title is None:
                    title = overlayTitle.format(len(handles))
                ax.set_title(title, size=pltConfig["singleTitleSize"])

    # Multi-channel panel
    else:

        # "Raw" data, do not respect any trials
        if nTrials == 0:

            # If required, compute max amplitude across provided channels
            if not hasattr(fig, "chanOffsets"):
                maxAmps = np.zeros((nChan, ), dtype=self.data.dtype)
                tickOffsets = maxAmps.copy()
                chanSec = np.arange(self.channel.size)[self._selection.channel]
                for k, chan in enumerate(chanSec):
                    idx[chanIdx] = chan
                    pltArr = np.abs(self.data[tuple(idx)].squeeze())
                    maxAmps[k] = pltArr.max()
                    tickOffsets[k] = pltArr.mean()
                fig.chanOffsets = np.cumsum([0] + [maxAmps.max()] *
                                            (nChan - 1))
                fig.tickOffsets = fig.chanOffsets + tickOffsets.mean()

            # Do not plot all at once but cycle through channels to not overflow memory
            for k, chan in enumerate(chanSec):
                idx[chanIdx] = chan
                ax.plot(self.data[tuple(idx)].squeeze() + fig.chanOffsets[k],
                        color=plt.rcParams["axes.prop_cycle"].by_key()["color"]
                        [fig.objCount],
                        label=os.path.basename(self.filename))
                if grid is not None:
                    ax.grid(grid)

            # Set plot title depending on dataset overlay
            if fig.objCount == 0:
                if title is None:
                    if nChan > 1:
                        title = "Entire Data Timecourse of {} channels".format(
                            nChan)
                    else:
                        title = "Entire Data Timecourse of {}".format(chArr[0])
                ax.set_yticks(fig.tickOffsets)
                ax.set_yticklabels(chArr)
                ax.set_title(title, size=pltConfig["singleTitleSize"])
            else:
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles[::(nChan + 1)], labels[::(nChan + 1)])
                if title is None:
                    title = overlayTitle.format(len(handles))
                ax.set_title(title, size=pltConfig["singleTitleSize"])

        # Average across trial(s)
        else:

            # Compute trial-average
            pltArr = np.zeros((tLengths[0], nChan), dtype=self.data.dtype)
            for k, trlno in enumerate(trList):
                idx[timeIdx] = self._selection.time[k]
                pltArr += np.swapaxes(
                    self._get_trial(trlno)[tuple(idx)], timeIdx, 0)
            pltArr /= nTrials

            # If required, compute offsets for multi-channel plot
            if not hasattr(fig, "chanOffsets"):
                fig.chanOffsets = np.cumsum([0] + [np.abs(pltArr).max()] *
                                            (nChan - 1))
                fig.tickOffsets = fig.chanOffsets + np.abs(pltArr).mean()

            # Plot the entire trial-averaged array at once
            time = self.time[trList[0]][self._selection.time[0]]
            ax.plot(time, (pltArr + fig.chanOffsets.reshape(1, nChan)).reshape(
                time.size, nChan),
                    color=plt.rcParams["axes.prop_cycle"].by_key()["color"][
                        fig.objCount],
                    label=os.path.basename(self.filename))
            if grid is not None:
                ax.grid(grid)

            # Set plot title depending on dataset overlay
            if fig.objCount == 0:
                if title is None:
                    title = "{0} channels {1}across {2} trials".format(
                        nChan, "averaged " if nTrials > 1 else "", nTrials)
                ax.set_title(title, size=pltConfig["singleTitleSize"])
            else:
                handles, labels = ax.get_legend_handles_labels()
                ax.legend(handles[::(nChan + 1)], labels[::(nChan + 1)])
                if title is None:
                    title = overlayTitle.format(len(handles))
                ax.set_title(title, size=pltConfig["singleTitleSize"])

    # Increment overlay-counter and draw figure
    fig.objCount += 1
    plt.draw()
    self._selection = None
    return fig
Beispiel #4
0
def multipanelplot(self,
                   trials="all",
                   channels="all",
                   tapers="all",
                   toilim=None,
                   foilim=None,
                   avg_channels=False,
                   avg_tapers=True,
                   avg_trials=True,
                   panels="channels",
                   interp="spline36",
                   cmap="plasma",
                   vmin=None,
                   vmax=None,
                   title=None,
                   grid=None,
                   fig=None,
                   **kwargs):
    """
    Plot contents of :class:`~syncopy.SpectralData` objects using multi-panel figure(s)
    
    Please refer to :func:`syncopy.multipanelplot` for detailed usage information. 
    
    Examples
    --------
    Use 16 panels to show frequency range 30-80 Hz of first 16 channels in `freqData` 
    averaged across trials 2, 4, and 6:
    
    >>> fig = spy.multipanelplot(freqData, trials=[2, 4, 6], channels=range(16),
                                 foilim=[30, 80], panels="channels")
                                  
    Same settings, but each panel represents a trial:
    
    >>> fig = spy.multipanelplot(freqData, trials=[2, 4, 6], channels=range(16),
                                 foilim=[30, 80], panels="trials", avg_trials=False, 
                                 avg_channels=True)
    
    Plot time-frequency contents of channels `'ecog_mua1'` and `'ecog_mua2'` of 
    `tfData` 
        
    >>> fig = spy.multipanelplot(tfData, channels=['ecog_mua1', 'ecog_mua2'])
    
    Note that multi-panel overlay plotting is **not** supported for 
    :class:`~syncopy.SpectralData` objects.
    
    See also
    --------
    syncopy.multipanelplot : visualize Syncopy data objects using multi-panel plots
    """

    # Collect input arguments in dict `inputArgs` and process them
    inputArgs = locals()
    inputArgs.pop("self")
    (dimArrs, dimCounts, isTimeFrequency, complexConversion, pltDtype,
     dataLbl) = _prep_spectral_plots(self, "multipanelplot", **inputArgs)
    (nTrials, nChan, nFreq, nTap) = dimCounts
    (trList, chArr, freqArr, tpArr) = dimArrs

    # No overlaying here...
    if hasattr(fig, "objCount"):
        msg = "Overlays of multi-panel `SpectralData` plots not supported"
        raise SPYError(msg)

    # Ensure panel-specification makes sense and is compatible w/averaging selection
    if not isinstance(panels, str):
        raise SPYTypeError(panels, varname="panels", expected="str")
    if panels not in availablePanels:
        lgl = "'" + "or '".join(opt + "' " for opt in availablePanels)
        raise SPYValueError(legal=lgl, varname="panels", actual=panels)
    if (panels == "channels" and avg_channels) or (panels == "trials" and avg_trials) \
        or (panels == "tapers" and avg_tapers):
        msg = "Cannot use `panels = {}` and average across {} at the same time. "
        SPYWarning(msg.format(panels, panels))
        return

    # Ensure the proper amount of averaging was specified
    avgFlags = [avg_channels, avg_trials, avg_tapers]
    if sum(avgFlags) == 0 and nTap * nTrials > 1:
        msg = "Need to average across at least one of tapers, channels or trials " +\
            "for visualization. "
        SPYWarning(msg)
        return
    if sum(avgFlags) == 3:
        msg = "Averaging across trials, channels and tapers results in " +\
            "single-panel plot. Please use `singlepanelplot` instead"
        SPYWarning(msg)
        return
    if isTimeFrequency:
        if sum(avgFlags) != 2:
            msg = "Multi-panel time-frequency visualization requires averaging across " +\
                "two out of three dimensions (tapers, channels trials)"
            SPYWarning(msg)
            return

    # Prepare figure (same for all cases)
    if panels == "channels":
        npanels = nChan
    elif panels == "trials":
        npanels = nTrials
    else:  # ``panels == "tapers"``
        npanels = nTap

    # Construct subplot panel layout or vet provided layout
    nrow = kwargs.get("nrow", None)
    ncol = kwargs.get("ncol", None)
    if not isTimeFrequency:
        fig, ax_arr = _setup_figure(npanels,
                                    nrow=nrow,
                                    ncol=ncol,
                                    xLabel="Frequency [Hz]",
                                    yLabel=dataLbl,
                                    grid=grid,
                                    include_colorbar=False,
                                    sharex=True,
                                    sharey=True)
    else:
        fig, ax_arr, cax = _setup_figure(npanels,
                                         nrow=nrow,
                                         ncol=ncol,
                                         xLabel="Time [s]",
                                         yLabel="Frequency [Hz]",
                                         grid=grid,
                                         include_colorbar=True,
                                         sharex=True,
                                         sharey=True)

    # Monkey-patch object-counter to newly created figure
    fig.spectralPlot = True

    # Start with the "simple" case: "regular" spectra, no time involved
    if not isTimeFrequency:

        # We're not dealing w/TF data here
        nTime = 1
        N = 1

        # For each panel stratification, set corresponding positional and
        # keyword args for iteratively calling `_compute_pltArr`
        if panels == "channels":

            panelVar = "channel"
            panelValues = chArr
            panelTitles = chArr

            if not avg_trials and avg_tapers:
                avgDim1 = "taper"
                avgDim2 = None
                innerVar = "trial"
                innerValues = trList
                majorTitle = "{} trials averaged across {} tapers".format(
                    nTrials, nTap)
                showLegend = True
            elif avg_trials and not avg_tapers:
                avgDim1 = None
                avgDim2 = None
                innerVar = "taper"
                innerValues = tpArr
                majorTitle = "{} tapers averaged across {} trials".format(
                    nTap, nTrials)
                showLegend = True
            else:  # `avg_trials` and `avg_tapers`
                avgDim1 = "taper"
                avgDim2 = None
                innerVar = "trial"
                innerValues = ["all"]
                majorTitle = " Average of {} tapers and {} trials".format(
                    nTap, nTrials)
                showLegend = False

        elif panels == "trials":

            panelVar = "trial"
            panelValues = trList
            panelTitles = ["Trial #{}".format(trlno) for trlno in trList]

            if not avg_channels and avg_tapers:
                avgDim1 = "taper"
                avgDim2 = None
                innerVar = "channel"
                innerValues = chArr
                majorTitle = "{} channels averaged across {} tapers".format(
                    nChan, nTap)
                showLegend = True
            elif avg_channels and not avg_tapers:
                avgDim1 = "channel"
                avgDim2 = None
                innerVar = "taper"
                innerValues = tpArr
                majorTitle = "{} tapers averaged across {} channels".format(
                    nTap, nChan)
                showLegend = True
            else:  # `avg_channels` and `avg_tapers`
                avgDim1 = "taper"
                avgDim2 = "channel"
                innerVar = "trial"
                innerValues = ["all"]
                majorTitle = " Average of {} channels and {} tapers".format(
                    nChan, nTap)
                showLegend = False

        else:  # panels = "tapers"

            panelVar = "taper"
            panelValues = tpArr
            panelTitles = ["Taper #{}".format(tpno) for tpno in tpArr]

            if not avg_trials and avg_channels:
                avgDim1 = "channel"
                avgDim2 = None
                innerVar = "trial"
                innerValues = trList
                majorTitle = "{} trials averaged across {} channels".format(
                    nTrials, nChan)
                showLegend = True
            elif avg_trials and not avg_channels:
                avgDim1 = None
                avgDim2 = None
                innerVar = "channel"
                innerValues = chArr
                majorTitle = "{} channels averaged across {} trials".format(
                    nChan, nTrials)
                showLegend = True
            else:  # `avg_trials` and `avg_channels`
                avgDim1 = "channel"
                avgDim2 = None
                innerVar = "trial"
                innerValues = ["all"]
                majorTitle = " Average of {} channels and {} trials".format(
                    nChan, nTrials)
                showLegend = False

        # Loop over panels, within each panel, loop over `innerValues` to (potentially)
        # plot multiple spectra per panel
        kwargs = {"avg1": avgDim1, "avg2": avgDim2}
        for panelCount, panelVal in enumerate(panelValues):
            kwargs[panelVar] = panelVal
            for innerVal in innerValues:
                kwargs[innerVar] = innerVal
                pltArr = _compute_pltArr(self, nFreq, N, nTime,
                                         complexConversion, pltDtype, **kwargs)
                ax_arr[panelCount].plot(freqArr,
                                        np.log10(pltArr),
                                        label=innerVar.capitalize() + " " +
                                        str(innerVal))
            ax_arr[panelCount].set_title(panelTitles[panelCount],
                                         size=pltConfig["multiTitleSize"])
        if showLegend:
            handles, labels = ax_arr[0].get_legend_handles_labels()
            ax_arr[0].legend(handles, labels)
        if title is None:
            fig.suptitle(majorTitle, size=pltConfig["singleTitleSize"])

    # Now, multi-panel time-frequency visualizations
    else:

        # Compute (and verify) length of selected time intervals
        tLengths = _prep_toilim_avg(self)
        nTime = tLengths[0]
        time = self.time[trList[0]][self._selection.time[0]]
        N = 1

        if panels == "channels":
            panelVar = "channel"
            panelValues = chArr
            panelTitles = chArr
            majorTitle = " Average of {} tapers and {} trials".format(
                nTap, nTrials)
            avgDim1 = "taper"
            avgDim2 = None

        elif panels == "trials":
            panelVar = "trial"
            panelValues = trList
            panelTitles = ["Trial #{}".format(trlno) for trlno in trList]
            majorTitle = " Average of {} channels and {} tapers".format(
                nChan, nTap)
            avgDim1 = "taper"
            avgDim2 = "channel"

        else:  # panels = "tapers"
            panelVar = "taper"
            panelValues = tpArr
            panelTitles = ["Taper #{}".format(tpno) for tpno in tpArr]
            majorTitle = " Average of {} channels and {} trials".format(
                nChan, nTrials)
            avgDim1 = "channel"
            avgDim2 = None

        # Loop over panels, within each panel, loop over `innerValues` to (potentially)
        # plot multiple spectra per panel
        kwargs = {"avg1": avgDim1, "avg2": avgDim2}
        vmins = []
        vmaxs = []
        for panelCount, panelVal in enumerate(panelValues):
            kwargs[panelVar] = panelVal
            pltArr = _compute_pltArr(self, nFreq, N, nTime, complexConversion,
                                     pltDtype, **kwargs)
            vmins.append(pltArr.min())
            vmaxs.append(pltArr.max())
            ax_arr[panelCount].imshow(pltArr,
                                      origin="lower",
                                      interpolation=interp,
                                      cmap=cmap,
                                      extent=(time[0], time[-1], freqArr[0],
                                              freqArr[-1]),
                                      aspect="auto")
            ax_arr[panelCount].set_title(panelTitles[panelCount],
                                         size=pltConfig["multiTitleSize"])

        # Render colorbar
        if vmin is None:
            vmin = min(vmins)
        if vmax is None:
            vmax = max(vmaxs)
        cbar = _setup_colorbar(fig,
                               ax_arr,
                               cax,
                               label=dataLbl.replace(" [dB]", ""),
                               outline=False,
                               vmin=vmin,
                               vmax=vmax)
        if title is None:
            fig.suptitle(majorTitle, size=pltConfig["singleTitleSize"])

    # Increment overlay-counter and draw figure
    fig.objCount += 1
    plt.draw()
    self._selection = None
    return fig