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
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
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
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