OSCR

Spatial Adaptation of Primate Retinal Ganglion Cells Between Artificial and Natural Stimuli.

Code ↔ Paper

6 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 6 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Methods › Training ↔ training/cross_stimulus_training.py, the whole file · a weak match · score 0.67 · ReduceLROnPlateau, learning rate scheduler, Adam, patience, epochs, optimizer
  2. [2] § Methods › Training ↔ ln_model_factorized_marmoset_nm.py, lines 272–350 · score 0.66 · ReduceLROnPlateau, learning rate scheduler, Adam, patience, epochs, optimizer
  3. [3] § Methods › Power spectra calculation ↔ notebooks/adaptation_paper_figures.ipynb, lines 848–884 · score 0.61 · Fourier Transform, frequency components, FFT, spectra, filtered
  4. [4] § Methods › Receptive field size and surround amplitude estimation ↔ notebooks/atick_redlich.ipynb, lines 89–194 · score 0.57 · whitening filter, NM spectra, styles, Power, noise, midget
  5. [5] § Methods › Data acquisition and stimuli ↔ datasets/natural_stimuli/create_data.py, lines 148–205 · score 0.54 · refresh rate, movements, naturalistic stimuli, pixels
  6. [6] § Methods › Receptive field size and surround amplitude estimation ↔ notebooks/adaptation_paper_figures.ipynb, lines 276–347 · score 0.53 · natural movies, white noise, boxes, styles, parasol, midget

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 1,089 lines · 51 KB · no license · 2 matches

  1. # %%
  2. %load_ext autoreload
  3. # %%
  4. %autoreload
  5. # %%
  6. import matplotlib.pyplot as plt
  7. import numpy as np
  8. import torch
  9. import seaborn as sns
  10. import pickle
  11. import pandas as pd
  12. import matplotlib
  13. from evaluations.sizes_estimations import handle_filter, get_filter_sign, fit_dog, center_filter
  14. from scipy.signal import find_peaks
  15. from tqdm import tqdm
  16. from scipy.stats import wilcoxon
  17. from scipy.stats import binned_statistic
  18. # %% [markdown]
  19. # ### Cell types
  20. # %% [markdown]
  21. # | | **SN** | **SS** |
  22. # | ------------| ------ | ------ |
  23. # | Midget OFF | 0 | 0 |
  24. # | Parasol OFF | 1 | 1,10 |
  25. # | Large OFF | 2,3,6 | 2 |
  26. # | Midget ON | 29 | 11 |
  27. # | Parasol ON | 28 | 9 |
  28. # %%
  29. cell_types_r1 = np.load('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/SN_cell_classification.npy')
  30. cell_types_r2 = np.load('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/SS_cell_classification.npy')
  31. cell_types_r4 = np.load('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/ON_DS_cell_classification.npy')
  32. # %%
  33. def translate_cell_types(cell_types, translation_dict):
  34. for i in range(cell_types.shape[0]):
  35. if cell_types[i][1].item() in translation_dict.keys():
  36. cell_types[i, 3] = int(translation_dict[cell_types[i][1].item()])
  37. print(f'specific type: {cell_types[i][1].item()} general type: {translation_dict[cell_types[i][1].item()]}')
  38. else:
  39. cell_types[i, 3] = cell_types[i][1]
  40. return cell_types
  41. # %%
  42. r1_translation_dict = {0:0, 2:2, 3:2, 4:24, 6:2, 29:3, 28:4}
  43. r4_translation_dict = {0:1, 1:4, 2:-1, 5:0, 3:-1, 4:-1}
  44. # %%
  45. g_cell_types_r4 = np.zeros((cell_types_r4.shape[0], cell_types_r4.shape[1]+1))
  46. g_cell_types_r4[:, :-1] = cell_types_r4
  47. # %%
  48. g_cell_types_r1 = np.zeros((cell_types_r1.shape[0], cell_types_r1.shape[1]+1))
  49. g_cell_types_r1[:, :-1] = cell_types_r1
  50. # %%
  51. cell_types_r1 = translate_cell_types(g_cell_types_r1, r1_translation_dict)
  52. # %%
  53. cell_types_r4 = translate_cell_types(g_cell_types_r4, r4_translation_dict)
  54. # %% [markdown]
  55. # # Loading performances
  56. #
  57. # #### Rank 1 LN model
  58. # %%
  59. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/test_correlations_r1_r2_r4.pkl', 'rb') as file:
  60. performances = pickle.load(file)
  61. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/nm_for_wn_test_correlations_r1_r4_finetuned_temp.pkl', 'rb') as f:
  62. nm_for_wn_performances = pickle.load(f)
  63. # %%
  64. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/nm_test_correlations_r1_r2_r4.pkl', 'rb') as file:
  65. nm_performances = pickle.load(file)
  66. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/wn_for_nm_test_correlations_r1_r4_finetuned_temp.pkl', 'rb') as f:
  67. wn_for_nm_performances = pickle.load(f)
  68. # %% [markdown]
  69. # #### Rank 2 LN model
  70. # %%
  71. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/cs_nm_test_correlations_r1_r4.pkl', 'rb') as file:
  72. cs_nm_performances = pickle.load(file)
  73. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/cs_nm_for_wn_test_correlations_r1_r4_finetuned_temp.pkl', 'rb') as file:
  74. cs_nm_for_wn_performances = pickle.load(file)
  75. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/cs_wn_test_correlations_r1_r4.pkl', 'rb') as file:
  76. cs_wn_performances = pickle.load(file)
  77. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/ln_model_performances/cs_wn_for_nm_test_correlations_r1_r4_finetuned_temp.pkl', 'rb') as file:
  78. cs_wn_for_nm_performances = pickle.load(file)
  79. # %%
  80. df = pd.DataFrame()
  81. for retina, r_dict in performances.items():
  82. if retina == '01':
  83. cell_types = cell_types_r1
  84. elif retina == '04':
  85. cell_types = cell_types_r4
  86. elif retina == '02':
  87. cell_types = None
  88. for cell, performance in r_dict.items():
  89. if cell in nm_performances[retina].keys():
  90. row = {'retina': retina, 'cell_name': cell,
  91. 'wn_cc': performance,
  92. 'nm_cc': nm_performances[retina][cell],
  93. 'wn_for_nm_cc': wn_for_nm_performances[retina][cell] if retina != '02' else None,
  94. "nm_for_wn_cc": nm_for_wn_performances[retina][cell] if retina != '02' else None,
  95. 'cs_wn_cc': cs_wn_performances[retina][cell] if retina != '02' else None,
  96. 'cs_nm_cc': cs_nm_performances[retina][cell] if retina != '02' else None,
  97. 'cs_wn_for_nm_cc': cs_wn_for_nm_performances[retina][cell] if retina != '02' else None,
  98. "cs_nm_for_wn_cc": cs_nm_for_wn_performances[retina][cell] if retina != '02' else None,
  99. 'cell_class_r': None if cell_types is None else int(cell_types[cell][1]), 'cell_class_g': None if cell_types is None else int(cell_types[cell][3])}
  100. row_df = pd.DataFrame([row])
  101. df = pd.concat([df, row_df])
  102. df = df.reset_index()
  103. # %% [markdown]
  104. # # Learned filters
  105. # %%
  106. class Filter:
  107. def __init__(self, f, kind='spat'):
  108. self.f = f
  109. self.type = kind
  110. # %%
  111. def add_filters(retina, filter_dict, df, spat_col, temp_col, center_col_x, center_col_y):
  112. if spat_col not in df.columns:
  113. df[spat_col] = pd.Series(dtype='object')
  114. df[temp_col] = pd.Series(dtype='object')
  115. for key in filter_dict.keys():
  116. spat_filter = filter_dict[key][0]
  117. if center_col_x is not None:
  118. x = df[(df['retina'] == retina) & (df['cell_name'] == key)][center_col_x].tolist()
  119. y = df[(df['retina'] == retina) & (df['cell_name'] == key)][center_col_y].tolist()
  120. if len(x) > 0:
  121. x = x[0]
  122. y = y[0]
  123. else:
  124. continue
  125. spat_filter = center_filter(spat_filter, mu=(x, y))
  126. spat = Filter(spat_filter, kind='spat')
  127. temp = Filter(filter_dict[key][1], kind='temp')
  128. df.loc[(df['retina'] == retina) & (df['cell_name'] == key), spat_col] = spat
  129. df.loc[(df['retina'] == retina) & (df['cell_name'] == key), temp_col] = temp
  130. def add_size_or_amp(retina, value_dict, df, size_col):
  131. for key in value_dict.keys():
  132. f = value_dict[key]
  133. df.loc[(df['retina'] == retina) & (df['cell_name'] == key),size_col] = f
  134. # %%
  135. def temp_sizes_for_cells(filter_dict, plot=False):
  136. s, a = {}, {}
  137. for key in tqdm(filter_dict.keys()):
  138. s[key], a[key] = handle_filter(filter_dict[key][0].reshape((15,15)), plot=plot)
  139. # t_s[key], t_a[key] = handle_temp_filter(filter_dict[key][1])
  140. return s, a
  141. # %%
  142. def spatial_info(filter_dict, plot=False):
  143. s, a, c = {}, {}, {}
  144. for key in filter_dict.keys():
  145. s[key], a[key], c[key] = handle_filter(filter_dict[key][0].reshape((15,15)), plot=plot)
  146. return s, a, c
  147. # %% [markdown]
  148. # ### Loading saved rank 1 LN model weights
  149. #
  150. # #### White noise filters
  151. # %%
  152. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/sc_wn_r1_v2', 'rb') as file:
  153. wn_sc_r1 = pickle.load(file)
  154. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/sc_wn_r4_v2', 'rb') as file:
  155. wn_sc_r4 = pickle.load(file)
  156. # %% [markdown]
  157. # #### Natural movie filters
  158. # %%
  159. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/sc_nm_r1_v2', 'rb') as file:
  160. nm_sc_r1 = pickle.load(file)
  161. with open('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/benchmark_paper/results/sc_nm_r4_v2', 'rb') as file:
  162. nm_sc_r4 = pickle.load(file)
  163. # %% [markdown]
  164. # #### Adding sizes and amplitudes to DataFrame
  165. # %%
  166. s, a, c = spatial_info(wn_sc_r1, plot=True)
  167. c_x = {key: np.round(v[0]) for key, v in c.items()}
  168. c_y = {key: np.round(v[1]) for key, v in c.items()}
  169. add_size_or_amp(retina='01', value_dict=s, df=df, size_col='wn_size')
  170. add_size_or_amp(retina='01', value_dict=a, df=df, size_col='wn_amp')
  171. add_size_or_amp(retina='01', value_dict=c_x, df=df, size_col='wn_center_x')
  172. add_size_or_amp(retina='01', value_dict=c_y, df=df, size_col='wn_center_y')
  173. s, a, c = spatial_info(wn_sc_r4, plot=True)
  174. c_x = {key: np.round(v[0]) for key, v in c.items()}
  175. c_y = {key: np.round(v[1]) for key, v in c.items()}
  176. add_size_or_amp(retina='04', value_dict=s, df=df, size_col='wn_size')
  177. add_size_or_amp(retina='04', value_dict=a, df=df, size_col='wn_amp')
  178. add_size_or_amp(retina='04', value_dict=c_x, df=df, size_col='wn_center_x')
  179. add_size_or_amp(retina='04', value_dict=c_y, df=df, size_col='wn_center_y')
  180. # %%
  181. s, a,c = spatial_info(nm_sc_r1, plot=True)
  182. c_x = {key: np.round(v[0]) for key, v in c.items()}
  183. c_y = {key: np.round(v[1]) for key, v in c.items()}
  184. add_size_or_amp(retina='01', value_dict=s, df=df, size_col='nm_size')
  185. add_size_or_amp(retina='01', value_dict=a, df=df, size_col='nm_amp')
  186. add_size_or_amp(retina='01', value_dict=c_x, df=df, size_col='nm_center_x')
  187. add_size_or_amp(retina='01', value_dict=c_y, df=df, size_col='nm_center_y')
  188. s, a, c = spatial_info(nm_sc_r4, plot=True)
  189. c_x = {key: np.round(v[0]) for key, v in c.items()}
  190. c_y = {key: np.round(v[1]) for key, v in c.items()}
  191. add_size_or_amp(retina='04', value_dict=s, df=df, size_col='nm_size')
  192. add_size_or_amp(retina='04', value_dict=a, df=df, size_col='nm_amp')
  193. add_size_or_amp(retina='04', value_dict=c_x, df=df, size_col='nm_center_x')
  194. add_size_or_amp(retina='04', value_dict=c_y, df=df, size_col='nm_center_y')
  195. # %%
  196. df
  197. # %% [markdown]
  198. # #### Adding actual filters to DataFrame
  199. # %%
  200. add_filters(retina='01', filter_dict= nm_sc_r1, df=df, spat_col='nm_spat', temp_col='nm_temp', center_col_x='nm_center_x', center_col_y='nm_center_y')
  201. add_filters(retina='04', filter_dict= nm_sc_r4, df=df, spat_col='nm_spat', temp_col='nm_temp', center_col_x='nm_center_x', center_col_y='nm_center_y')
  202. # %%
  203. add_filters(retina='01', filter_dict= wn_sc_r1, df=df, spat_col='wn_spat', temp_col='wn_temp', center_col_x='wn_center_x', center_col_y='wn_center_y')
  204. add_filters(retina='04', filter_dict= wn_sc_r4, df=df, spat_col='wn_spat', temp_col='wn_temp', center_col_x='wn_center_x', center_col_y='wn_center_y')
  205. # %%
  206. df = df.reset_index()
  207. # %%
  208. colors = ['#F25764', '#FCC9BD', '#39B4E4', '#0871CD', '#011126']
  209. light_colors = ['#FF5C69', '#DFB2A7', '#39B3E3', '#0871CC']
  210. dark_colors = ['gray']*4
  211. # %% [markdown]
  212. # # Performance figure
  213. # %%
  214. from matplotlib.patches import Patch
  215. def plot_boxplot(cell_values, labels, save_file):
  216. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  217. sns.set_style("whitegrid")
  218. plt.rcParams['lines.linewidth'] = 0.5
  219. plt.rcParams['font.size'] = 8
  220. matplotlib.rcParams['svg.fonttype'] = 'none' # This keeps text as text in SVG files
  221. fig, ax = plt.subplots(figsize=(3., 1.7))
  222. bar_types = ['WN', 'NM', 'WN', 'NM', 'WN', 'NM', 'WN', 'NM']
  223. new_order = ['Midget OFF', 'Parasol OFF', 'Large OFF', 'Parasol ON']
  224. data = None
  225. for i, values in enumerate(cell_values):
  226. for value in values:
  227. if value == 'N/A':
  228. continue
  229. if data is None:
  230. data = pd.DataFrame({'model_type': labels[i], 'correlation': value, 'type': bar_types[i]}, index=[0])
  231. else:
  232. data = pd.concat([data, pd.DataFrame({'model_type': labels[i], 'correlation': value, 'type': bar_types[i]}, index=[0])], ignore_index= True)
  233. data['Group'] = data['type'] + ' ' + data['model_type']
  234. data['Hue'] = list(zip(data['model_type'], data['type']))
  235. data['model_type'] = data['model_type'].astype('category')
  236. palette = colors
  237. flierprops = dict(marker='o', markerfacecolor='silver', markeredgecolor='None', markersize=3, linestyle='none')
  238. box = sns.boxplot(x='model_type', y='correlation', data=data, hue='type', palette=palette, dodge=True, order=new_order, flierprops=flierprops)
  239. sns.stripplot(x='model_type', y='correlation', data=data, hue='type', size=1, color="black", dodge=True, label='_nolegend_', order=new_order)
  240. for i, artist in enumerate(ax.artists):
  241. # Fully filled boxes for the wn value in each pair
  242. if i % 2 == 0:
  243. artist.set_facecolor(palette[i//2])
  244. artist.set_edgecolor('black')
  245. else:
  246. # Striped boxes for the nm value in each pair
  247. artist.set_facecolor(palette[i//2])
  248. artist.set_hatch('/////')
  249. artist.set_linewidth(0.5)
  250. artist.set_edgecolor('black')
  251. ax.grid(False)
  252. ax.spines['top'].set_visible(False)
  253. ax.spines['right'].set_visible(False)
  254. ax.spines['left'].set_color('black')
  255. ax.spines['bottom'].set_color('black')
  256. ax.tick_params(bottom=True, top=False, left=True, right=False)
  257. legend_patches = [
  258. Patch(facecolor='white', edgecolor='black', label='White noise'),
  259. Patch(facecolor='white', edgecolor='black', hatch='/////', label='Natural movies')
  260. ]
  261. plt.legend(handles=legend_patches)
  262. sns.despine(offset=5)
  263. if save_file is not None:
  264. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg',
  265. transparent=True, bbox_inches='tight', dpi=300)
  266. plt.show()
  267. return data
  268. # %% [markdown]
  269. # ### Loading rank-1 model cell-type specific performances
  270. # %%
  271. wn_midget_off = df[(df['cell_class_g'] == 0)]['wn_cc'].tolist()
  272. wn_parasol_off = df[(df['cell_class_g'] == 1)]['wn_cc'].tolist()
  273. wn_large_off = df[(df['cell_class_g'] == 2)]['wn_cc'].tolist()
  274. wn_midget_on = df[(df['cell_class_g'] == 3)]['wn_cc'].tolist()
  275. wn_parasol_on = df[(df['cell_class_g'] == 4)]['wn_cc'].tolist()
  276. nm_midget_off = df[(df['cell_class_g'] == 0)]['nm_cc'].tolist()
  277. nm_parasol_off = df[(df['cell_class_g'] == 1)]['nm_cc'].tolist()
  278. nm_large_off = df[(df['cell_class_g'] == 2)]['nm_cc'].tolist()
  279. nm_midget_on = df[(df['cell_class_g'] == 3)]['nm_cc'].tolist()
  280. nm_parasol_on = df[(df['cell_class_g'] == 4)]['nm_cc'].tolist()
  281. wn_for_nm_midget_off = df[(df['cell_class_g'] == 0)]['wn_for_nm_cc'].tolist()
  282. wn_for_nm_parasol_off = df[(df['cell_class_g'] == 1)]['wn_for_nm_cc'].tolist()
  283. wn_for_nm_large_off = df[(df['cell_class_g'] == 2)]['wn_for_nm_cc'].tolist()
  284. wn_for_nm_midget_on = df[(df['cell_class_g'] == 3)]['wn_for_nm_cc'].tolist()
  285. wn_for_nm_parasol_on = df[(df['cell_class_g'] == 4)]['wn_for_nm_cc'].tolist()
  286. nm_for_wn_midget_off = df[(df['cell_class_g'] == 0)]['nm_for_wn_cc'].tolist()
  287. nm_for_wn_parasol_off = df[(df['cell_class_g'] == 1)]['nm_for_wn_cc'].tolist()
  288. nm_for_wn_large_off = df[(df['cell_class_g'] == 2)]['nm_for_wn_cc'].tolist()
  289. nm_for_wn_midget_on = df[(df['cell_class_g'] == 3)]['nm_for_wn_cc'].tolist()
  290. nm_for_wn_parasol_on = df[(df['cell_class_g'] == 4)]['nm_for_wn_cc'].tolist()
  291. # %% [markdown]
  292. # #### Loading rank-2 cell-specific performances
  293. # %%
  294. cs_wn_midget_off = df[(df['cell_class_g'] == 0)]['cs_wn_cc'].tolist()
  295. cs_wn_parasol_off = df[(df['cell_class_g'] == 1)]['cs_wn_cc'].tolist()
  296. cs_wn_large_off = df[(df['cell_class_g'] == 2)]['cs_wn_cc'].tolist()
  297. cs_wn_midget_on = df[(df['cell_class_g'] == 3)]['cs_wn_cc'].tolist()
  298. cs_wn_parasol_on = df[(df['cell_class_g'] == 4)]['cs_wn_cc'].tolist()
  299. cs_nm_midget_off = df[(df['cell_class_g'] == 0)]['cs_nm_cc'].tolist()
  300. cs_nm_parasol_off = df[(df['cell_class_g'] == 1)]['cs_nm_cc'].tolist()
  301. cs_nm_large_off = df[(df['cell_class_g'] == 2)]['cs_nm_cc'].tolist()
  302. cs_nm_midget_on = df[(df['cell_class_g'] == 3)]['cs_nm_cc'].tolist()
  303. cs_nm_parasol_on = df[(df['cell_class_g'] == 4)]['cs_nm_cc'].tolist()
  304. cs_wn_for_nm_midget_off = df[(df['cell_class_g'] == 0)]['cs_wn_for_nm_cc'].tolist()
  305. cs_wn_for_nm_parasol_off = df[(df['cell_class_g'] == 1)]['cs_wn_for_nm_cc'].tolist()
  306. cs_wn_for_nm_large_off = df[(df['cell_class_g'] == 2)]['cs_wn_for_nm_cc'].tolist()
  307. cs_wn_for_nm_midget_on = df[(df['cell_class_g'] == 3)]['cs_wn_for_nm_cc'].tolist()
  308. cs_wn_for_nm_parasol_on = df[(df['cell_class_g'] == 4)]['cs_wn_for_nm_cc'].tolist()
  309. cs_nm_for_wn_midget_off = df[(df['cell_class_g'] == 0)]['cs_nm_for_wn_cc'].tolist()
  310. cs_nm_for_wn_parasol_off = df[(df['cell_class_g'] == 1)]['cs_nm_for_wn_cc'].tolist()
  311. cs_nm_for_wn_large_off = df[(df['cell_class_g'] == 2)]['cs_nm_for_wn_cc'].tolist()
  312. cs_nm_for_wn_midget_on = df[(df['cell_class_g'] == 3)]['cs_nm_for_wn_cc'].tolist()
  313. cs_nm_for_wn_parasol_on = df[(df['cell_class_g'] == 4)]['cs_nm_for_wn_cc'].tolist()
  314. # %%
  315. data = plot_boxplot([wn_midget_off, nm_midget_off, wn_parasol_off, nm_parasol_off, wn_large_off, nm_large_off, wn_parasol_on, nm_parasol_on],
  316. labels=['Midget OFF', 'Midget OFF', 'Parasol OFF', 'Parasol OFF', 'Large OFF', 'Large OFF', 'Parasol ON', 'Parasol ON'], save_file='ln_perfs_boxplot')
  317. # %%
  318. data = plot_boxplot([cs_wn_midget_off, cs_nm_midget_off, cs_wn_parasol_off, cs_nm_parasol_off, cs_wn_large_off, cs_nm_large_off, cs_wn_parasol_on, cs_nm_parasol_on],
  319. labels=['Midget OFF', 'Midget OFF', 'Parasol OFF', 'Parasol OFF', 'Large OFF', 'Large OFF', 'Parasol ON', 'Parasol ON'], save_file='ln_perfs_boxplot')
  320. # %%
  321. def plot_single_wn_nm(ax, wn, nm, title, color):
  322. ax.grid(False)
  323. ax.spines['top'].set_visible(False)
  324. ax.spines['right'].set_visible(False)
  325. ax.spines['bottom'].set_color('black')
  326. ax.spines['left'].set_color('black')
  327. ax.tick_params(bottom=True, top=False, left=True, right=False)
  328. ax.scatter(nm, wn, facecolor=color, edgecolor='None', s=15, alpha=0.6,)
  329. ax.plot([0,1], [0,1], linestyle='--', color='black', linewidth=0.5)
  330. plt.title(title)
  331. def plot_wn_nm(wn, nm, title, color, save_file=None, xlabel='Correlation NM', ylabel='Correlation WN', xlim=0.15, ylim=0.15,
  332. figsize=(1.7, 1.7)):
  333. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  334. sns.set_style("whitegrid")
  335. plt.rcParams['lines.linewidth'] = 0.5
  336. plt.rcParams['font.size'] = 8
  337. matplotlib.rcParams['svg.fonttype'] = 'none' # This keeps text as text in SVG files
  338. fig, ax = plt.subplots(figsize=figsize)
  339. plot_single_wn_nm(ax, wn, nm, title=title, color=color)
  340. plt.xlabel(xlabel)
  341. plt.ylabel(ylabel)
  342. plt.xlim(xlim)
  343. plt.ylim(ylim)
  344. sns.despine(offset=5)
  345. plt.tight_layout()
  346. if save_file is not None:
  347. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg', dpi=300, transparent=True,
  348. bbox_inches='tight')
  349. plt.show()
  350. def plot_shared_wn_nm(wns, nms, figsize, colors, title, save_file=None):
  351. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  352. sns.set_style("whitegrid")
  353. plt.rcParams['lines.linewidth'] = 0.5
  354. plt.rcParams['font.size'] = 8
  355. matplotlib.rcParams['svg.fonttype'] = 'none' # This keeps text as text in SVG files
  356. fig, ax = plt.subplots(figsize=figsize)
  357. for i, (wn, nm) in enumerate(zip(wns, nms)):
  358. clean_nm = [x for x,y in zip(nm, wn) if x!= 'N/A' and y != 'N/A']
  359. clean_wn = [y for x,y in zip(nm, wn) if x!= 'N/A' and y != 'N/A']
  360. plot_single_wn_nm(ax, clean_wn, clean_nm, color=colors[i], title='')
  361. for i, (wn, nm) in enumerate(zip(wns, nms)):
  362. clean_nm = [x for x,y in zip(nm, wn) if x!= 'N/A' and y != 'N/A']
  363. clean_wn = [y for x,y in zip(nm, wn) if x!= 'N/A' and y != 'N/A']
  364. ax.scatter(np.nanmean(clean_nm), np.nanmean(clean_wn), facecolor=colors[i], marker='X', s=20, edgecolor='black', linewidth=0.5)
  365. plt.xlabel('CC in domain')
  366. plt.ylabel('CC out of domain')
  367. plt.xlim(0.1, 1)
  368. plt.ylim(0.1, 1)
  369. plt.title(title)
  370. if save_file is not None:
  371. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg', dpi=300, transparent=True)
  372. plt.show()
  373. # %%
  374. plot_shared_wn_nm([nm_for_wn_midget_off, nm_for_wn_parasol_off, nm_for_wn_large_off, nm_for_wn_parasol_on], [wn_midget_off, wn_parasol_off, wn_large_off, wn_parasol_on],
  375. (1.5,1.5), colors,
  376. title='Performance on WN', save_file='WN_scatter_plot')
  377. plot_shared_wn_nm([wn_for_nm_midget_off, wn_for_nm_parasol_off, wn_for_nm_large_off, wn_for_nm_parasol_on], [nm_midget_off, nm_parasol_off, nm_large_off, nm_parasol_on],
  378. (1.5,1.5), colors,
  379. title='Performance on NM', save_file='NM_scatter_plot')
  380. # %%
  381. plot_shared_wn_nm([cs_nm_for_wn_midget_off, cs_nm_for_wn_parasol_off, cs_nm_for_wn_large_off, cs_nm_for_wn_parasol_on], [cs_wn_midget_off, cs_wn_parasol_off, cs_wn_large_off, cs_wn_parasol_on],
  382. (1.5,1.5), colors,
  383. title='Performance on WN', save_file='WN_scatter_plot')
  384. plot_shared_wn_nm([cs_wn_for_nm_midget_off, cs_wn_for_nm_parasol_off, cs_wn_for_nm_large_off, cs_wn_for_nm_parasol_on], [cs_nm_midget_off, cs_nm_parasol_off, cs_nm_large_off, cs_nm_parasol_on],
  385. (1.5,1.5), colors,
  386. title='Performance on NM', save_file='NM_scatter_plot')
  387. # %%
  388. plot_wn_nm(wn_midget_off, nm_midget_off, title='Midget OFF', color=colors[0], save_file='scatter_perf_midged_off')
  389. plot_wn_nm(wn_parasol_off, nm_parasol_off, title='Parasol OFF', color=colors[1], save_file='scatter_perf_parasol_off')
  390. plot_wn_nm(wn_large_off, nm_large_off, title='Large OFF', color=colors[2], save_file='scatter_perf_large_off')
  391. plot_wn_nm(wn_parasol_on, nm_parasol_on, title='Parasol ON', color=colors[3], save_file='scatter_perf_parasol_on')
  392. # %%
  393. limit = 0.15
  394. plot_wn_nm(nm=wn_midget_off, wn=nm_for_wn_midget_off, title='Midget OFF', color=colors[0], save_file='wn_vs_nm_for_wn_perf_midged_off',
  395. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  396. figsize=(1.2,1.2))
  397. plot_wn_nm(nm=wn_parasol_off, wn=nm_for_wn_parasol_off, title='Parasol OFF', color=colors[1], save_file='wn_vs_nm_for_wn_perf_parasol_off',
  398. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  399. figsize=(1.2,1.2))
  400. plot_wn_nm(nm=wn_large_off, wn=nm_for_wn_large_off, title='Large OFF', color=colors[2], save_file='wn_vs_nm_for_wn_perf_large_off',
  401. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  402. figsize=(1.2,1.2))
  403. plot_wn_nm(nm=wn_parasol_on, wn=nm_for_wn_parasol_on, title='Parasol ON', color=colors[3], save_file='wn_vs_nm_for_wn_perf_parasol_on',
  404. xlabel='CC in', ylabel='CC out ', xlim=limit, ylim=limit,
  405. figsize=(1.2,1.2))
  406. # %%
  407. plot_wn_nm(nm=nm_midget_off, wn=wn_for_nm_midget_off, title='Midget OFF', color=colors[0], save_file='nm_vs_wn_for_nm_perf_midged_off',
  408. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  409. figsize=(1.2,1.2))
  410. plot_wn_nm(nm=nm_parasol_off, wn=wn_for_nm_parasol_off, title='Parasol OFF', color=colors[1], save_file='nm_vs_wn_for_nm_perf_parasol_off',
  411. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  412. figsize=(1.2,1.2))
  413. plot_wn_nm(nm=nm_large_off, wn=wn_for_nm_large_off, title='Large OFF', color=colors[2], save_file='nm_vs_wn_for_nm_perf_large_off',
  414. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  415. figsize=(1.2,1.2))
  416. plot_wn_nm(nm=nm_parasol_on, wn=wn_for_nm_parasol_on, title='Parasol ON', color=colors[3], save_file='nm_vs_wn_for_nm_perf_parasol_on',
  417. xlabel='CC in', ylabel='CC out', xlim=limit, ylim=limit,
  418. figsize=(1.2,1.2))
  419. # %%
  420. def plot_traces(responses, outputs, start_index, length, color, dark_color=None, title=None, save_file=None):
  421. out_domain = None
  422. if isinstance(responses, list):
  423. out_domain = responses[1]
  424. responses = responses[0]
  425. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  426. sns.set_style("whitegrid")
  427. plt.rcParams['lines.linewidth'] = 0.5
  428. plt.rcParams['font.size'] = 8
  429. matplotlib.rcParams['svg.fonttype'] = 'none'
  430. fig, ax = plt.subplots(figsize=(3.2,0.6))
  431. ax.tick_params(bottom=True, top=False, left=True, right=False)
  432. ax.spines['top'].set_visible(False)
  433. ax.spines['right'].set_visible(False)
  434. ax.spines['bottom'].set_color('black')
  435. ax.spines['left'].set_color('black')
  436. ax.grid(False)
  437. plt.plot(np.linspace(0, length/85, length), outputs[start_index:start_index+length]*85, label='Responses', color='black', linewidth=0.5, linestyle='--')
  438. if out_domain is not None:
  439. plt.plot(np.linspace(0, length/85, length), out_domain[start_index:start_index+length]*85, label='Predictions out of domain', color=dark_color, linewidth=0.5)
  440. plt.plot(np.linspace(0, length/85, length), responses[start_index:start_index+length]*85, label='Predictions' if out_domain is None else 'Predictions in domain', color=color, linewidth=0.5)
  441. plt.xlabel('Time (s)')
  442. plt.ylabel('Hz')
  443. plt.ylim(0,170)
  444. if save_file is not None:
  445. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures//{save_file}.svg', transparent=True, dpi=300, bbox_inches='tight')
  446. plt.show()
  447. def get_outputs_and_responses(path, setting_str=''):
  448. outputs = np.load(f'{path}/{setting_str}test_outputs.npy')
  449. responses = np.load(f'{path}/{setting_str}test_responses.npy')
  450. return outputs, responses
  451. # %%
  452. # midget cell
  453. c_67_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina01/cell_67/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/stats/seed_8/')
  454. c_67_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina01/cell_67/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/stats/seed_8/')
  455. c_67_wn_for_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina01/cell_67/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/wn_for_nm/ff_0_pn_1/stats/seed_8/',
  456. setting_str='wn_for_nm_')
  457. c_67_nm_for_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina01/cell_67/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/nm_for_wn/ff_0_pn_1/stats/seed_8/',
  458. setting_str='nm_for_wn_')
  459. # parasol off cell
  460. c_93_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina04/cell_93/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/stats/seed_8/')
  461. c_93_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina04/cell_93/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/stats/seed_8/')
  462. c_93_wn_for_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina04/cell_93/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/wn_for_nm/ff_0_pn_1/stats/seed_8/',
  463. setting_str='wn_for_nm_')
  464. c_93_nm_for_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina04/cell_93/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/nm_for_wn/ff_0_pn_1/stats/seed_8/',
  465. setting_str='nm_for_wn_')
  466. # large off cell
  467. c_294_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina01/cell_294/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0//stats/seed_8/')
  468. c_294_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina01/cell_294/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/stats/seed_8/')
  469. c_294_wn_for_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina01/cell_294/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/wn_for_nm/ff_0_pn_1/stats/seed_8/',
  470. setting_str='wn_for_nm_')
  471. c_294_nm_for_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina01/cell_294/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/nm_for_wn/ff_0_pn_1/stats/seed_8/',
  472. setting_str='nm_for_wn_')
  473. # parason ON cell
  474. c_2_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina04/cell_2/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/stats/seed_8/')
  475. c_2_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina04/cell_2/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/stats/seed_8/')
  476. c_2_wn_for_nm = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized__ete/marmoset/retina04/cell_2/lr_0.005_whole_rf_15_ch_30_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_fn_1_do_n_1_dog_0/wn_for_nm/ff_0_pn_1/stats/seed_8/',
  477. setting_str='wn_for_nm_')
  478. c_2_nm_for_wn = get_outputs_and_responses('/usr/users/vystrcilova/retinal_circuit_modeling/models/ln_models/ln_models_factorized_4_ete_nm/marmoset/retina04/cell_2/lr_0.005_whole_rf_15_ch_30_l1_0.0_l2_0.0_g_0.0_bs_32_tr_250_s_1_c_0_n_0_fn_1_do_n_1_flipped_gf_0_dog_0/nm_for_wn/ff_0_pn_1/stats/seed_8/',
  479. setting_str='nm_for_wn_')
  480. # %%
  481. plot_traces([c_67_nm[0], c_67_wn_for_nm[0]], c_67_nm[1], start_index=960, length=100, color=colors[0], dark_color=dark_colors[0], save_file='NM_Midget_OFF')
  482. plot_traces([c_67_wn[0], c_67_nm_for_wn[0]], c_67_wn[1], start_index=100, length=100, color=colors[0], dark_color=dark_colors[0], save_file='WN_Midget_OFF')
  483. plot_traces([c_93_nm[0], c_93_nm_for_wn[0]], c_93_nm[1], start_index=400, length=100, color=colors[1], dark_color=dark_colors[1], save_file='NM_Parasol_OFF')
  484. plot_traces([c_93_wn[0], c_93_wn_for_nm[0]], c_93_wn[1], start_index=200, length=100, color=colors[1], dark_color=dark_colors[1], save_file='WN_Parasol_OFF')
  485. plot_traces([c_294_nm[0], c_294_wn_for_nm[0]], c_294_nm[1], start_index=930, length=100, color=colors[2], dark_color=dark_colors[2], save_file='NM_Large_OFF')
  486. plot_traces([c_294_wn[0], c_294_nm_for_wn[0]], c_294_wn[1], start_index=420, length=100, color=colors[2], dark_color=dark_colors[2], save_file='WN_Large_OFF')
  487. plot_traces([c_2_nm[0], c_2_wn_for_nm[0]], c_2_nm[1], start_index=820, length=100, color=colors[3], dark_color=dark_colors[3], save_file='NM_Parasol_ON')
  488. plot_traces([c_2_wn[0], c_2_nm_for_wn[0]], c_2_wn[1], start_index=100, length=100, color=colors[3], dark_color=dark_colors[3], save_file='WN_Parasol_ON')
  489. # %% [markdown]
  490. # #### Calculating mean cell specific performances
  491. # %%
  492. print("WN")
  493. print('midget off', np.mean(wn_midget_off), np.std(wn_midget_off))
  494. print('parasol off', np.mean(wn_parasol_off), np.std(wn_parasol_off))
  495. print('large off', np.mean(wn_large_off), np.std(wn_large_off))
  496. print('parasol on', np.mean(wn_parasol_on), np.std(wn_parasol_on))
  497. print("NM")
  498. print('midget off', np.mean(nm_midget_off), np.std(nm_midget_off))
  499. print('parasol off', np.mean(nm_parasol_off), np.std(nm_parasol_off))
  500. print('large off', np.mean(nm_large_off), np.std(nm_large_off))
  501. print('parasol on', np.mean(nm_parasol_on), np.std(nm_parasol_on))
  502. # %%
  503. print("WN")
  504. print('midget off', np.mean(cs_wn_midget_off), np.std(cs_wn_midget_off))
  505. print('parasol off', np.mean(cs_wn_parasol_off), np.std(cs_wn_parasol_off))
  506. print('large off', np.mean(cs_wn_large_off), np.std(cs_wn_large_off))
  507. print('parasol on', np.mean(cs_wn_parasol_on), np.std(cs_wn_parasol_on))
  508. print("NM")
  509. print('midget off', np.mean(cs_nm_midget_off), np.std(cs_nm_midget_off))
  510. print('parasol off', np.mean(cs_nm_parasol_off), np.std(cs_nm_parasol_off))
  511. print('large off', np.mean(cs_nm_large_off), np.std(cs_nm_large_off))
  512. print('parasol on', np.mean(cs_nm_parasol_on), np.std(cs_nm_parasol_on))
  513. # %%
  514. print("WN for NM")
  515. print('midget off', np.mean(wn_for_nm_midget_off), np.std(wn_for_nm_midget_off))
  516. print('parasol off', np.mean(wn_for_nm_parasol_off), np.std(wn_for_nm_parasol_off))
  517. print('large off', np.mean(wn_for_nm_large_off), np.std(wn_for_nm_large_off))
  518. print('parasol on', np.mean(wn_for_nm_parasol_on), np.std(wn_for_nm_parasol_on))
  519. print("NM for WN")
  520. print('midget off', np.mean(nm_for_wn_midget_off), np.std(nm_for_wn_midget_off))
  521. print('parasol off', np.mean(nm_for_wn_parasol_off), np.std(nm_for_wn_parasol_off))
  522. print('large off', np.mean(nm_for_wn_large_off), np.std(nm_for_wn_large_off))
  523. print('parasol on', np.mean(nm_for_wn_parasol_on), np.std(nm_for_wn_parasol_on))
  524. # %% [markdown]
  525. # ## Spatial filter figure
  526. # %%
  527. import os
  528. def get_spectrum(path):
  529. s = np.load(path)
  530. s = np.mean(s, axis=0)
  531. s = clean_peaks(s)
  532. s = s/s[0]
  533. return s
  534. def load_sc_spectra(path, cell_type, retinas, cell_stimulus, stimulus):
  535. spectra = []
  536. for retina in retinas:
  537. files = os.listdir(f'{path}/retina_{int(retina)}')
  538. for file in files:
  539. if f'stim_{stimulus}_f_F_{cell_stimulus}_{cell_type}' in file:
  540. print(file)
  541. try:
  542. s = get_spectrum(f'{path}/retina_{int(retina)}/{file}/spectra.npy')
  543. except Exception as e:
  544. print('file', file, 'failed')
  545. continue
  546. spectra.append(s)
  547. return spectra
  548. def clean_peaks(spectrum):
  549. peaks, _ = find_peaks(spectrum, height=0.000003)
  550. x = np.arange(len(spectrum))
  551. clean_spectrum = spectrum.copy()
  552. for peak in peaks:
  553. clean_spectrum[peak] = np.nan
  554. clean_spectrum = np.interp(x, x[~np.isnan(clean_spectrum)], clean_spectrum[~np.isnan(clean_spectrum)])
  555. return clean_spectrum
  556. # %%
  557. nm_spectrum = np.load('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/spectra/spectra_nm.npy')
  558. wn_spectrum = np.load('/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/spectra/spectra_wn.npy')
  559. # %%
  560. def get_kvals(frame_width, frame_height):
  561. kfreq_x = np.fft.fftshift(np.fft.fftfreq(frame_width)) * frame_width
  562. kfreq_y = np.fft.fftshift(np.fft.fftfreq(frame_height)) * frame_height
  563. kfreq_2d = np.meshgrid(kfreq_x, kfreq_y)
  564. k_norm = np.sqrt(kfreq_2d[0] ** 2 + kfreq_2d[1] ** 2)
  565. kbins = np.arange(0.5, max(frame_height, frame_width) // 2 + 1)
  566. kvals = 0.5 * (kbins[1:] + kbins[:-1])
  567. return kvals
  568. # %%
  569. def get_class_filters(df, cell_class, col, class_type='OFF', retina_index=None):
  570. sub_df = df[df['cell_class_g'] == cell_class]
  571. all_filters = None
  572. idx = 0
  573. for i, row in sub_df.iterrows():
  574. f = sub_df.loc[i, col]
  575. working_filter = f.f.copy()
  576. if all_filters is None:
  577. all_filters = np.zeros((sub_df.shape[0], 60, 60))
  578. sign = get_filter_sign(f.f)
  579. if (class_type == 'OFF') and (sign > 0):
  580. print(f'flipping OFF cell {i}')
  581. working_filter = -working_filter
  582. elif (class_type == 'ON') and (sign < 0):
  583. working_filter = -working_filter
  584. working_filter = torch.nn.functional.upsample_nearest(torch.tensor(working_filter).unsqueeze(0).unsqueeze(0), scale_factor=4).numpy()[0,0]
  585. working_filter = working_filter/np.sum(np.abs(working_filter))
  586. max_value = np.max(np.abs(working_filter))
  587. plt.imshow(working_filter, cmap='coolwarm', vmin=-0.5*max_value, vmax=0.5*max_value)
  588. plt.title(f'Cell {row["cell_name"]}, retina {row["retina"]}, wn cor {row["wn_cc"]}, nm_cor {row["nm_cc"]}')
  589. plt.show()
  590. all_filters[idx] = working_filter
  591. idx+=1
  592. return all_filters
  593. def get_ft_filters(class_filters, norm_value=None):
  594. ft_filters = np.zeros((class_filters.shape[0], 300))
  595. for i in range(class_filters.shape[0]):
  596. ft_f = get_spatial_ft(class_filters[i], norm_value=norm_value)
  597. ft_filters[i] = ft_f
  598. return ft_filters
  599. def radial_average(data, center=None):
  600. y, x = np.indices((data.shape))
  601. if center is None:
  602. center = np.array([x.max() / 2, y.max() / 2])
  603. # Compute radial distances
  604. r = np.sqrt((x - center[0])**2 + (y - center[1])**2)
  605. r = r.astype(np.int)
  606. radial_mean = np.bincount(r.ravel(), data.ravel()) / np.bincount(r.ravel())
  607. return radial_mean
  608. def get_power_spectrum(image, norm_ks, k_bins):
  609. fft_amp = np.abs(np.fft.fftshift(np.fft.fft2(image)))
  610. plt.imshow(fft_amp)
  611. plt.show()
  612. amps = binned_statistic(
  613. norm_ks.flatten(), fft_amp.flatten(),
  614. statistic="mean",
  615. bins=k_bins,
  616. )[0]
  617. amps /= amps.sum()
  618. return amps
  619. def get_spatial_ft(spat_filter, norm_value=None, plot=False):
  620. spat_filter = np.pad(spat_filter, 270, mode='constant')
  621. frame_width = spat_filter.shape[0]
  622. frame_height = spat_filter.shape[1]
  623. kfreq_x = np.fft.fftshift(np.fft.fftfreq(frame_width)) * frame_width
  624. kfreq_y = np.fft.fftshift(np.fft.fftfreq(frame_height)) * frame_height
  625. kfreq_2d = np.meshgrid(kfreq_x, kfreq_y)
  626. k_norm = np.sqrt(kfreq_2d[0] ** 2 + kfreq_2d[1] ** 2)
  627. kbins = np.arange(0.5, max(frame_height, frame_width) // 2 + 1)
  628. kvals = 0.5 * (kbins[1:] + kbins[:-1])
  629. if plot:
  630. plt.imshow(spat_filter)
  631. if norm_value is not None:
  632. spat_filter /= np.linalg.norm(spat_filter, ord=2)
  633. spectrum = get_power_spectrum(spat_filter, k_norm, kbins)
  634. return spectrum
  635. # %%
  636. print('m off WN')
  637. m_off = get_class_filters(df, 0, col='wn_spat', class_type='OFF')
  638. nm_m_off = get_class_filters(df, 0, col='nm_spat', class_type='OFF')
  639. print('p off NM')
  640. nm_p_off = get_class_filters(df, 1, col='nm_spat', class_type='OFF')
  641. print('p off WN')
  642. p_off = get_class_filters(df, 1, col='wn_spat', class_type='OFF',)
  643. print('l off WN')
  644. l_off = get_class_filters(df, 2, col='wn_spat', class_type='OFF')
  645. print('l off NM')
  646. nm_l_off = get_class_filters(df, 2, col='nm_spat', class_type='OFF')
  647. print('p on NM')
  648. nm_p_on = get_class_filters(df, 4, col='nm_spat', class_type='ON')
  649. print('p on WN')
  650. p_on = get_class_filters(df, 4, col='wn_spat', class_type='ON')
  651. max_value_wn = max([np.max(np.abs(m_off)), np.max(np.abs(p_off)), np.max(np.abs(p_on)), np.max(np.abs(l_off))])
  652. max_value_nm = max([np.max(np.abs(nm_m_off)), np.max(np.abs(nm_p_off)), np.max(np.abs(nm_p_on)), np.max(np.abs(nm_l_off))])
  653. max_value = max(max_value_nm, max_value_wn)
  654. # %%
  655. m_off_ft = get_ft_filters(m_off, norm_value=max_value)
  656. nm_m_off_ft = get_ft_filters(nm_m_off, norm_value=max_value)
  657. p_off_ft = get_ft_filters(p_off)
  658. nm_p_off_ft = get_ft_filters(nm_p_off)
  659. l_off_ft = get_ft_filters(l_off)
  660. nm_l_off_ft = get_ft_filters(nm_l_off)
  661. p_on_ft = get_ft_filters(p_on)
  662. nm_p_on_ft = get_ft_filters(nm_p_on)
  663. # %%
  664. def normalize_spat_filter(spatial_filter):
  665. # Compute the 2D Fourier Transform
  666. fft_result = np.fft.fft2(spatial_filter)
  667. # Calculate the amplitude (magnitude) spectrum
  668. amplitude_spectrum = np.abs(fft_result)
  669. # Extract the amplitude of the first frequency component (DC component)
  670. first_freq_amplitude = amplitude_spectrum[0, 0]
  671. # Avoid division by zero
  672. if first_freq_amplitude == 0:
  673. raise ValueError("The amplitude of the first frequency component is zero, cannot normalize.")
  674. # Normalize the entire frequency response
  675. normalized_fft = fft_result / first_freq_amplitude
  676. # Compute the inverse 2D FFT to get the normalized spatial filter
  677. normalized_spatial_filter = np.fft.ifft2(normalized_fft)
  678. # Since the inverse FFT may return complex numbers due to numerical errors,
  679. # take the real part if you expect a real-valued filter
  680. normalized_spatial_filter = np.real(normalized_spatial_filter)
  681. # Optional: Verification
  682. # Compute the FFT of the normalized spatial filter
  683. fft_normalized_filter = np.fft.fft2(normalized_spatial_filter)
  684. # Calculate the amplitude spectrum
  685. amplitude_spectrum_normalized = np.abs(fft_normalized_filter)
  686. # Verify the amplitude of the first frequency component
  687. normalized_first_freq_amplitude = amplitude_spectrum_normalized[0, 0]
  688. print("Amplitude of the first frequency component after normalization:", normalized_first_freq_amplitude)
  689. return normalized_spatial_filter/np.max(np.abs(normalized_spatial_filter))
  690. # %%
  691. def plot_paper_spat_filters(wn_filters, nm_filters, wn_ft_filters, nm_ft_filters, color, light_color, max_scalar=0.9, save_file=None, filter_file=None, max_value_nm=None, max_value_wn=None, xticks=None):
  692. wn_mean = np.mean(wn_filters, axis=0)
  693. nm_mean = np.mean(nm_filters, axis=0)
  694. wn_mean = normalize_spat_filter(wn_mean)
  695. nm_mean = normalize_spat_filter(nm_mean)
  696. wn_ft_filters = wn_ft_filters/wn_ft_filters[:, 0][:, None]
  697. nm_ft_filters = nm_ft_filters/nm_ft_filters[:, 0][:, None]
  698. wn_ft_mean = np.mean(wn_ft_filters, axis=0)
  699. nm_ft_mean = np.mean(nm_ft_filters, axis=0)
  700. wn_ft_std = np.std(wn_ft_filters, axis=0)
  701. nm_ft_std = np.std(nm_ft_filters, axis=0)
  702. if filter_file is not None:
  703. np.save(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/wn_mean_{filter_file}.npy', wn_mean)
  704. np.save(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/nm_mean_{filter_file}.npy', nm_mean)
  705. np.save(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/wn_ft_mean_{filter_file}.npy', wn_ft_mean)
  706. np.save(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/nm_ft_mean_{filter_file}.npy', nm_ft_mean)
  707. plt.rcParams['lines.linewidth'] = 0.5
  708. plt.rcParams['font.size'] = 8
  709. matplotlib.rcParams['svg.fonttype'] = 'none'
  710. fig, ax = plt.subplots(3,1, figsize=(0.8, 2.4))
  711. cax0 = ax[0].imshow(np.flip(wn_mean, axis=1), cmap='gray', vmin=-max_scalar*max_value_wn, vmax=max_scalar*max_value_wn)
  712. ax[0].grid(False)
  713. ax[1].grid(False)
  714. ax[0].get_xaxis().set_visible(False)
  715. ax[0].get_yaxis().set_visible(False)
  716. ax[1].get_xaxis().set_visible(False)
  717. ax[1].get_yaxis().set_visible(False)
  718. ax[0].spines['bottom'].set_visible(False)
  719. ax[0].spines['left'].set_visible(False)
  720. ax[0].spines['right'].set_visible(False)
  721. ax[0].spines['top'].set_visible(False)
  722. ax[1].spines['bottom'].set_visible(False)
  723. ax[1].spines['left'].set_visible(False)
  724. ax[1].spines['right'].set_visible(False)
  725. ax[1].spines['top'].set_visible(False)
  726. plt.grid(False)
  727. cax1 = ax[1].imshow(nm_mean, cmap='gray', vmin=-max_scalar*max_value_nm, vmax=max_scalar*max_value_nm)
  728. ax[2].plot(np.arange(len(nm_ft_mean))/800/7.5, wn_ft_mean, label='WN ft', linewidth=0.5, color=color)
  729. ax[2].fill_between(np.arange(len(wn_ft_mean))/800/7.5, wn_ft_mean - wn_ft_std, wn_ft_mean + wn_ft_std, color=color, alpha=0.2, label='WN std')
  730. ax[2].plot(np.arange(len(nm_ft_mean))/800/7.5, nm_ft_mean, label='NM ft', linewidth=0.5, color=color, linestyle='--')
  731. ax[2].fill_between(np.arange(len(nm_ft_mean))/800/7.5, nm_ft_mean - nm_ft_std, nm_ft_mean + nm_ft_std, color=light_color, alpha=0.2, label='NM std')
  732. ax[2].set_xlabel('Cycle per \u03bcm')
  733. ax[2].set_ylabel('Magnitude')
  734. ax[2].set_xlim(0.00, 0.012)
  735. ax[2].tick_params(bottom=True, top=False, left=True, right=False)
  736. sns.despine(offset=5)
  737. if save_file is not None:
  738. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg', dpi=300, transparent=True)
  739. plt.show()
  740. return wn_ft_mean, nm_ft_mean
  741. # %%
  742. wn_m_off_ft_mean, nm_m_off_ft_mean = plot_paper_spat_filters(m_off, nm_m_off, m_off_ft, nm_m_off_ft, save_file='spat_mean_off_midget', max_value_nm=1, max_value_wn=1, filter_file='m_off', xticks=[0.4, 0.8], color=colors[0], light_color=light_colors[0])
  743. wn_p_off_ft_mean, nm_p_off_ft_mean = plot_paper_spat_filters(p_off, nm_p_off, p_off_ft, nm_p_off_ft,save_file='spat_mean_off_parasol', max_value_nm=1, max_value_wn=1, filter_file='p_off', xticks=[0.4, 0.8], color=colors[1], light_color=light_colors[1])
  744. wn_l_off_ft_mean, nm_l_off_ft_mean = plot_paper_spat_filters(l_off, nm_l_off, l_off_ft, nm_l_off_ft,save_file='spat_mean_off_large', max_value_nm=1, max_value_wn=1, filter_file='l_off', xticks=[0.4, 0.8], color=colors[2], light_color=light_colors[2])
  745. wn_p_on_ft_mean, nm_p_on_ft_mean = plot_paper_spat_filters(p_on, nm_p_on, p_on_ft, nm_p_on_ft,save_file='spat_mean_on_parasol', max_value_nm=1, max_value_wn=1, filter_file='p_on', xticks=[0.4, 0.8], color=colors[3], light_color=light_colors[3])
  746. # %%
  747. midget = df[df['cell_class_g']==0]['nm_size'], df[df['cell_class_g']==0]['wn_size']
  748. parasol = df[df['cell_class_g']==1]['nm_size'], df[df['cell_class_g']==1]['wn_size']
  749. large = df[df['cell_class_g']==2]['nm_size'], df[df['cell_class_g']==2]['wn_size']
  750. on = df[df['cell_class_g']==4]['nm_size'], df[df['cell_class_g']==4]['wn_size']
  751. # %%
  752. midget_a = df[df['cell_class_g']==0]['nm_amp'], df[df['cell_class_g']==0]['wn_amp']
  753. parasol_a = df[df['cell_class_g']==1]['nm_amp'], df[df['cell_class_g']==1]['wn_amp']
  754. large_a = df[df['cell_class_g']==2]['nm_amp'], df[df['cell_class_g']==2]['wn_amp']
  755. on_a = df[df['cell_class_g']==4]['nm_amp'], df[df['cell_class_g']==4]['wn_amp']
  756. # %% [markdown]
  757. # ### Significance testing
  758. # %% [markdown]
  759. # | Midget OFF | Parasol OFF | Large OFFF | Parasol ON |
  760. # | -- | -- | -- | -- |
  761. # | NM > WN ** | NM > WN ** | NM < WN ** | NM, WN not significantly different |
  762. # | NM amp > WN amp ** | NM amp > WN amp ** | NM amp > WN amp ** | NM amp, WN amp not significantl different |
  763. # %%
  764. def test_significance(wn_cells, nm_cells, alternatives='greater'):
  765. statistic, p_value = wilcoxon(wn_cells, nm_cells, zero_method='wilcox', correction=False, alternative=alternatives, mode='auto')
  766. print('Wilcoxon Signed-Rank Test Results:')
  767. print('Statistic:', statistic)
  768. print('p-value:', p_value)
  769. # %%
  770. test_significance(midget[0], midget[1], alternatives='greater')
  771. test_significance(parasol[0], parasol[1], alternatives='greater')
  772. test_significance(large[0], large[1], alternatives='greater')
  773. test_significance(on[0], on[1], alternatives='greater')
  774. # %%
  775. # %%
  776. test_significance(midget_a[0], midget_a[1], alternatives='greater')
  777. test_significance(parasol_a[0], parasol_a[1], alternatives='greater')
  778. test_significance(large_a[0], large_a[1], alternatives='greater')
  779. test_significance(on_a[0], on_a[1], alternatives='greater')
  780. # %%
  781. def plot_pretty_size_plot(df, lw=0, s=15, save_file=None, a=0.75):
  782. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  783. plt.rcParams['lines.linewidth'] = 0.5
  784. plt.rcParams['font.size'] = 8
  785. matplotlib.rcParams['svg.fonttype'] = 'none'
  786. fig, ax = plt.subplots(figsize=(2.1,2.1))
  787. ax.tick_params(bottom=True, top=False, left=True, right=False)
  788. plt.grid(False)
  789. midget = df[df['cell_class_g']==0]['nm_size'], df[df['cell_class_g']==0]['wn_size']
  790. parasol = df[df['cell_class_g']==1]['nm_size'], df[df['cell_class_g']==1]['wn_size']
  791. large = df[df['cell_class_g']==2]['nm_size'], df[df['cell_class_g']==2]['wn_size']
  792. on = df[df['cell_class_g']==4]['nm_size'], df[df['cell_class_g']==4]['wn_size']
  793. plt.scatter(*midget, color=colors[0], label='Midget OFF', s=s, linewidth=lw ,alpha=a)
  794. plt.scatter(*parasol, color=colors[1], label='Parasol OFF', s=s, linewidth=lw, alpha=a)
  795. plt.scatter(*large, color=colors[2], label='Large OFF', s=s, linewidth=lw, alpha=a)
  796. plt.scatter(*on, color=colors[3], label='Parasol ON', s=s, linewidth=lw, alpha=a)
  797. plt.scatter(np.mean(on[0]), np.mean(on[1]), facecolor=colors[3], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  798. plt.scatter(np.mean(midget[0]), np.mean(midget[1]), facecolor=colors[0], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  799. plt.scatter(np.mean(parasol[0]), np.mean(parasol[1]), facecolor=colors[1], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  800. plt.scatter(np.mean(large[0]), np.mean(large[1]), facecolor=colors[2], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  801. plt.xlim(25*4*30, 1450*4*30)
  802. plt.ylim(25*4*30, 1450*4*30)
  803. plt.xscale('log')
  804. plt.yscale('log')
  805. plt.plot([10*2*30 ,1450*4* 30] , [10*2*30, 1450*4*30], color='black', linewidth=1, linestyle='--')
  806. sns.despine(offset=5)
  807. plt.xlabel('NM center size')
  808. plt.ylabel('WN center size')
  809. # plt.legend()
  810. if save_file is not None:
  811. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg', transparent=True, bbox_inches='tight', dpi=300)
  812. plt.show()
  813. def plot_pretty_amplitude_plot(df, lw=0, s=15, save_file=None, a=0.75):
  814. sns.color_palette("ch:start=.2,rot=-.3", as_cmap=True)
  815. plt.rcParams['lines.linewidth'] = 0.5
  816. plt.rcParams['font.size'] = 8
  817. matplotlib.rcParams['svg.fonttype'] = 'none'
  818. fig, ax = plt.subplots(figsize=(2.1,2.1))
  819. ax.tick_params(bottom=True, top=False, left=True, right=False)
  820. plt.grid(False)
  821. midget = df[df['cell_class_g']==0]['nm_amp'], df[df['cell_class_g']==0]['wn_amp']
  822. parasol = df[df['cell_class_g']==1]['nm_amp'], df[df['cell_class_g']==1]['wn_amp']
  823. large = df[df['cell_class_g']==2]['nm_amp'], df[df['cell_class_g']==2]['wn_amp']
  824. on = df[df['cell_class_g'] == 4]['nm_amp'], df[df['cell_class_g']==4]['wn_amp']
  825. plt.scatter(*midget, color=colors[0], label='Midget OFF', s=s, linewidth=lw, alpha=a)
  826. plt.scatter(*parasol, color=colors[1], label='Parasol OFF', s=s, linewidth=lw, alpha=a)
  827. plt.scatter(*large, color=colors[2], label='Large OFF', s=s, linewidth=lw, alpha=a)
  828. plt.scatter(*on, color=colors[3], label='Parasol ON', s=s, linewidth=lw, alpha=a)
  829. plt.scatter(np.mean(midget[0]), np.mean(midget[1]), facecolor=colors[0], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  830. plt.scatter(np.mean(parasol[0]), np.mean(parasol[1]), facecolor=colors[1], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  831. plt.scatter(np.mean(large[0]), np.mean(large[1]), facecolor=colors[2], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  832. plt.scatter(np.mean(on[0]), np.mean(on[1]), facecolor=colors[3], edgecolor='black', marker='X', linewidth=0.5, s=s*2, alpha=1)
  833. plt.plot([0, 0.15], [0, 0.15], color='black', linewidth=1, linestyle='--')
  834. sns.despine(offset=5)
  835. plt.xlabel('NM surround amplitude')
  836. plt.ylabel('WN surround amplitude')
  837. plt.xlim(-0.01, 0.15)
  838. plt.ylim(-0.01, 0.15)
  839. # plt.legend()
  840. if save_file is not None:
  841. plt.savefig(f'/usr/users/vystrcilova/retinal_circuit_modeling/evaluations/visualizations/adaptation_paper/figures/{save_file}.svg', transparent=True, bbox_inches='tight', dpi=300)
  842. plt.show()
  843. # %%
  844. plot_pretty_size_plot(df, save_file='size_scatter', s=25)
  845. plot_pretty_amplitude_plot(df, save_file='amps_scatter', a=0.6, s=25)

adaptation_paper_figures.ipynb at commit 0d48996, no license · at the source

Overview

Authors: Michaela Vystrčilová1, Shashwat Sridhar2,3, Max F Burg1,4,5, Mohammad H Khani2,3, Dimokratis Karamanlis2,3, Helene M Schreyer2,3, Varsha Ramakrishna2,3,6, Steffen Krüppel2,3, Sören J Zapp2,3, Matthias Mietsch7,8, Tim Gollisch2,3,9, Alexander S Ecker1,10
  1. University of Göttingen, Institute of Computer Science and Campus Institute Data Science, Göttingen 37037, Germany
  2. Department of Ophthalmology, University Medical Center Göttingen, Göttingen 37073, Germany
  3. Bernstein Center for Computational Neuroscience Göttingen, Göttingen 14195, Germany
  4. International Max Planck Research School for Intelligent Systems, Tübingen 72076, Germany
  5. Tübingen AI Center, University of Tübingen, Tübingen 72076, Germany
  6. International Max Planck Research School for Neurosciences, Göttingen 72074, Germany
  7. Laboratory Animal Science Unit, German Primate Center, Göttingen 37077, Germany
  8. German Center for Cardiovascular Research, Partner Site Göttingen, Göttingen 37073, Germany
  9. Cluster of Excellence “Multiscale Bioimaging: from Molecular Machines to Networks of Excitable Cells” (MBExC), University of Göttingen, Göttingen 37075, Germany
  10. Max Planck Institutefor Dynamics and Self-Organization, Göttingen 37077, Germany
Journal: eNeuro, volume 13, issue 5, pages ENEURO.0060-26.2026
Dates: received 16 February 2026; accepted 18 February 2026; published online 30 April 2026; in print May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1523/eneuro.0060-26.2026 · PMID 41856791 · PMCID PMC13138850 · OpenAlex W7138879380
Open access: gold, a free copy (OpenAlex)
Status: code verified
Methods: Spectral & time-frequency, Single-unit activity, calcium imaging
Keywords: LN models, receptive fields, retinal ganglion cells, stimulus adaptation
MeSH: Adaptation, Physiological*, Retinal Ganglion Cells*, Space Perception*, Action Potentials, Animals, Callithrix, Male, Models, Neurological, Nonlinear Dynamics, Photic Stimulation, Visual Fields (* major topic)
Topic: Retinal Development and Disorders (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: German Research Foundation (432680300, 515774656); European Reseach Council (101041669)
Citations: not cited yet (Europe PMC); 48 references in the paper

Abstract

The retina encodes a broad range of stimuli, adapting its computations to features like brightness, contrast, and motion. However, it is unclear whether it also adapts when switching between natural scenes and white noise (WN). To address this, we analyzed the neural activity of male marmoset retinal ganglion cells (RGCs) in response to WN and naturalistic movies. We trained linear–nonlinear models on both stimuli, evaluated their performance, and compared their receptive fields across stimulus domains. We found that models with spatial filters trained on one stimulus ensemble were less accurate when predicting neural activity on the other compared to models trained directly on the target stimulus. This suggests that spatial processing adapts to stimulus statistics. Different RGC types exhibited distinct changes: The OFF midget cells’ receptive fields became enlarged under natural movies (NMs), resulting in a lower cutoff frequency. Parasol cells and large OFF cells did not significantly change their receptive field sizes. All cell types exhibited stronger surrounds under NMs, resembling the whitening filters predicted by efficient coding for stimulus decorrelation, prompting us to test whether these changes were related to the different spectral content of the two stimulus types. Quantifying the effects of the filters’ enhanced surrounds on the stimulus power spectrum showed a significant contribution toward whitening only in ON parasol cells, where a whitening effect emerged regardless of the training stimulus. These results suggest that while RGCs adapt to the differences between WN and NM stimuli, efficient coding can only partially account for this adaptation.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 6 matches between paragraphs and lines of code.

doi.gin.g-node.org/10.12751/g-node.t43ph1

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Data and code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)

ecker-lab/retina-adaptation

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 0d489964e2c9efd3fb04c815e33b2a67a1051c08, 16 February 2025
Languages: Python (25), Jupyter (3), MATLAB (2)
Size: 57 files, 30 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: README, environment (requirements.txt), 3 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: PyTorch (22 files), NumPy (21 files), Matplotlib (10 files), seaborn (5 files), SciPy (4 files), OpenCV (1 file), pandas (1 file), Pillow (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
31 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 30 scripts, each with its path and the digest of its content;
  • 6 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and code availability

The data is available on GIN (https://doi.gin.g-node.org/10.12751/g-node.t43ph1/) and the code on GitHub (https://github.com/ecker-lab/retina-adaptation)

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 4 keywords, 11 MeSH terms, 2 funders, 45 references.

Cite

This paper

Vystrčilová, M., Sridhar, S., Burg, M. F., Khani, M. H., Karamanlis, D., Schreyer, H. M., Ramakrishna, V., Krüppel, S., Zapp, S. J., Mietsch, M., Gollisch, T., & Ecker, A. S. (2026). Spatial Adaptation of Primate Retinal Ganglion Cells Between Artificial and Natural Stimuli. eNeuro, 13(5), ENEURO.0060-26.2026. https://doi.org/10.1523/eneuro.0060-26.2026

BibTeX

@article{vystrcilova2026spatial,
author = {Vystrčilová, Michaela and Sridhar, Shashwat and Burg, Max F and Khani, Mohammad H and Karamanlis, Dimokratis and Schreyer, Helene M and Ramakrishna, Varsha and Krüppel, Steffen and Zapp, Sören J and Mietsch, Matthias and Gollisch, Tim and Ecker, Alexander S},
title = {{Spatial Adaptation of Primate Retinal Ganglion Cells Between Artificial and Natural Stimuli}},
journal = {eNeuro},
year = {2026},
month = may,
volume = {13},
number = {5},
pages = {ENEURO.0060--26.2026},
publisher = {Society for Neuroscience},
issn = {2373-2822},
doi = {10.1523/eneuro.0060-26.2026},
url = {https://doi.org/10.1523/eneuro.0060-26.2026},
pmid = {41856791},
pmcid = {PMC13138850}
}

RIS

TY - JOUR
AU - Vystrčilová, Michaela
AU - Sridhar, Shashwat
AU - Burg, Max F
AU - Khani, Mohammad H
AU - Karamanlis, Dimokratis
AU - Schreyer, Helene M
AU - Ramakrishna, Varsha
AU - Krüppel, Steffen
AU - Zapp, Sören J
AU - Mietsch, Matthias
AU - Gollisch, Tim
AU - Ecker, Alexander S
TI - Spatial Adaptation of Primate Retinal Ganglion Cells Between Artificial and Natural Stimuli
T2 - eNeuro
J2 - eNeuro
PY - 2026
DA - 2026/05/04
VL - 13
IS - 5
SP - ENEURO.0060
EP - 26.2026
SN - 2373-2822
PB - Society for Neuroscience
DO - 10.1523/eneuro.0060-26.2026
UR - https://doi.org/10.1523/eneuro.0060-26.2026
LA - en
ER -

CSL-JSON

{
"id": "10.1523/eneuro.0060-26.2026",
"type": "article-journal",
"title": "Spatial Adaptation of Primate Retinal Ganglion Cells Between Artificial and Natural Stimuli",
"container-title": "eNeuro",
"author": [
{
"family": "Vystrčilová",
"given": "Michaela"
},
{
"family": "Sridhar",
"given": "Shashwat"
},
{
"family": "Burg",
"given": "Max F"
},
{
"family": "Khani",
"given": "Mohammad H"
},
{
"family": "Karamanlis",
"given": "Dimokratis"
},
{
"family": "Schreyer",
"given": "Helene M"
},
{
"family": "Ramakrishna",
"given": "Varsha"
},
{
"family": "Krüppel",
"given": "Steffen"
},
{
"family": "Zapp",
"given": "Sören J"
},
{
"family": "Mietsch",
"given": "Matthias"
},
{
"family": "Gollisch",
"given": "Tim"
},
{
"family": "Ecker",
"given": "Alexander S"
}
],
"container-title-short": "eNeuro",
"volume": "13",
"issue": "5",
"page": "ENEURO.0060-26.2026",
"DOI": "10.1523/eneuro.0060-26.2026",
"PMID": "41856791",
"PMCID": "PMC13138850",
"ISSN": "2373-2822",
"publisher": "Society for Neuroscience",
"URL": "https://doi.org/10.1523/eneuro.0060-26.2026",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
4
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014157 [code]
Modeling spatial contrast sensitivity in responses of primate retinal ganglion cells to natural movies.
Journal: PLoS computational biology
In common: PyTorch, pandas, SciPy, 1 other tool, 9 references, 5 authors
[2] doi:10.1126/sciadv.aed4172 [code]
Locomotion optimizes sensory representations through a computational principle shared by rodents and primates.
Journal: Science advances
In common: OpenCV, PyTorch, pandas, 3 other tools, 2 references
[3] doi:10.7554/elife.105953 [code]
Top-down feedback in deep neural networks leads to functional differences during audiovisual integration.
Journal: eLife
In common: Pillow, PyTorch, seaborn, 4 other tools, 1 reference
[4] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: OpenCV, Pillow, PyTorch, 5 other tools
[5] doi:10.1038/s43856-026-01817-x [code]
Visual prompt engineering for multimodal and irregularly sampled medical data.
Journal: Communications medicine
In common: OpenCV, Pillow, PyTorch, 5 other tools
[6] doi:10.1038/s41467-026-76837-1 [code]
Drug screen and machine learning predict neuroprotective agents in a preclinical human model of childhood dementia.
Journal: Nature communications
In common: OpenCV, Pillow, PyTorch, 5 other tools
[7] doi:10.1038/s41593-026-02388-9 [code]
Hippocampal CA3 connectomics reveals a gradient of mossy fiber inputs and selective feedforward inhibition onto pyramidal cells.
Journal: Nature neuroscience
In common: OpenCV, Pillow, PyTorch, 5 other tools
[8] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: OpenCV, Pillow, PyTorch, 5 other tools
[9] doi:10.1162/imag.a.1309 [code]
Probing the content of semantic representations in body-selective regions.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: OpenCV, Pillow, PyTorch, 5 other tools
[10] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: OpenCV, Pillow, PyTorch, 5 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.