def plot_erp_specdiffs( d, samplerate=None, NFFT=256, freq_range=[0.1, 50], classes=[0,1], significant_only=False, pval=0.05, fig=None): assert d.data.ndim == 3 assert len(classes) == 2 assert d.feat_lab is not None if fig is None: fig = plot.figure() tf = trials.trial_specgram(d, samplerate, NFFT) tf_erp = trials.erp(tf) diff = np.log(tf_erp.data[...,classes[0]]) - np.log(tf_erp.data[...,classes[1]]) if significant_only: _,ps = scipy.stats.ttest_ind(tf.get_class(classes[0]).data, tf.get_class(classes[1]).data, axis=3) diff[ps > pval] = 0 ch_labs = tf_erp.feat_lab[0] freqs = np.array([float(x) for x in tf_erp.feat_lab[1]]) times = np.array([float(x) for x in tf_erp.feat_lab[2]]) selection = np.logical_and(freqs >= freq_range[0], freqs <= freq_range[1]) freqs = freqs[selection] diff = diff[:,selection] clim = (-np.max(np.abs(diff)), np.max(np.abs(diff))) num_channels = d.data.shape[0] num_cols = max(1, num_channels/8) num_rows = min(num_channels, 8) fig.subplots_adjust(hspace=0) cdict = {'red': ((0.0, 1.0, 1.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)), 'green': ((0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)), 'blue': ((0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 1.0, 1.0))} cmap = matplotlib.colors.LinearSegmentedColormap('polarity',cdict,256) for channel in range(num_channels): s = diff[channel,:,:] col = channel / num_rows row = channel % num_rows ax = plot.subplot(num_rows, num_cols, num_cols*row+col+1) im = plot.imshow( s, aspect='auto', extent=[np.min(times), np.max(times), np.min(freqs), np.max(freqs)], cmap=cmap ) plot.ylim(freq_range[0], freq_range[1]) plot.clim(clim) plot.ylabel(ch_labs[channel]) ax.xaxis.grid(True, color='w', which='major') ax.yaxis.grid(False) if row == num_rows-1 or channel == num_channels-1: plot.xlabel('Time (s)') else: [label.set_visible(False) for label in ax.get_xticklabels()] [tick.set_visible(False) for tick in ax.get_xticklines()] cax = fig.add_axes([0.91, 0.1, 0.01, 0.8]) fig.colorbar(im, cax=cax) return fig
def plot_topo( d, layout, samplerate=None, classes=None, vspace=None, cl_lab=None, ch_lab=None, draw_scale=True, start=0, fig=None, mirror_y=False, colors=['b', 'r', 'g', 'c', 'm', 'y', 'k', '#ffaa00'], linestyles=['-','-','-','-','-','-','-','-'], linewidths=[1, 1, 1, 1, 1, 1, 1, 1], pval=0.05, fwer=None, np_test=False, np_iter=1000, conf_inter=None, enforce_equal_n=True, ): ''' Create an Event Related Potential plot which aims to be as informative as possible. The result is aimed to be a publication ready figure, therefore this function supplies a lot of customization. The input can either be a sliced dataset (``d.data`` = [channels x samples x trials]) or a readily computed ERP given by :class:`psychic.nodes.ERP` or :func:`psychic.erp`. When possible, regions where ERPs differ significantly are shaded. This is meant to be an early indication of area's of interest and not meant as sound statistical evidence of an actual difference. When a sliced dataset is given, which contains two classes (or two classes are specified using the ``classes`` parameter) t-tests are performed for each sample. Significant sections (see the ``pval`` parameter) are drawn shaded. P-values are corrected using the Benjamini-Hochberg method. See the ``fwer`` parameter for other corrections (or to disable it). See the ``np_test`` parameter for a better (but slower) non-parametric test to determine significant regions. Parameters ---------- d : :class:`psychic.DataSet` A sliced Golem dataset that will be displayed. layout : :class:`psychic.layouts.Layout` Channel layout to use. classes : list (default=all) When specified, ERPs will be drawn only for the classes with the given indices. vspace : float (optional) Amount of vertical space between the ERP traces, by default the minumum value so traces don't overlap. samplerate : float (optional) By default determined through ``d.feat_lab[1]``, but can be specified when missing. cl_lab : list (optional) List with a label for each class, by default taken from ``d.cl_lab``, but can be specified if missing. ch_lab : list (optional) List of channel labels, by default taken from ``d.feat_lab[0]``, but can be specified if missing. draw_scale : bool (default=True) Whether to draw a scale next to the plot. start : float (default=0) Time used as T0, by default timing is taken from ``d.feat_lab[1]``, but can be specified if missing. fig : :class:`matplotlib.Figure` If speficied, a reference to the figure in which to draw the ERP plot. By default a new figure is created. mirror_y : bool (default=False) When set, negative is plotted up. Some publications use this style of plotting. colors : list (optional) Sets a color for each ERP. Values are given as matplotlib color specifications. See: http://matplotlib.org/api/colors_api.html linestyles : list (optional) Line style specifications for each ERP. See: http://matplotlib.org/1.3.0/api/pyplot_api.html#matplotlib.pyplot.plot linewidths : list (optional) Line width specifications for each ERP. Values are given in points. pval : float (default=0.05) Minimum p-value at which to color significant regions, set to None to disable it completely. fwer : function (default=None) Method for pval adjustment to correct for family-wise errors rising from performing multiple t-tests, choose one of the methods from the :mod:`psychic.fwer` module, or specify ``None`` to disable this correction. np_test : bool (default=False) Perform a non-parametric test to determine significant regions. This is much slower, but a much more powerful statistical method that deals correctly with the family-wise error problem. When this method is used, the ``fwer`` parameter is ignored. np_iter : int (default=1000) Number of iterations to perform when using the non-parametric test. Higher means a better approximation of the true p-values, at the cost of longer computation time. conf_inter : float (default=None) Draw given confidence interval of the ERP as a transparent band. The confidence interval can be specified in percent. Set to None to disable drawing of the confidence interval. enforce_equal_n : bool (default=True) Enforce that each ERP is calculated using the same number of trials. If one of the classes has more trials than the others, a random subset of the corresponding trials will be taken. Returns ------- fig : :class:`matplotlib.Figure` A handle to the matplotlib figure. ''' assert d.data.ndim == 3, 'Expecting sliced data' num_channels, num_samples = d.data.shape[:2] # Determine properties of the data that weren't explicitly supplied as # arguments. if cl_lab is None: cl_lab = d.cl_lab if d.cl_lab else['class %d' % cl for cl in classes] if ch_lab is None: if d.feat_lab is not None: ch_lab = d.feat_lab[0] else: ch_lab = ['CH %d' % (x+1) for x in range(num_channels)] if classes is None: classes = range(d.nclasses) num_classes = len(classes) # Determine number of trials num_trials = np.min( np.array(d.ninstances_per_class)[classes] ) # Calculate significance (if appropriate) if num_classes == 2 and np.min(np.array(d.ninstances_per_class)[classes]) >= 5: # Construct significant clusters for each channel. These will be # highlighted in the plot. significant_clusters = [] * num_channels if np_test: # Perform a non-parametric test from stats import temporal_permutation_cluster_test as test significant_clusters = test(d, np_iter, pval, classes)[:,:3] significance_test_performed = True elif pval is not None: # Perform a t-test ts, ps = scipy.stats.ttest_ind(d.get_class(classes[0]).data, d.get_class(classes[1]).data, axis=2) if fwer is not None: ps = fwer(ps.ravel()).reshape(ps.shape) for ch in range(num_channels): clusters = np.flatnonzero( np.diff(np.hstack(([False], ps[ch,:] < pval, [False]))) ).reshape(-1,2) for cl, cluster in enumerate(clusters): significant_clusters.append([ch, cluster[0], cluster[1]]) significance_test_performed = True else: significance_test_performed = False else: significance_test_performed = False # Calculate ERP erp = trials.erp(d, classes=classes, enforce_equal_n=enforce_equal_n) # Calculate a sane vspace if vspace is None: vspace = (np.min(erp.data), np.max(erp.data)) elif type(vspace) == float or type(vspace) == int: vspace = (-vspace, vspace) # Calculate timeline, using the best information available if samplerate is not None: ids = np.arange(num_samples) / float(samplerate) - start elif erp.feat_lab is not None: ids = np.array(erp.feat_lab[1], dtype=float) - start else: ids = np.arange(num_samples) - start # Plot ERP if fig is None: fig = plot.figure() def plot_channel(ax, ch): to_plot = erp.data[ch,:,:] if not mirror_y else -erp.data[ch,: , :] # Plot each class for cl in range(num_classes): plt.plot(ids, to_plot[:, cl], label=cl_lab[classes[cl]], color=colors[cl % len(colors)], linestyle=linestyles[cl % len(linestyles)], linewidth=linewidths[cl % len(linewidths)], clip_on=False) # Color significant differences if significance_test_performed: for cl in significant_clusters: c, x1, x2 = cl if c != ch: continue x = range(int(x1), int(x2)) y1 = np.min(to_plot[x, :], axis=1) y2 = np.max(to_plot[x, :], axis=1) x = np.concatenate((ids[x], ids[x[::-1]])) y = np.concatenate((y1, y2[::-1])) p = ax.fill(x, y, facecolor='g', alpha=0.2) plt.grid(False) plt.axis('off') plt.xlim(ids[0], ids[-1]) plt.ylim(vspace) plt.axhline(0, color='k') plt.axvline(0, color='k') plt.text(0, 1, ch_lab[ch], verticalalignment='top', transform=ax.transAxes) def plot_scale(ax): ax.spines['left'].set_position(('data', 0)) ax.spines['left'].set_color('k') ax.spines['bottom'].set_position(('data', 0)) ax.spines['bottom'].set_color('k') ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left') fig.canvas.draw() plt.xlim(ids[0], ids[-1]) plt.ylim(vspace) xticklabels = [tl.get_text() for tl in ax.get_xticklabels()] ax.set_xticklabels([''] + xticklabels[1:]) plt.grid(False) plt.axvline(0, color='k') plt.axhline(0, color='k') plt.xlabel('time (s)') plt.ylabel(u'voltage (µV)') for ch, ax in enumerate(layout.axes(fig)): plot_channel(ax, ch) if draw_scale: ax = fig.add_axes(layout.get_scale()) plot_scale(ax) return fig
def plot_erp( d, samplerate=None, classes=None, vspace=None, cl_lab=None, ch_lab=None, draw_scale=True, ncols=None, start=0, fig=None, mirror_y=False, colors=['b', 'r', 'g', 'c', 'm', 'y', 'k', '#ffaa00'], linestyles=['-','-','-','-','-','-','-','-'], linewidths=[1, 1, 1, 1, 1, 1, 1, 1], pval=0.05, fwer=None, np_test=False, np_iter=1000, conf_inter=None, enforce_equal_n=True, ): ''' Create an Event Related Potential plot which aims to be as informative as possible. The result is aimed to be a publication ready figure, therefore this function supplies a lot of customization. The input can either be a sliced dataset (``d.data`` = [channels x samples x trials]) or a readily computed ERP given by :class:`psychic.nodes.ERP` or :func:`psychic.erp`. When possible, regions where ERPs differ significantly are shaded. This is meant to be an early indication of area's of interest and not meant as sound statistical evidence of an actual difference. When a sliced dataset is given, which contains two classes (or two classes are specified using the ``classes`` parameter) t-tests are performed for each sample. Significant sections (see the ``pval`` parameter) are drawn shaded. P-values are corrected using the Benjamini-Hochberg method. See the ``fwer`` parameter for other corrections (or to disable it). See the ``np_test`` parameter for a better (but slower) non-parametric test to determine significant regions. Parameters ---------- d : :class:`psychic.DataSet` A sliced Golem dataset that will be displayed. classes : list (default=all) When specified, ERPs will be drawn only for the classes with the given indices. vspace : float (optional) Amount of vertical space between the ERP traces, by default the minumum value so traces don't overlap. samplerate : float (optional) By default determined through ``d.feat_lab[1]``, but can be specified when missing. cl_lab : list (optional) List with a label for each class, by default taken from ``d.cl_lab``, but can be specified if missing. ch_lab : list (optional) List of channel labels, by default taken from ``d.feat_lab[0]``, but can be specified if missing. draw_scale : bool (default=True) Whether to draw a scale next to the plot. ncols : int (default=nchannels/15) Number of columns to use for layout. start : float (default=0) Time used as T0, by default timing is taken from ``d.feat_lab[1]``, but can be specified if missing. fig : :class:`matplotlib.Figure` If speficied, a reference to the figure in which to draw the ERP plot. By default a new figure is created. mirror_y : bool (default=False) When set, negative is plotted up. Some publications use this style of plotting. colors : list (optional) Sets a color for each ERP. Values are given as matplotlib color specifications. See: http://matplotlib.org/api/colors_api.html linestyles : list (optional) Line style specifications for each ERP. See: http://matplotlib.org/1.3.0/api/pyplot_api.html#matplotlib.pyplot.plot linewidths : list (optional) Line width specifications for each ERP. Values are given in points. pval : float (default=0.05) Minimum p-value at which to color significant regions, set to None to disable it completely. fwer : function (default=None) Method for pval adjustment to correct for family-wise errors rising from performing multiple t-tests, choose one of the methods from the :mod:`psychic.fwer` module, or specify ``None`` to disable this correction. np_test : bool (default=False) Perform a non-parametric test to determine significant regions. This is much slower, but a much more powerful statistical method that deals correctly with the family-wise error problem. When this method is used, the ``fwer`` parameter is ignored. np_iter : int (default=1000) Number of iterations to perform when using the non-parametric test. Higher means a better approximation of the true p-values, at the cost of longer computation time. conf_inter : float (default=None) Draw given confidence interval of the ERP as a transparent band. The confidence interval can be specified in percent. Set to None to disable drawing of the confidence interval. enforce_equal_n : bool (default=True) Enforce that each ERP is calculated using the same number of trials. If one of the classes has more trials than the others, a random subset of the corresponding trials will be taken. Returns ------- fig : :class:`matplotlib.Figure` A handle to the matplotlib figure. ''' assert d.data.ndim == 3, 'Expecting sliced data' num_channels, num_samples = d.data.shape[:2] # Determine properties of the data that weren't explicitly supplied as # arguments. if cl_lab is None: cl_lab = d.cl_lab if d.cl_lab else['class %d' % cl for cl in classes] if ch_lab is None: if d.feat_lab is not None: ch_lab = d.feat_lab[0] else: ch_lab = ['CH %d' % (x+1) for x in range(num_channels)] if classes is None: classes = range(d.nclasses) num_classes = len(classes) # Determine number of trials num_trials = np.min( np.array(d.ninstances_per_class)[classes] ) # Calculate significance (if appropriate) if num_classes == 2 and np.min(np.array(d.ninstances_per_class)[classes]) >= 5: # Construct significant clusters for each channel. These will be # highlighted in the plot. significant_clusters = [] * num_channels if np_test: # Perform a non-parametric test from stats import temporal_permutation_cluster_test as test significant_clusters = test(d, np_iter, pval, classes)[:,:3] significance_test_performed = True elif pval is not None: # Perform a t-test ts, ps = scipy.stats.ttest_ind(d.get_class(classes[0]).data, d.get_class(classes[1]).data, axis=2) if fwer is not None: ps = fwer(ps.ravel()).reshape(ps.shape) for ch in range(num_channels): clusters = np.flatnonzero( np.diff(np.hstack(([False], ps[ch,:] < pval, [False]))) ).reshape(-1,2) for cl, cluster in enumerate(clusters): significant_clusters.append([ch, cluster[0], cluster[1]]) significance_test_performed = True else: significance_test_performed = False else: significance_test_performed = False # Calculate ERP erp = trials.erp(d, classes=classes, enforce_equal_n=enforce_equal_n) # Calculate a sane vspace if vspace is None: vspace = (np.max(erp.data) - np.min(erp.data)) # Calculate timeline, using the best information available if samplerate is not None: ids = np.arange(num_samples) / float(samplerate) - start elif erp.feat_lab is not None: ids = np.array(erp.feat_lab[1], dtype=float) - start else: ids = np.arange(num_samples) - start # Plot ERP if fig is None: fig = plot.figure() if ncols is None: ncols = max(1, num_channels/15) channels_per_col = int(np.ceil(num_channels / float(ncols))) for subplot in range(ncols): if ncols > 1: plot.subplot(1, ncols, subplot+1) # Determine channels to plot channels = np.arange( subplot * channels_per_col, min(num_channels, (subplot+1) * channels_per_col), dtype = np.int ) # Spread out the channels with vspace bases = vspace * np.arange(len(channels))[::-1] to_plot = np.zeros((len(channels), num_samples, num_classes)) for i in range(len(channels)): to_plot[i,:,:] = (erp.data[channels[i],:,:] if not mirror_y else -1*erp.data[channels[i],:,:]) + bases[i] # Plot each class for cl in range(num_classes): traces = matplotlib.collections.LineCollection( [zip(ids, to_plot[y,:,cl]) for y in range(len(channels))], label=cl_lab[classes[cl]], color=[colors[cl % len(colors)]], linestyle=[linestyles[cl % len(linestyles)]], linewidth=[linewidths[cl % len(linewidths)]] ) plot.gca().add_collection(traces) # Color significant differences if significance_test_performed: for cl in significant_clusters: c, x1, x2 = cl if not c in channels: continue else: c -= channels[0] x = range(int(x1), int(x2)) y1 = np.min(to_plot[c,x,:], axis=1) y2 = np.max(to_plot[c,x,:], axis=1) x = np.concatenate( (ids[x], ids[x[::-1]]) ) y = np.concatenate((y1, y2[::-1])) p = plot.fill(x, y, facecolor='g', alpha=0.2) # Plot confidence intervals if conf_inter is not None: stds = np.concatenate([ np.std(d.get_class(classes[i]).data[channels,:,:], axis=2)[:,:,np.newaxis] for i in range(num_classes) ], axis=2) x = np.concatenate( (ids, ids[::-1]) ) y = np.concatenate((to_plot + stds, to_plot[:, ::-1, :] - stds[:, ::-1, :]), axis=1) for i in range(num_classes): for j in range(len(channels)): plot.fill(x, y[j,:,i], facecolor=colors[i], alpha=0.2) _draw_eeg_frame(channels_per_col, vspace, ids, np.array(ch_lab)[channels].tolist(), mirror_y, draw_scale=(draw_scale and (subplot == ncols-1))) plot.axvline(0, 0, 1, color='k') plot.xlabel('Time (s)') if subplot == 0: l = plot.legend(loc='upper left') l.draggable(True) plot.title('Event Related Potential (n=%d)' % num_trials) plot.ylabel('Channels') return fig