interneuron_placement.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. import random as rng
  2. import matplotlib.pyplot as plt
  3. import noise
  4. import numpy as np
  5. from matplotlib.patches import Ellipse
  6. from tqdm import tqdm
  7. from brian2.units import *
  8. from scripts.spatial_maps.orientation_map import OrientationMap
  9. class Pickle:
  10. def __init__(self, x, y, a, b, phi):
  11. self.x = x
  12. self.y = y
  13. self.a = a # Semimajor axis
  14. self.b = b # Semiminor axis
  15. self.phi = phi
  16. self.c = 2.0 * a # Sum distance
  17. self.foc_len = np.sqrt(a ** 2 - b ** 2)
  18. self.area = np.pi * a * b
  19. def get_p1(self):
  20. x1 = self.x - self.foc_len * np.cos(self.phi)
  21. y1 = self.y - self.foc_len * np.sin(self.phi)
  22. return x1, y1
  23. def get_p2(self):
  24. x2 = self.x + self.foc_len * np.cos(self.phi)
  25. y2 = self.y + self.foc_len * np.sin(self.phi)
  26. return x2, y2
  27. def get_ellipse(self):
  28. ell = Ellipse((self.x, self.y), 2.0 * self.a, 2.0 * self.b,
  29. angle=self.phi * 180. / np.pi, linewidth=1, fill=False, zorder=2)
  30. return ell
  31. def get_area(self):
  32. return np.pi * self.a * self.b
  33. def plot_neural_sheet(ex_positions, ex_tunings, axonal_clouds=None, delta_xy=[]):
  34. X, Y = get_position_mesh(ex_positions)
  35. n_ex = int(np.sqrt(len(ex_positions)))
  36. head_dir_preference = np.array(ex_tunings).reshape((n_ex, n_ex))
  37. fig = plt.figure()
  38. ax = fig.add_subplot(111)
  39. plt.set_cmap('twilight')
  40. c = ax.pcolor(X, Y, head_dir_preference.T, vmin=-np.pi, vmax=np.pi)
  41. fig.colorbar(c, ax=ax, label="Tuning")
  42. # plt.colorbar()
  43. if axonal_clouds is not None:
  44. for i, p in enumerate(axonal_clouds):
  45. ell = p.get_ellipse()
  46. # print(ell)
  47. ax.add_artist(ell)
  48. # ax.quiver(p.x, p.y, delta_xy[i][0], delta_xy[i][1], scale=2.0)
  49. # px = axonal_clouds[0].x
  50. # py = axonal_clouds[0].y
  51. # # print(px,py)
  52. # ax.plot(px, py, 'rx')
  53. # p1 = axonal_clouds[0].get_p1()
  54. # # print(p1)
  55. # ax.plot(p1[0], p1[1], 'bx')
  56. # p2 = axonal_clouds[0].get_p2()
  57. # # print(p2)
  58. # ax.plot(p2[0], p2[1], 'bx')
  59. def get_position_mesh(ex_positions):
  60. n_ex = int(np.sqrt(len(ex_positions)))
  61. X = np.array([x for x, _ in ex_positions]).reshape((n_ex, n_ex))
  62. Y = np.array([y for _, y in ex_positions]).reshape((n_ex, n_ex))
  63. return X, Y
  64. def get_entropy_of_axonal_coverage(ex_neuron_tuning, ie_connections):
  65. ex_phase_vals_per_pickle = []
  66. phase_entropy_per_pickle = []
  67. n_hist_bins = 10
  68. for connected_idxs in ie_connections:
  69. ex_phase_vals_of_p = [ex_neuron_tuning[idx] for idx in connected_idxs]
  70. phase_entropy = 0.0
  71. ex_phase_vals_per_pickle.append(ex_phase_vals_of_p)
  72. ex_phase_hist, _ = np.histogram(ex_phase_vals_of_p, n_hist_bins)
  73. n_ex_of_p = float(len(ex_phase_vals_of_p))
  74. ex_phase_hist = 1. / n_ex_of_p * ex_phase_hist.astype(float)
  75. for n_phi in ex_phase_hist:
  76. if n_phi != 0:
  77. phase_entropy -= n_phi * np.log(n_phi)
  78. phase_entropy_per_pickle.append(phase_entropy)
  79. return phase_entropy_per_pickle
  80. def get_excitatory_neurons_in_inhibitory_axonal_clouds(ex_neuron_positions, inhibitory_axonal_clouds):
  81. ie_connections = []
  82. for p in inhibitory_axonal_clouds:
  83. ie_conn_p = []
  84. x_p_1, y_p_1 = p.get_p1()
  85. x_p_2, y_p_2 = p.get_p2()
  86. for ex_idx, ex_pos in enumerate(ex_neuron_positions):
  87. x_ex, y_ex = ex_pos
  88. d_1 = np.sqrt((x_ex - x_p_1) ** 2 + (y_ex - y_p_1) ** 2)
  89. d_2 = np.sqrt((x_ex - x_p_2) ** 2 + (y_ex - y_p_2) ** 2)
  90. if d_1 + d_2 < p.c:
  91. ie_conn_p.append(ex_idx)
  92. ie_connections.append(ie_conn_p)
  93. return ie_connections
  94. def get_excitatory_phases_in_inhibitory_axon(ex_neuron_positions, ex_neuron_tunings, inhibitory_neuron):
  95. ex_phase_vals = []
  96. x_p_1, y_p_1 = inhibitory_neuron.get_p1()
  97. x_p_2, y_p_2 = inhibitory_neuron.get_p2()
  98. for ex_idx, ex_pos in enumerate(ex_neuron_positions):
  99. x_ex, y_ex = ex_pos
  100. d_1 = np.sqrt((x_ex - x_p_1) ** 2 + (y_ex - y_p_1) ** 2)
  101. d_2 = np.sqrt((x_ex - x_p_2) ** 2 + (y_ex - y_p_2) ** 2)
  102. if d_1 + d_2 < inhibitory_neuron.c:
  103. ex_phase_vals.append(ex_neuron_tunings[ex_idx])
  104. return ex_phase_vals
  105. def get_interneuron_entropy(ex_phase_vals, entropy_bins):
  106. phase_entropy = 0.
  107. ex_phase_hist, _ = np.histogram(ex_phase_vals,entropy_bins)
  108. ex_phase_prob = ex_phase_hist/len(ex_phase_vals)
  109. for prob in ex_phase_prob:
  110. if prob > 0.0:
  111. phase_entropy += -prob * np.log(prob)
  112. return phase_entropy
  113. def create_interneuron_sheet_by_noise(ex_positions, ex_tunings, number_of_interneurons, long_axis, short_axis, max_x, max_y, random_seed=None,
  114. n_iterations = 100):
  115. '''
  116. Start with uniform distribution
  117. '''
  118. if random_seed is not None:
  119. rng.seed(random_seed)
  120. pickle_a = long_axis
  121. pickle_b = short_axis
  122. pickle_n = number_of_interneurons
  123. x_n = int(np.sqrt(pickle_n))
  124. y_n = int(np.sqrt(pickle_n))
  125. x_step = int(max_x/(x_n+1))
  126. y_step = int(max_y/(y_n+1))
  127. inhibitory_axonal_clouds = []
  128. for n in range(pickle_n):
  129. inhibitory_axonal_clouds.append(Pickle(((n % x_n) + 1) * x_step, \
  130. (int(n / y_n) + 1) * y_step, \
  131. pickle_a, pickle_b, rng.random() * 2. * np.pi))
  132. '''
  133. Determine initial tunings and entropy values
  134. '''
  135. entropy_bins = np.linspace(-np.pi, np.pi, 30)
  136. ex_tunings_of_interneurons = []
  137. entropies_of_interneurons = []
  138. for i_neuron in inhibitory_axonal_clouds:
  139. ex_phase_vals = get_excitatory_phases_in_inhibitory_axon(ex_positions, ex_tunings, i_neuron)
  140. ex_tunings_of_interneurons.append(ex_phase_vals)
  141. entropies_of_interneurons.append(get_interneuron_entropy(ex_phase_vals, entropy_bins))
  142. '''
  143. (Randomly) perturb interneuron and see if entropy increases, if so update position and axonal tunings
  144. '''
  145. # print('Mean Entropy before rotation ', np.mean(entropies_of_interneurons))
  146. pert_phi = np.pi / 4.0
  147. pert_phi_decrease = pert_phi / (n_iterations + 1)
  148. for it in tqdm(range(n_iterations), desc="Optimizing axonal cloud distribution"):
  149. rotation_perturbation = pert_phi - it * pert_phi_decrease
  150. for i_neuron_id, i_neuron in enumerate(inhibitory_axonal_clouds):
  151. perturbed_interneuron = Pickle(i_neuron.x, i_neuron.y, long_axis, short_axis, i_neuron.phi + rotation_perturbation)
  152. perturbed_phase_vals = get_excitatory_phases_in_inhibitory_axon(ex_positions, ex_tunings, perturbed_interneuron)
  153. perturbed_entropy = get_interneuron_entropy(perturbed_phase_vals, entropy_bins)
  154. if perturbed_entropy > entropies_of_interneurons[i_neuron_id]:
  155. ex_tunings_of_interneurons.append(ex_phase_vals)
  156. entropies_of_interneurons[i_neuron_id] = perturbed_entropy
  157. i_neuron.phi = perturbed_interneuron.phi
  158. else:
  159. perturbed_interneuron = Pickle(i_neuron.x, i_neuron.y, long_axis, short_axis,
  160. i_neuron.phi - rotation_perturbation)
  161. perturbed_phase_vals = get_excitatory_phases_in_inhibitory_axon(ex_positions, ex_tunings,
  162. perturbed_interneuron)
  163. perturbed_entropy = get_interneuron_entropy(perturbed_phase_vals, entropy_bins)
  164. if perturbed_entropy > entropies_of_interneurons[i_neuron_id]:
  165. ex_tunings_of_interneurons.append(ex_phase_vals)
  166. entropies_of_interneurons[i_neuron_id] = perturbed_entropy
  167. i_neuron.phi = perturbed_interneuron.phi
  168. # print('Mean Entropy after rotation', np.mean(entropies_of_interneurons))
  169. return inhibitory_axonal_clouds, np.mean(entropies_of_interneurons)
  170. def create_interneuron_sheet_entropy_max_orientation(ex_positions, ex_tunings, number_of_interneurons, long_axis,
  171. short_axis, max_x, max_y, trial_orientations = 30):
  172. '''
  173. Start with uniform distribution
  174. '''
  175. pickle_a = long_axis
  176. pickle_b = short_axis
  177. pickle_n = number_of_interneurons
  178. x_n = int(np.sqrt(pickle_n))
  179. y_n = int(np.sqrt(pickle_n))
  180. x_step = int(max_x/(x_n+1))
  181. y_step = int(max_y/(y_n+1))
  182. inhibitory_axonal_clouds = []
  183. for n in range(pickle_n):
  184. inhibitory_axonal_clouds.append(Pickle(((n % x_n) + 1) * x_step, \
  185. (int(n / y_n) + 1) * y_step, \
  186. pickle_a, pickle_b, rng.random() * 2. * np.pi))
  187. '''
  188. Determine initial tunings and entropy values
  189. '''
  190. entropy_bins = np.linspace(-np.pi, np.pi, 30)
  191. entropies_of_interneurons = []
  192. # for i_neuron in tqdm(inhibitory_axonal_clouds, desc="Maximizing entropy by rotation"):
  193. for i_neuron in inhibitory_axonal_clouds:
  194. orientations = np.linspace(-np.pi, np.pi, trial_orientations)
  195. entropy_for_orientation = []
  196. for orientation in orientations:
  197. perturbed_interneuron = Pickle(i_neuron.x, i_neuron.y, long_axis, short_axis, orientation)
  198. ex_phase_vals = get_excitatory_phases_in_inhibitory_axon(ex_positions, ex_tunings, perturbed_interneuron)
  199. entropy_for_orientation.append(get_interneuron_entropy(ex_phase_vals, entropy_bins))
  200. optimal_orientation = orientations[np.argmax(entropy_for_orientation)]
  201. i_neuron.phi = optimal_orientation
  202. entropies_of_interneurons.append(np.max(entropy_for_orientation))
  203. # print(len(ex_phase_vals),short_axis,long_axis)
  204. return inhibitory_axonal_clouds, np.mean(entropies_of_interneurons)
  205. def create_interneuron_sheet_by_repulsive_force(number_of_interneurons, long_axis, short_axis, max_x, max_y, random_seed=None,
  206. n_iterations = 100):
  207. # '''
  208. # Start with random pickle distribution
  209. # '''
  210. # if random_seed is not None:
  211. # rng.seed(random_seed)
  212. # pickle_a = long_axis
  213. # pickle_b = short_axis
  214. # pickle_n = number_of_interneurons
  215. # x_rng_min = pickle_a
  216. # x_rng_max = max_x - pickle_a
  217. # y_rng_min = pickle_a
  218. # y_rng_max = max_y - pickle_a
  219. # inhibitory_axonal_clouds = []
  220. # for n in range(pickle_n):
  221. # inhibitory_axonal_clouds.append(Pickle(rng.uniform(x_rng_min, x_rng_max), \
  222. # rng.uniform(y_rng_min, y_rng_max), \
  223. # pickle_a, pickle_b, rng.random() * 2. * np.pi))
  224. '''
  225. Start with uniform distribution
  226. '''
  227. if random_seed is not None:
  228. rng.seed(random_seed)
  229. pickle_a = long_axis
  230. pickle_b = short_axis
  231. pickle_n = number_of_interneurons
  232. x_n = int(np.sqrt(pickle_n))
  233. y_n = int(np.sqrt(pickle_n))
  234. x_step = int(max_x/(x_n+1))
  235. y_step = int(max_y/(y_n+1))
  236. inhibitory_axonal_clouds = []
  237. for n in range(pickle_n):
  238. inhibitory_axonal_clouds.append(AxonalCloud(((n % x_n) + 1) * x_step, \
  239. (int(n / y_n) + 1) * y_step, \
  240. pickle_a, pickle_b, rng.random() * 2. * np.pi))
  241. # plotting()
  242. '''
  243. Distribute pickles by repulsive "force"
  244. '''
  245. f_scale = 0.2
  246. def point_repulsion(x_i, y_i, x_j, y_j):
  247. d_x = x_i - x_j
  248. d_y = y_i - y_j
  249. # Distance is torus-like as to approach more homogenous distribution
  250. if d_x > max_x / 2.:
  251. d_x = -max_x + d_x
  252. elif d_x < -max_x / 2.:
  253. d_x = max_x + d_x
  254. if d_y > max_y / 2.:
  255. d_y = -max_y + d_y
  256. elif d_y < -max_y / 2.:
  257. d_y = max_y + d_y
  258. d = np.sqrt(d_x ** 2 + d_y ** 2)
  259. f_i = f_scale * 1. / (d + 1) ** 2
  260. f_x = f_i * d_x / d
  261. f_y = f_i * d_y / d
  262. return f_x, f_y
  263. def pickle_repulsion(p_i, p_j):
  264. phi = p_i.phi
  265. p_i_x1, p_i_y1 = p_i.get_p1()
  266. p_i_x2, p_i_y2 = p_i.get_p2()
  267. p_j_x1, p_j_y1 = p_j.get_p1()
  268. p_j_x2, p_j_y2 = p_j.get_p2()
  269. f_p1p1_x, f_p1p1_y = point_repulsion(p_i_x1, p_i_y1, p_j_x1, p_j_y1) # p1-p1
  270. f_p1p2_x, f_p1p2_y = point_repulsion(p_i_x1, p_i_y1, p_j_x2, p_j_y2) # p1-p2
  271. f_p2p1_x, f_p2p1_y = point_repulsion(p_i_x2, p_i_y2, p_j_x1, p_j_y1) # p2-p1
  272. f_p2p2_x, f_p2p2_y = point_repulsion(p_i_x2, p_i_y2, p_j_x2, p_j_y2) # p2-p2
  273. f_p1_x = f_p1p1_x + f_p1p2_x
  274. f_p1_y = f_p1p1_y + f_p1p2_y
  275. f_p2_x = f_p2p1_x + f_p2p2_x
  276. f_p2_y = f_p2p1_y + f_p2p2_y
  277. m_p1 = (f_p1_x * np.sin(phi) - f_p1_y * np.cos(phi))
  278. m_p2 = (-f_p2_x * np.sin(phi) + f_p2_y * np.cos(phi))
  279. f_x_sum = f_p1_x + f_p2_x
  280. f_y_sum = f_p1_y + f_p2_y
  281. m_sum = m_p1 + m_p2
  282. return f_x_sum, f_y_sum, m_sum
  283. for i in tqdm(range(n_iterations), desc="Optimizing axonal cloud distribution"):
  284. delta_xyphi = []
  285. for i, p_i in enumerate(inhibitory_axonal_clouds):
  286. delta_x = 0.
  287. delta_y = 0.
  288. delta_phi = 0.
  289. for p_j in inhibitory_axonal_clouds:
  290. if p_i is not p_j:
  291. del_x, del_y, del_m = pickle_repulsion(p_i, p_j)
  292. delta_x += del_x
  293. delta_y += del_y
  294. delta_phi += del_m
  295. delta_xyphi.append([delta_x, delta_y, delta_phi])
  296. # plotting(delta_xy)
  297. for i, p_i in enumerate(inhibitory_axonal_clouds):
  298. x_new = p_i.x + delta_xyphi[i][0]
  299. y_new = p_i.y + delta_xyphi[i][1]
  300. p_i.phi = p_i.phi + delta_xyphi[i][2]
  301. # Torus-like distance so pickles can cross borders and behave like a subset of a bigger distribution
  302. if x_new < max_x and x_new > 0.0:
  303. p_i.x = x_new
  304. elif x_new < 0.:
  305. p_i.x = max_x + x_new
  306. elif x_new > max_x:
  307. p_i.x = x_new - max_x
  308. if y_new < max_y and y_new > 0.0:
  309. p_i.y = y_new
  310. elif y_new < 0.:
  311. p_i.y = max_y + y_new
  312. elif y_new > max_y:
  313. p_i.y = y_new - max_y
  314. # plotting()
  315. # plotting()
  316. return inhibitory_axonal_clouds
  317. def create_grid_of_excitatory_neurons(max_x, max_y, number_of_excitatory_neurons_per_row, tuning_map):
  318. grid_x = number_of_excitatory_neurons_per_row
  319. grid_y = number_of_excitatory_neurons_per_row
  320. x = np.linspace(0, max_x, grid_x)
  321. y = np.linspace(0, max_y, grid_y)
  322. ex_neuron_positions = []
  323. ex_neuron_tuning = []
  324. head_dir_preference = np.zeros((grid_x, grid_y))
  325. for y_idx, yy in enumerate(y):
  326. for x_idx, xx in enumerate(x):
  327. p_noise = tuning_map(xx, yy)
  328. head_dir_preference[x_idx, y_idx] = p_noise
  329. ex_neuron_positions.append([xx, yy])
  330. ex_neuron_tuning.append(p_noise)
  331. return ex_neuron_positions, ex_neuron_tuning
  332. def test_run():
  333. '''
  334. Neural sheet
  335. '''
  336. # To go from the 3d data obtained in Peng 2017 to a 2d sheet is not straightforward
  337. # Assuming a depth of 100um, the density becomes a 1000 interneurons/mm2 and thus about 3000-4000 exneurons/mm2
  338. # A long 200um short 50um interneuron axon would cover about 90 partners
  339. # On the other hand, the typical contacts per excitatory neuron would be A_total/(A_I*N_I) = 30
  340. N_E = 400
  341. N_I = 40
  342. sheet_x = 300 * um
  343. sheet_y = 300 * um
  344. inhibitory_axon_long_axis = 100 * um
  345. inhibitory_axon_short_axis = 25 * um
  346. number_of_excitatory_neurons_per_row = int(np.sqrt(N_E))
  347. '''
  348. Tuning Maps
  349. '''
  350. # tuning_label = "Perlin"
  351. tuning_label = "Orientation map"
  352. if tuning_label == "Perlin":
  353. tuning_map = lambda x, y: noise.pnoise2(x / 100.0, y / 100.0, octaves=2) * np.pi
  354. elif tuning_label == "Orientation map":
  355. map = OrientationMap(20, 20, 7, sheet_x / um, sheet_y / um)
  356. map.improve(10)
  357. tuning_map = lambda x, y: map.orientation_map_float(x, y)
  358. ex_positions, ex_tunings = create_grid_of_excitatory_neurons(sheet_x / um, sheet_y / um,
  359. number_of_excitatory_neurons_per_row, tuning_map)
  360. # inhibitory_axonal_clouds = create_interneuron_sheet(N_I, inhibitory_axon_long_axis / um,
  361. # inhibitory_axon_short_axis / um, sheet_x / um,
  362. # sheet_y / um, random_seed=2, n_iterations=1000)
  363. inhibitory_axonal_clouds = create_interneuron_sheet_by_noise(ex_positions, ex_tunings, N_I,
  364. inhibitory_axon_long_axis / um,
  365. inhibitory_axon_short_axis / um, sheet_x / um,
  366. sheet_y / um, random_seed=3, n_iterations=10)
  367. plot_neural_sheet(ex_positions, ex_tunings, inhibitory_axonal_clouds)
  368. if __name__ == "__main__":
  369. test_run()