|
@@ -0,0 +1,758 @@
|
|
|
+# code for Figure 5 panels
|
|
|
+
|
|
|
+# import libs
|
|
|
+from matplotlib import pyplot as plt
|
|
|
+import numpy as np
|
|
|
+import pandas
|
|
|
+from scipy import stats
|
|
|
+from importlib import reload
|
|
|
+import pickle
|
|
|
+from scipy.optimize import curve_fit
|
|
|
+import spatint_utils
|
|
|
+
|
|
|
+
|
|
|
+# reload module
|
|
|
+reload(spatint_utils)
|
|
|
+
|
|
|
+spatint_utils.plot_params()
|
|
|
+_, _, optocolor = spatint_utils.get_colors()
|
|
|
+
|
|
|
+
|
|
|
+class Fig5:
|
|
|
+ """Class for plotting panels for Fig.5"""
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ """Init class"""
|
|
|
+
|
|
|
+ # read trn size tuning dataframe
|
|
|
+ self.trn_sztun_opto_df = pandas.read_pickle(
|
|
|
+ filepath_or_buffer='./to_publish/data/trn_sztun_opto_df.pkl')
|
|
|
+
|
|
|
+ # read dict for trn sz tuning example
|
|
|
+ with open('./to_publish/data/trn_sztun_opto_ex_dict.pkl', 'rb') as f:
|
|
|
+ self.trn_sztun_opto_ex_dict = pickle.load(f)
|
|
|
+
|
|
|
+ def ex_sztun_curve(self, figsize=(5, 2.5), ax=None):
|
|
|
+ """Plot example dLGN size-tuning raster plot and curves (Fig. 5bc)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # create figure
|
|
|
+ f = plt.figure(figsize=figsize)
|
|
|
+ axs = []
|
|
|
+ axs.append(f.add_axes([0.1, 0.2, 0.35, 0.6]))
|
|
|
+ axs.append(f.add_axes([0.6, 0.2, 0.35, 0.6]))
|
|
|
+
|
|
|
+ # get data for example trn neuron
|
|
|
+ ex_data = self.trn_sztun_opto_ex_dict
|
|
|
+
|
|
|
+ # plot raster
|
|
|
+ spatint_utils.plot_raster(raster=ex_data['rasters'][ex_data['u']],
|
|
|
+ tranges=ex_data['tranges'],
|
|
|
+ opto=ex_data['opto'],
|
|
|
+ opto_ranges=ex_data['opto_ranges'],
|
|
|
+ ax=axs[0])
|
|
|
+
|
|
|
+ # plot curves
|
|
|
+ spatint_utils.plot_tun(means=ex_data['tun_mean'],
|
|
|
+ sems=ex_data['tun_sem'],
|
|
|
+ spons=ex_data['tun_spon_mean'],
|
|
|
+ xs=ex_data['ti_axes'],
|
|
|
+ params=ex_data['tun_pars'],
|
|
|
+ ax=axs[1],
|
|
|
+ sponline='-',
|
|
|
+ sponlinewidth=0.5,
|
|
|
+ ms=3.4)
|
|
|
+
|
|
|
+ # format layout
|
|
|
+ axs[1].set_xticks((0, 25, 50, 75))
|
|
|
+ axs[1].set_yticks((0, 25, 50))
|
|
|
+ axs[1].spines['bottom'].set_bounds(0, 75)
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|
|
|
+ def fit_norm_curves(self, ax=None, figsize=(2.5, 2.5)):
|
|
|
+ """Plot mean normalized model fit for size tuning in TRN (Fig. 5d)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # create figure if ax is none
|
|
|
+ f, ax = plt.subplots(figsize=figsize)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ trn_sztun_opto_df = self.trn_sztun_opto_df
|
|
|
+
|
|
|
+ # restrict to units fitted in both conditions
|
|
|
+ trn_sztun_opto_df = trn_sztun_opto_df[
|
|
|
+ trn_sztun_opto_df.tun_pars.apply(lambda x: any(~np.isnan(x[1])))]
|
|
|
+ trn_sztun_opto_df.reset_index(inplace=True)
|
|
|
+
|
|
|
+ # create array to store all generated curves in both conditions
|
|
|
+ x_eval = np.arange(76)
|
|
|
+ ys = np.zeros((len(trn_sztun_opto_df), len(x_eval), 2))
|
|
|
+ ys[:] = np.nan
|
|
|
+
|
|
|
+ # iterate over units
|
|
|
+ for bestexp in trn_sztun_opto_df.itertuples():
|
|
|
+ # get size tun parameters
|
|
|
+ params = bestexp.tun_pars
|
|
|
+
|
|
|
+ # evaluate model
|
|
|
+ ycont = spatint_utils.rog_offset(x_eval, *params[0])
|
|
|
+ yopto = spatint_utils.rog_offset(x_eval, *params[1])
|
|
|
+
|
|
|
+ # normalize by largest value
|
|
|
+ maxy = np.nanmax(np.concatenate((ycont, yopto)))
|
|
|
+ ycont_norm = ycont / maxy
|
|
|
+ yopto_norm = yopto / maxy
|
|
|
+
|
|
|
+ # store curves in array
|
|
|
+ ys[bestexp.Index, :, 0] = ycont_norm
|
|
|
+ ys[bestexp.Index, :, 1] = yopto_norm
|
|
|
+
|
|
|
+ # compute mean for both conditions
|
|
|
+ cont_mean = np.nanmean(ys[:, :, 0], axis=0)
|
|
|
+ opto_mean = np.nanmean(ys[:, :, 1], axis=0)
|
|
|
+
|
|
|
+ # plot curves and sem
|
|
|
+ ax.plot(x_eval, cont_mean, color='k', linestyle='-')
|
|
|
+ cont_sem = stats.sem(ys[:, :, 0], axis=0)
|
|
|
+ ax.fill_between(x_eval, cont_mean - cont_sem, cont_mean + cont_sem, color='k',
|
|
|
+ alpha=0.5, linewidth=0)
|
|
|
+ ax.plot(x_eval, opto_mean, color=optocolor, linestyle='-')
|
|
|
+ opto_sem = stats.sem(ys[:, :, 1], axis=0)
|
|
|
+ ax.fill_between(x_eval, opto_mean - opto_sem, opto_mean + opto_sem,
|
|
|
+ color=optocolor, alpha=0.5, linewidth=0)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ ax.set_ylabel('Normalized firing rate')
|
|
|
+ ax.set_xlabel('Diameter ($\degree$)')
|
|
|
+ ax.set_xticks((0, 25, 50, 75))
|
|
|
+ ax.set_yticks((0, 0.5, 1))
|
|
|
+ ax.spines['bottom'].set_bounds(0, 75)
|
|
|
+ ax.spines['left'].set_bounds(0, 1)
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|
|
|
+ def scatter(self, figsize=(2.5, 2.5), ax=None, alys=None):
|
|
|
+ """Plot scatterplots to compare features of size tuning curves with and without V1
|
|
|
+ suppression (Fig. 5e-h)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ alys: string
|
|
|
+ analysis to compute: responsiveness (all_stims), burst ratio (bratio),
|
|
|
+ preferred size (rfcs), surround suppression (si)
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # create figure if ax is none
|
|
|
+ f, ax = plt.subplots(figsize=figsize)
|
|
|
+
|
|
|
+ # determine data set
|
|
|
+ if (alys == 'all_stims') or (alys == 'bratio'):
|
|
|
+ # for responsiveness and burst ratio we consider also units that fired little in
|
|
|
+ # opto condition or were not well fit
|
|
|
+ trn_sztun_opto_df = self.trn_sztun_opto_df
|
|
|
+
|
|
|
+ elif (alys == 'rfcs') or (alys == 'si'):
|
|
|
+ # to interpret preferred size and si units must be fit in both conditions and
|
|
|
+ # must not be completely suppressed by V1 suppression
|
|
|
+ trn_sztun_opto_df = self.trn_sztun_opto_df[
|
|
|
+ (self.trn_sztun_opto_df.tun_rsq.apply(lambda x: ~np.isnan(x[1]))) &
|
|
|
+ (self.trn_sztun_opto_df.tun_mean.apply(lambda x: np.mean(x[1])) >= 0.1)]
|
|
|
+
|
|
|
+ # get index of example key for plotting
|
|
|
+ ex_row = trn_sztun_opto_df[
|
|
|
+ (trn_sztun_opto_df['m'] == self.trn_sztun_opto_ex_dict['m']) &
|
|
|
+ (trn_sztun_opto_df['s'] == self.trn_sztun_opto_ex_dict['s']) &
|
|
|
+ (trn_sztun_opto_df['e'] == self.trn_sztun_opto_ex_dict['e']) &
|
|
|
+ (trn_sztun_opto_df['u'] == self.trn_sztun_opto_ex_dict['u'])
|
|
|
+ ].index.values
|
|
|
+ all_indic = trn_sztun_opto_df.index.values
|
|
|
+ ex_index = np.where(ex_row == all_indic)[0]
|
|
|
+
|
|
|
+ if alys == 'all_stims':
|
|
|
+ # compare mean response to all stimuli (panel e)
|
|
|
+
|
|
|
+ # get mean responses to all stims from data
|
|
|
+ fr_lst = trn_sztun_opto_df.tun_mean.tolist()
|
|
|
+ cont = np.asarray([np.nanmean(fr[:, 0]) for fr in fr_lst])
|
|
|
+ supp = np.asarray([np.nanmean(fr[:, 1]) for fr in fr_lst])
|
|
|
+
|
|
|
+ # get stats for example unit
|
|
|
+ ex_cont = cont[ex_index]
|
|
|
+ ex_supp = supp[ex_index]
|
|
|
+ ex_diff = ((ex_supp / ex_cont) - 1) * 100
|
|
|
+ print('example cell mean response: \n'
|
|
|
+ 'control: %.3f \n'
|
|
|
+ 'suppressed: %.3f \n'
|
|
|
+ 'perc change: %.3f\n'
|
|
|
+ % (ex_cont, ex_supp, ex_diff))
|
|
|
+
|
|
|
+ # compute stats for population
|
|
|
+ cont_mean, supp_mean = spatint_utils.compute_stats(cont=cont, supp=supp, alys=alys)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ titlestr = 'Responsiveness (sp/s)'
|
|
|
+ ax.set_title(titlestr)
|
|
|
+ uplim = 85
|
|
|
+ assert not max(max(cont), max(supp)) > uplim, 'found resp > uplim'
|
|
|
+ ax.plot((0, uplim), (0, uplim), linestyle='-', color='grey', zorder=-1,
|
|
|
+ linewidth=0.35)
|
|
|
+ ax.set_xlim(-3, uplim + 3)
|
|
|
+ ax.set_ylim(-3, uplim + 3)
|
|
|
+ ax.set_xticks((0, 40, 80))
|
|
|
+ ax.set_yticks((0, 40, 80))
|
|
|
+ xlims = ax.get_xlim()
|
|
|
+ ylims = ax.get_ylim()
|
|
|
+ ax.spines['left'].set_bounds(0, ylims[1])
|
|
|
+ ax.spines['bottom'].set_bounds(0, xlims[1])
|
|
|
+
|
|
|
+ elif alys == 'bratio':
|
|
|
+ # compare burst ratios (panel f)
|
|
|
+
|
|
|
+ # compute per condition
|
|
|
+ cont = trn_sztun_opto_df.bratio_c.apply(lambda x: np.nanmean(x))
|
|
|
+ supp = trn_sztun_opto_df.bratio_op.apply(lambda x: np.nanmean(x))
|
|
|
+
|
|
|
+ # compute and calculate stats
|
|
|
+ cont_mean, supp_mean = spatint_utils.compute_stats(cont=cont, supp=supp, alys=alys)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ titlestr = 'Burst ratio'
|
|
|
+ ax.set_title(titlestr)
|
|
|
+ ax.set_xlim(0.003, 1.05)
|
|
|
+ ax.set_ylim(0.003, 1.05)
|
|
|
+ ax.plot((0.0035, 1), (0.0035, 1), linestyle='-', color='grey', linewidth=0.35,
|
|
|
+ zorder=-1)
|
|
|
+ ax.spines['left'].set_bounds(0.0035, 1)
|
|
|
+ ax.spines['bottom'].set_bounds(0.0035, 1)
|
|
|
+
|
|
|
+ # threshold for plotting
|
|
|
+ cont[cont == 0] = 0.0035
|
|
|
+ supp[supp == 0] = 0.0035
|
|
|
+ assert not max(max(cont), max(supp)) > 1, 'found bratio > 1'
|
|
|
+ assert not min(min(cont), min(supp)) < 0.0035, 'found bratio < 0.0035'
|
|
|
+
|
|
|
+ # print burst ratios for example neuron
|
|
|
+ ex_cont = cont[ex_index]
|
|
|
+ ex_supp = supp[ex_index]
|
|
|
+ print('example neuron burst ratio: \n'
|
|
|
+ 'control: %.3f \n'
|
|
|
+ 'suppressed: %.3f \n'
|
|
|
+ % (ex_cont, ex_supp))
|
|
|
+
|
|
|
+ elif alys == 'rfcs':
|
|
|
+ # compare preferred size (panel g)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ rfcs_int = np.vstack(trn_sztun_opto_df.rfcs_76.values)
|
|
|
+ cont = rfcs_int[:, 0]
|
|
|
+ supp = rfcs_int[:, 1]
|
|
|
+
|
|
|
+ # print preferred size for example neuron
|
|
|
+ ex_cont = rfcs_int[ex_index][0][0]
|
|
|
+ ex_supp = rfcs_int[ex_index][0][1]
|
|
|
+ print('example cell rfcs: \n'
|
|
|
+ 'control: %.3f \n'
|
|
|
+ 'suppressed: %.3f \n'
|
|
|
+ % (ex_cont, ex_supp))
|
|
|
+
|
|
|
+ # compute stats
|
|
|
+ cont_mean, supp_mean = spatint_utils.compute_stats(cont=cont, supp=supp, alys=alys)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ titlestr = 'Preferred size'
|
|
|
+ ax.set_title(titlestr)
|
|
|
+ ax.set_xlim(-3, np.nanmax(cont) + 3)
|
|
|
+ ax.set_ylim(-3, np.nanmax(supp) + 3)
|
|
|
+ ax.plot((0, 75), (0, 75), linestyle='-', color='grey', zorder=-1, linewidth=0.35)
|
|
|
+ ax.spines['left'].set_bounds(0, 75)
|
|
|
+ ax.spines['bottom'].set_bounds(0, 75)
|
|
|
+
|
|
|
+ elif alys == 'si':
|
|
|
+ # compare suppression indices (panel h)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ si_int = np.vstack(trn_sztun_opto_df.si_76.values)
|
|
|
+ assert not any(si_int[si_int > 1] or si_int[si_int < 0]), 'si > 1 or < 0, check!'
|
|
|
+ cont = si_int[:, 0]
|
|
|
+ supp = si_int[:, 1]
|
|
|
+
|
|
|
+ # get example si
|
|
|
+ ex_cont = si_int[ex_index][0][0]
|
|
|
+ ex_supp = si_int[ex_index][0][1]
|
|
|
+ print('example cell si: \n'
|
|
|
+ 'control: %.3f \n'
|
|
|
+ 'suppressed: %.3f \n'
|
|
|
+ % (ex_cont, ex_supp))
|
|
|
+
|
|
|
+ # compute stats
|
|
|
+ cont_mean, supp_mean = spatint_utils.compute_stats(cont=cont, supp=supp, alys=alys)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ titlestr = 'si'
|
|
|
+ ax.set_title(titlestr)
|
|
|
+ ax.set_xlim(-0.05, 1.05)
|
|
|
+ ax.set_ylim(-0.05, 1.05)
|
|
|
+ ax.set_xticks((0, 0.5, 1))
|
|
|
+ ax.set_yticks((0, 0.5, 1))
|
|
|
+ ax.plot((0, 1), (0, 1), linestyle='-', color='grey', linewidth=0.35, zorder=-1)
|
|
|
+ ax.spines['left'].set_bounds(0, 1)
|
|
|
+ ax.spines['bottom'].set_bounds(0, 1)
|
|
|
+
|
|
|
+ else:
|
|
|
+ print('No proper analysis selected')
|
|
|
+ return
|
|
|
+
|
|
|
+ # general layout
|
|
|
+ ax.set_ylabel('V1 suppression')
|
|
|
+ ax.yaxis.label.set_color(optocolor)
|
|
|
+ ax.set_xlabel('Control')
|
|
|
+
|
|
|
+ if alys == 'bratio':
|
|
|
+ # plot on log scale
|
|
|
+
|
|
|
+ ax.scatter(cont, supp, s=15, facecolors='none', edgecolors='k', linewidth=0.5)
|
|
|
+ ax.plot(cont_mean, supp_mean, linestyle='', marker='.', color='goldenrod', ms=15)
|
|
|
+ ax.set_yscale('log')
|
|
|
+ ax.set_xscale('log')
|
|
|
+ ax.set_yticks((0.01, 0.1, 1))
|
|
|
+ ax.set_xticks((0.01, 0.1, 1))
|
|
|
+ ax.set_xticklabels((0.01, 0.1, 1))
|
|
|
+ ax.set_yticklabels((0.01, 0.1, 1))
|
|
|
+
|
|
|
+ else:
|
|
|
+ ax.scatter(cont, supp, s=15, facecolors='none', edgecolors='k', linewidth=0.5)
|
|
|
+ ax.plot(cont_mean, supp_mean, linestyle='', marker='.', color='goldenrod', ms=15)
|
|
|
+
|
|
|
+ # plot example neuron
|
|
|
+ ax.plot(ex_cont, ex_supp, linestyle='', marker='.', color='deeppink', ms=15)
|
|
|
+
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|
|
|
+ def resp_differences(self, figsize=(2.5, 2.5), ax=None, alpha=0.4, itnum=1000,
|
|
|
+ sig_alpha=0.05, stepsize=1):
|
|
|
+ """Plots difference between modelled trn responses under V1 suppression and control
|
|
|
+ condition (Fig. 5i)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ alpha: float
|
|
|
+ level of transparency
|
|
|
+ itnum: int
|
|
|
+ number of bootstraps
|
|
|
+ sig_alpha: float
|
|
|
+ significance level
|
|
|
+ stepsize: int
|
|
|
+ stepssize to evalute consecutive difference between conditions
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # init figure
|
|
|
+ f, ax = plt.subplots(figsize=figsize)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ bestexps = self.trn_sztun_opto_df
|
|
|
+
|
|
|
+ # filter out all units that were not fit in opto conditon
|
|
|
+ bestexps = bestexps[bestexps.tun_pars.apply(lambda x: any(~np.isnan(x[1])))]
|
|
|
+ bestexps.reset_index(inplace=True)
|
|
|
+
|
|
|
+ # define eval space
|
|
|
+ x_eval = np.arange(0, 76, 1)
|
|
|
+
|
|
|
+ # init lists to store rog diffs
|
|
|
+ diff_lst = []
|
|
|
+
|
|
|
+ for bestexp in bestexps.itertuples():
|
|
|
+ # loop over units
|
|
|
+
|
|
|
+ # evaluate rog models for both conditions
|
|
|
+ cont_fr = spatint_utils.rog_offset(x_eval, *bestexp.tun_pars[0])
|
|
|
+ opto_fr = spatint_utils.rog_offset(x_eval, *bestexp.tun_pars[1])
|
|
|
+
|
|
|
+ # normalize by max fr in control
|
|
|
+ cont_max = np.max(cont_fr)
|
|
|
+ cont_fr_norm = cont_fr / cont_max
|
|
|
+ opto_fr_norm = opto_fr / cont_max
|
|
|
+
|
|
|
+ # calculate difference
|
|
|
+ diff_fr = opto_fr_norm - cont_fr_norm
|
|
|
+
|
|
|
+ # collect fr diffs in list
|
|
|
+ diff_lst.append(diff_fr)
|
|
|
+
|
|
|
+ # plot single trace
|
|
|
+ ax.plot(x_eval, diff_fr, color='grey', alpha=alpha, linewidth=0.5)
|
|
|
+
|
|
|
+ # print number of plotted curves
|
|
|
+ print('plotted n = %d individual curves' % len(bestexps))
|
|
|
+
|
|
|
+ # plot mean diff_fr
|
|
|
+ cent_diff = np.mean(np.array(diff_lst), axis=0)
|
|
|
+ ax.plot(x_eval[0::stepsize], cent_diff[0::stepsize], linestyle='-', color='k')
|
|
|
+
|
|
|
+ # bootstrap rog diffs
|
|
|
+ # init list to collect bootstrapped diffs
|
|
|
+ bootdiffs_lst = []
|
|
|
+ # set seed
|
|
|
+ np.random.seed(0)
|
|
|
+ for _ in range(itnum):
|
|
|
+ # bootstrap rog differences
|
|
|
+ # get random indices
|
|
|
+ randidc = np.array(
|
|
|
+ [np.random.randint(0, len(diff_lst)) for x in range(len(diff_lst))])
|
|
|
+ # get random diffs
|
|
|
+ bootdiffs = [diff_lst[randidx] for randidx in randidc]
|
|
|
+ # take diffs between consecutive size steps
|
|
|
+ bootchanges = np.diff(np.array(bootdiffs), axis=1)
|
|
|
+ # compute mean
|
|
|
+ bootcent = np.mean(bootchanges, axis=0)
|
|
|
+ # only get each xth value
|
|
|
+ bootcent = bootcent[0::stepsize]
|
|
|
+ # take diff and store in list
|
|
|
+ bootdiffs_lst.append(bootcent)
|
|
|
+
|
|
|
+ # convert boot diff list to array
|
|
|
+ bootdiffs_arr = np.vstack(bootdiffs_lst)
|
|
|
+ # init list to store ci percentils for each point
|
|
|
+ percentils = []
|
|
|
+ for steps in range(bootdiffs_arr.shape[1]):
|
|
|
+ # loop over each sizestep and get 95% ci
|
|
|
+ percentils.append([np.percentile(bootdiffs_arr[:, steps], sig_alpha * 100 / 2),
|
|
|
+ np.percentile(bootdiffs_arr[:, steps],
|
|
|
+ 100 - (sig_alpha * 100 / 2))])
|
|
|
+ # check if slope is sig different from 0
|
|
|
+ sig_inc = [percentil[0] < percentil[1] < 0 for percentil in percentils]
|
|
|
+ sig_dec = [percentil[1] > percentil[0] > 0 for percentil in percentils]
|
|
|
+
|
|
|
+ # print report
|
|
|
+ nstimincreases = len(sig_inc)
|
|
|
+ nsig_dec = np.sum(sig_dec)
|
|
|
+ nsig_inc = np.sum(sig_inc)
|
|
|
+
|
|
|
+ # get indices of sig dec/inc
|
|
|
+ sig_dec_idc = np.where(sig_dec)[0]
|
|
|
+ sig_inc_idc = np.where(sig_inc)[0]
|
|
|
+
|
|
|
+ print(
|
|
|
+ '%d out of %d increases in diameter led to a significant increase in response'
|
|
|
+ ' reduction' % (nsig_inc, nstimincreases))
|
|
|
+ print(
|
|
|
+ '%d out of %d increases in diameter led to a significant decrease in response'
|
|
|
+ ' reduction' % (nsig_dec, nstimincreases))
|
|
|
+ print(
|
|
|
+ 'Stim sizes for which reduction was sig greater than for the next smaller size:',
|
|
|
+ sig_inc_idc)
|
|
|
+ print(
|
|
|
+ 'Stim sizes for which reduction was sig smaller than for the next smaller size:',
|
|
|
+ sig_dec_idc)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ ax.set_ylabel('Normalized rate $\Delta$')
|
|
|
+ ax.set_xlabel('Diameter ($\degree$)')
|
|
|
+ ax.plot(x_eval[0::stepsize][sig_inc_idc], cent_diff[0::stepsize][sig_inc_idc],
|
|
|
+ linestyle='-', color='mediumseagreen')
|
|
|
+ ax.plot(x_eval[0::stepsize][sig_dec_idc], cent_diff[0::stepsize][sig_dec_idc],
|
|
|
+ linestyle='-', color='blue')
|
|
|
+ ax.set_yticks((0, -0.5, -1))
|
|
|
+ ax.set_xticks((0, 25, 50, 75))
|
|
|
+ ylims = ax.get_ylim()
|
|
|
+ ax.spines['left'].set_bounds(-1, ylims[1])
|
|
|
+ ax.spines['bottom'].set_bounds(0, 75)
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|
|
|
+ def threshlin_ex(self, figsize=(2.5, 2.5), ax=None):
|
|
|
+ """Plot threshold linear model fit to repsonses of example neuron (Fig. 5j)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # init figure
|
|
|
+ f, ax = plt.subplots(figsize=figsize)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ bestexps = self.trn_sztun_opto_df
|
|
|
+
|
|
|
+ # get data for example neuron
|
|
|
+ ex_cell = bestexps[
|
|
|
+ (bestexps['m'] == self.trn_sztun_opto_ex_dict['m']) &
|
|
|
+ (bestexps['s'] == self.trn_sztun_opto_ex_dict['s']) &
|
|
|
+ (bestexps['e'] == self.trn_sztun_opto_ex_dict['e']) &
|
|
|
+ (bestexps['u'] == self.trn_sztun_opto_ex_dict['u'])]
|
|
|
+
|
|
|
+ # evaluate rog models
|
|
|
+ xeval = range(76)
|
|
|
+ cont_rog = spatint_utils.rog_offset(xeval, *ex_cell.tun_pars.values[0][0])
|
|
|
+ opto_rog = spatint_utils.rog_offset(xeval, *ex_cell.tun_pars.values[0][1])
|
|
|
+
|
|
|
+ # normalize to max in control
|
|
|
+ cont_norm = cont_rog / np.nanmax(cont_rog)
|
|
|
+ opto_norm = opto_rog / np.nanmax(cont_rog)
|
|
|
+
|
|
|
+ # define start parameters for threshlin fit
|
|
|
+ startparams = (1, 0)
|
|
|
+
|
|
|
+ # fit threshold linear model
|
|
|
+ tlparams, _ = curve_fit(spatint_utils.threshlin, cont_norm, opto_norm, p0=startparams)
|
|
|
+
|
|
|
+ # evaluate threshlin model
|
|
|
+ xeval = np.arange(0, 1.01, 0.01)
|
|
|
+ tl_fit = spatint_utils.threshlin(xeval, *tlparams)
|
|
|
+
|
|
|
+ # compute threshold
|
|
|
+ thres = (tlparams[1] * -1) / tlparams[0]
|
|
|
+
|
|
|
+ # plot
|
|
|
+ ax.plot((0, 1), (0, 1), linestyle='-', color='grey', zorder=-1, linewidth=0.35)
|
|
|
+ ax.scatter(cont_norm, opto_norm, edgecolors='k', facecolors='none', linewidth=0.5)
|
|
|
+ ax.plot(xeval, tl_fit, color=optocolor)
|
|
|
+ ax.plot((thres, thres), (-0.2, 0.75), '--', color='k', clip_on=False, linewidth=0.5)
|
|
|
+
|
|
|
+ # label threshold for panel
|
|
|
+ ax.text(thres - 0.2, 0.8, s='Threshold', fontsize=4)
|
|
|
+
|
|
|
+ # layout
|
|
|
+ ax.set_xlim((-0.05, 1))
|
|
|
+ ax.set_ylim((-0.05, 1))
|
|
|
+ ax.set_xticks((0, 0.5, 1))
|
|
|
+ ax.set_yticks((0, 0.5, 1))
|
|
|
+ ax.spines['bottom'].set_bounds(0, 1)
|
|
|
+ ax.spines['left'].set_bounds(0, 1)
|
|
|
+ ax.set_ylabel('V1 suppression', color=optocolor)
|
|
|
+ ax.set_xlabel('Control')
|
|
|
+ ax.set_title('Normalized\nRoG-model rates')
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|
|
|
+ def threshlin(self, figsize=(2.5, 2.5), ax=None, rsq_thres=0.8, outlier=1.1,
|
|
|
+ xbounds=(-1, 1), ybounds=(-2, 2)):
|
|
|
+ """Plot threshold and slope parameter for trn population (Fig. 5k)
|
|
|
+
|
|
|
+ Parameters
|
|
|
+ -------
|
|
|
+ figsize: tuple
|
|
|
+ Figure size (width, height)
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ rsq_thres: float
|
|
|
+ Rsquared threshold for threshold linear model
|
|
|
+ outlier: float
|
|
|
+ factor for placing outliers at the boundary of plot
|
|
|
+ xbounds: tuple of len 2
|
|
|
+ plotting bounds for x axis
|
|
|
+ ybounds: tuple of len 2
|
|
|
+ plotting bounds for y axis
|
|
|
+
|
|
|
+ Returns
|
|
|
+ -------
|
|
|
+ ax: mpl axis
|
|
|
+ axis for plot
|
|
|
+ """
|
|
|
+
|
|
|
+ if ax is None:
|
|
|
+ # init figure
|
|
|
+ f, ax = plt.subplots(figsize=figsize)
|
|
|
+
|
|
|
+ # get data
|
|
|
+ bestexps = self.trn_sztun_opto_df
|
|
|
+
|
|
|
+ # filter out all units that were not fit in opto conditon
|
|
|
+ bestexps = bestexps[bestexps.tun_pars.apply(lambda x: any(~np.isnan(x[1])))]
|
|
|
+ bestexps.reset_index(inplace=True)
|
|
|
+
|
|
|
+ # define eval space
|
|
|
+ x_eval = range(76)
|
|
|
+
|
|
|
+ # init list to store xintercepts, slopes, and rsqs
|
|
|
+ xinters = []
|
|
|
+ slopes = []
|
|
|
+ rsqs = []
|
|
|
+ bad_indices = []
|
|
|
+ badfits = 0
|
|
|
+
|
|
|
+ for bestexp in bestexps.itertuples():
|
|
|
+ # loop over units
|
|
|
+
|
|
|
+ # evaluate models
|
|
|
+ cont_rog = spatint_utils.rog_offset(x_eval, *bestexp.tun_pars[0])
|
|
|
+ opto_rog = spatint_utils.rog_offset(x_eval, *bestexp.tun_pars[1])
|
|
|
+
|
|
|
+ # normalize to max in control
|
|
|
+ cont_norm = cont_rog / np.nanmax(cont_rog)
|
|
|
+ opto_norm = opto_rog / np.nanmax(cont_rog)
|
|
|
+
|
|
|
+ # define start parameters for threshlin fit
|
|
|
+ startparams = (1, 0)
|
|
|
+
|
|
|
+ # fit threshold linear model
|
|
|
+ tlparams, _ = curve_fit(spatint_utils.threshlin, cont_norm, opto_norm,
|
|
|
+ p0=startparams)
|
|
|
+
|
|
|
+ # get rsq for fit
|
|
|
+ rsq = spatint_utils.rsquared(opto_norm, spatint_utils.threshlin(cont_norm,
|
|
|
+ *tlparams))
|
|
|
+
|
|
|
+ # compute and store xintercept and slope
|
|
|
+ if rsq < rsq_thres:
|
|
|
+ # nan if fit below threshold
|
|
|
+ badfits += 1
|
|
|
+ xinters.append(np.nan)
|
|
|
+ slopes.append(np.nan)
|
|
|
+ rsqs.append(np.nan)
|
|
|
+ bad_indices.append(bestexp.index)
|
|
|
+
|
|
|
+ else:
|
|
|
+ xinter = -tlparams[1] / tlparams[0]
|
|
|
+ xinters.append(xinter)
|
|
|
+ slopes.append(tlparams[0])
|
|
|
+ rsqs.append(rsq)
|
|
|
+
|
|
|
+ # transform lists to arrays
|
|
|
+ xinters = np.array(xinters)
|
|
|
+ slopes = np.array(slopes)
|
|
|
+ rsqs = np.array(rsqs)
|
|
|
+
|
|
|
+ # get index of example key for plotting
|
|
|
+ ex_row = bestexps[
|
|
|
+ (bestexps['m'] == self.trn_sztun_opto_ex_dict['m']) &
|
|
|
+ (bestexps['s'] == self.trn_sztun_opto_ex_dict['s']) &
|
|
|
+ (bestexps['e'] == self.trn_sztun_opto_ex_dict['e']) &
|
|
|
+ (bestexps['u'] == self.trn_sztun_opto_ex_dict['u'])
|
|
|
+ ].index.values
|
|
|
+ all_indic = bestexps.index.values
|
|
|
+ ex_index = np.where(ex_row == all_indic)[0]
|
|
|
+ ex_slope = slopes[ex_index]
|
|
|
+ ex_xinter = xinters[ex_index]
|
|
|
+ ex_rsq = rsqs[ex_index]
|
|
|
+ print('threshold linear paramters example cell \n'
|
|
|
+ 'slope: %0.3f \n'
|
|
|
+ 'threshold: %0.3f \n'
|
|
|
+ 'rsq: %0.3f \n'
|
|
|
+ % (ex_slope, ex_xinter, ex_rsq))
|
|
|
+
|
|
|
+ # remove nans
|
|
|
+ xinters = xinters[~np.isnan(xinters)]
|
|
|
+ slopes = slopes[~np.isnan(slopes)]
|
|
|
+
|
|
|
+ # convert to log2
|
|
|
+ slopes_log2 = np.log2(slopes)
|
|
|
+
|
|
|
+ # compute stats
|
|
|
+ nsamples = len(slopes_log2)
|
|
|
+ W_xint, p_xint = stats.wilcoxon(xinters)
|
|
|
+ W_slopes, p_slopes = stats.wilcoxon(slopes_log2)
|
|
|
+ mean_slopes = 2 ** np.mean(slopes_log2)
|
|
|
+ mean_xint = np.mean(xinters)
|
|
|
+ sem_xint = stats.sem(xinters)
|
|
|
+ sem_slopes = 2 ** stats.sem(slopes_log2)
|
|
|
+
|
|
|
+ print('population stats: \n'
|
|
|
+ 'n = %d \n'
|
|
|
+ 'threshold mean +- sem = %.3f +- %.3f \n'
|
|
|
+ 'W_thres = %0.3f \n'
|
|
|
+ 'p_thres = %0.3f \n'
|
|
|
+ 'slope mean +- sem = %.3f +- %.3f \n'
|
|
|
+ 'W_slope = %0.3f \n'
|
|
|
+ 'p_slope = 10 ** %0.3f \n'
|
|
|
+
|
|
|
+ % (nsamples, mean_xint, sem_xint, W_xint, p_xint, mean_slopes, sem_slopes,
|
|
|
+ W_slopes, np.log10(p_slopes)))
|
|
|
+
|
|
|
+ # move outlier
|
|
|
+ xinters_plot = np.copy(xinters)
|
|
|
+ slopes_plot = np.copy(slopes_log2)
|
|
|
+ slopes_plot[np.where(slopes_plot > ybounds[1])[0]] = ybounds[1] * outlier
|
|
|
+ slopes_plot[np.where(slopes_plot < ybounds[0])[0]] = ybounds[0] * outlier
|
|
|
+
|
|
|
+ # plot
|
|
|
+ ax.plot(xbounds, [0, 0], linestyle='-', color='grey', zorder=-1, linewidth=0.35, ms=15)
|
|
|
+ ax.plot([0, 0], ybounds, linestyle='-', color='grey', zorder=-1, linewidth=0.35, ms=15)
|
|
|
+ # all datapoints
|
|
|
+ ax.scatter(xinters_plot, slopes_plot, facecolors='none', edgecolors='k', s=15,
|
|
|
+ linewidth=0.5)
|
|
|
+ ax.plot(np.mean(xinters), np.mean(slopes_log2), '.', color='goldenrod', ms=15) # mean
|
|
|
+ ax.plot(ex_xinter, np.log2(ex_slope), marker='.', color='deeppink', ms=15) # example
|
|
|
+
|
|
|
+ # layout
|
|
|
+ ax.set_yticks([ybounds[0] * outlier, ybounds[0] / 2, 0, ybounds[1] / 2,
|
|
|
+ ybounds[1] * outlier])
|
|
|
+ ax.set_xticks([-1, 0, 1])
|
|
|
+ ax.set_yticklabels([('<%.2f' % 2 ** ybounds[0]), 2 ** (ybounds[0] / 2), 2 ** 0,
|
|
|
+ int(2 ** (ybounds[1] / 2)), ('>%d' % 2 ** ybounds[1])])
|
|
|
+ ax.spines['left'].set_bounds(ybounds[0], ybounds[1])
|
|
|
+ ax.spines['bottom'].set_bounds(xbounds[0], xbounds[1])
|
|
|
+ ax.set_ylabel('Slope')
|
|
|
+ ax.set_xlabel('Threshold')
|
|
|
+ f = plt.gcf()
|
|
|
+ f.tight_layout()
|
|
|
+
|
|
|
+ return ax
|
|
|
+
|