sim_edog.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. # code for simulating size tuning curves using the edog model
  2. # import
  3. import matplotlib.pyplot as plt
  4. from operator import itemgetter
  5. from edog.tools import*
  6. import spatint_utils
  7. from importlib import reload
  8. import os
  9. # reload modules
  10. reload(spatint_utils)
  11. # load params
  12. spatint_utils.plot_params()
  13. _, _, optocolor = spatint_utils.get_colors()
  14. class SimEdog:
  15. def __init__(self, start=1, stop=41):
  16. """Init class
  17. Parameters
  18. -----
  19. start: int
  20. starting width for inhibitory feedback kernel
  21. stop: int
  22. end width for inhibitory feedback kernel width + 1
  23. """
  24. self.start = start
  25. self.stop = stop
  26. self.step = 1
  27. self.lowbounds = None
  28. self.highbounds = None
  29. self.sids = None
  30. self.rfcsds = None
  31. self.smallrateds = None
  32. self.largerateds = None
  33. self.siss = None
  34. self.rfcsss = None
  35. self.wavenumber = None
  36. self.patch_diameter = None
  37. self.w_rc_mix = None
  38. def describe(self):
  39. """Get parameter boundaries in which experimental results are qualitatively replicated.
  40. Creates two dictionaries, one for the low boundaries, one for the high boundaries. Each
  41. dict contains one key for each parameter (smallrateds: response to preferred size;
  42. largerateds: response to largest stimulus; sids: surround suppression; rfcsds:
  43. preferred size) plus one key containing the intersection of all parameter boundaries
  44. (all)."""
  45. if self.sids is None:
  46. # run simulations if this has not been done
  47. self.iterate()
  48. # init boundary dicts
  49. lowbounds = {}
  50. highbounds = {}
  51. # check for which kernel size CT feedback increases responses to optimal stimulus size
  52. smallrateds_increaseis = (np.where(np.array(self.smallrateds) > 0))[0] + self.start
  53. lowbounds['smallrateds'] = np.min(smallrateds_increaseis)
  54. highbounds['smallrateds'] = np.max(smallrateds_increaseis)
  55. print('Responses to optimal stimulus diameter inreases with feedback for kernel '
  56. 'widths between %d and %d degree.'
  57. % (lowbounds['smallrateds'], highbounds['smallrateds']))
  58. # check for which kernel size CT feedback decreases responses to largest stimulus size
  59. largerateds_decreaseis = (np.where(np.array(self.largerateds) < 0))[0] + self.start
  60. lowbounds['largerateds'] = np.min(largerateds_decreaseis)
  61. highbounds['largerateds'] = np.max(largerateds_decreaseis)
  62. print('Responses to largest stimulus diameter decreases with feedback for kernel '
  63. 'widths between %d and %d degree.'
  64. % (lowbounds['largerateds'], highbounds['largerateds']))
  65. # check for which kernel size CT feedback increases surround suppression
  66. sids_increaseis = (np.where(np.array(self.sids) > 0))[0] + self.start
  67. lowbounds['sids'] = np.min(sids_increaseis)
  68. highbounds['sids'] = np.max(sids_increaseis)
  69. print('SI increases with feedback for kernel widths between %d and %d degree.'
  70. % (lowbounds['sids'], highbounds['sids']))
  71. # check for which kernel size CT feedback decreases receptive field center size
  72. rfcsds_decreaseis = (np.where(np.array(self.rfcsds) < 0))[0] + self.start
  73. lowbounds['rfcsds'] = np.min(rfcsds_decreaseis)
  74. highbounds['rfcsds'] = np.max(rfcsds_decreaseis)
  75. print('RFCS decreases with feedback for kernel widths between %d and %d degree.'
  76. % (lowbounds['rfcsds'], highbounds['rfcsds']))
  77. # check range for all paramters
  78. all_lowi = max(lowbounds.items(), key=itemgetter(1))[0]
  79. all_low = lowbounds[all_lowi]
  80. all_highi = min(highbounds.items(), key=itemgetter(1))[0]
  81. all_high = highbounds[all_highi]
  82. lowbounds['all'] = all_low
  83. highbounds['all'] = all_high
  84. print('Qualitatively matching range between %d and %d degree.'
  85. % (lowbounds['all'], highbounds['all']))
  86. # store dicts as attributes
  87. self.lowbounds = lowbounds
  88. self.highbounds = highbounds
  89. def iterate(self):
  90. """Run size tuning simulation for range of inhibitory FB kernel widths specified for
  91. conditions in which feedback is intact (weight: 1) and feedback is abolished (
  92. weight: 0). For each stimulation store percent change in surround suppression (
  93. sids); preferred size (rfcsds), response to preferred size (smallrateds),
  94. and response to the largest stimulus size (largerateds), as well as absolute values
  95. for suppression index (siss) and preferred size (rfcsss) in both conditions"""
  96. # init lists to store values
  97. sids = []
  98. rfcsds = []
  99. smallrateds = []
  100. largerateds = []
  101. siss = []
  102. rfcsss = []
  103. # create vector of inhibitory feedback widths
  104. inhib_fbs = np.arange(self.start, self.stop, self.step)
  105. # iterate over widths
  106. for inhib_fb in inhib_fbs:
  107. # run simulation
  108. curves = self.simulate(inhib_fb=inhib_fb)
  109. # compute target values
  110. sid, rfcsd, smallrated, largerated, sis, rfcss = self.characterize(curves=curves)
  111. # store values in list
  112. sids.append(sid)
  113. rfcsds.append(rfcsd)
  114. smallrateds.append(smallrated)
  115. largerateds.append(largerated)
  116. siss.append(sis)
  117. rfcsss.append(rfcss)
  118. # store as attributes
  119. self.sids = sids
  120. self.rfcsds = rfcsds
  121. self.smallrateds = smallrateds
  122. self.largerateds = largerateds
  123. self.siss = siss
  124. self.rfcsss = rfcsss
  125. def simulate(self, inhib_fb=None):
  126. """Run the edog model and return the two tuning curves
  127. Parameters
  128. ----
  129. inhib_fb: int
  130. width of inhibitory feedback kernel
  131. """
  132. # get parameters
  133. parentdir = os. path. dirname(os. getcwd())
  134. filename = parentdir + '/data/params_mouse.yaml'
  135. params = parse_parameters(filename)
  136. nt, nr, dt, dr = itemgetter("nt", "nr", "dt", "dr")(params["grid"])
  137. k_id, w_id, patch_diameter = itemgetter("k_id", "w_id", "patch_diameter")(
  138. params["stimulus"])
  139. A_g, a_g, B_g, b_g = itemgetter("A", "a", "B", "b")(params["ganglion"])
  140. w_rg, A_rg, a_rg = itemgetter("w", "A", "a")(params["relay"]["Krg"])
  141. w_rig, A_rig, a_rig = itemgetter("w", "A", "a")(params["relay"]["Krig"])
  142. w_rc_mix = itemgetter("w")(params["relay"]["Krc_mix"])
  143. A_rc_mix_in, a_rc_mix_in = itemgetter("A", "a")(params["relay"]["Krc_mix"]["Krc_in"])
  144. A_rc_mix_ex, a_rc_mix_ex = itemgetter("A", "a")(params["relay"]["Krc_mix"]["Krc_ex"])
  145. if inhib_fb is not None:
  146. # if input to inhib_fb is not none replace default value of inhibitory FB kernel
  147. # width
  148. a_rc_mix_in = inhib_fb * pq.deg
  149. # initiate variables
  150. tuning_curve = np.zeros([len(w_rc_mix), len(patch_diameter)])
  151. cen_size = np.zeros(len(w_rc_mix))
  152. supp_index = np.zeros(len(w_rc_mix))
  153. tuning_curve[:] = np.nan
  154. cen_size[:] = np.nan
  155. supp_index[:] = np.nan
  156. # run model
  157. for i, w in enumerate(w_rc_mix):
  158. network = create_spatial_network(nt=nt, nr=nr, dt=dt, dr=dr,
  159. A_g=A_g, a_g=a_g, B_g=B_g, b_g=b_g,
  160. w_rg=w_rg, A_rg=A_rg, a_rg=a_rg,
  161. w_rig=w_rig, A_rig=A_rig, a_rig=a_rig,
  162. w_rc_in=w, A_rc_in=A_rc_mix_in,
  163. a_rc_in=a_rc_mix_in, w_rc_ex=w,
  164. A_rc_ex=A_rc_mix_ex, a_rc_ex=a_rc_mix_ex)
  165. angular_freq = network.integrator.temporal_angular_freqs[int(w_id)]
  166. wavenumber = network.integrator.spatial_angular_freqs[int(k_id)]
  167. spatiotemporal_tuning = spatiotemporal_size_tuning(network=network,
  168. angular_freq=angular_freq,
  169. wavenumber=wavenumber,
  170. patch_diameter=patch_diameter)
  171. # store tuning curve, preferred size and suppression index
  172. tuning_curve[i, :] = spatiotemporal_tuning[0, :]
  173. # normalize by no feedback condition
  174. curves = tuning_curve / tuning_curve[0].max()
  175. # store anglular frequencies, FB weighs and patch diameters
  176. self.wavenumber = network.integrator.spatial_angular_freqs[int(k_id)]
  177. self.patch_diameter = patch_diameter
  178. self.w_rc_mix = w_rc_mix
  179. return curves
  180. def characterize(self, curves):
  181. """Compute differences between size tuning curves with and without feedback. Return
  182. percent change in surround suppression (sid); preferred size (rfcsd), response to
  183. preferred size (smallrated), and response to the largest stimulus size (largerated),
  184. as well as absolute values for suppression index (sis) and preferred size (rfcss)
  185. Parameters
  186. ----
  187. curves: ndarray
  188. 2D array containing size tuning curves in both conditions
  189. Returns
  190. ----
  191. sid: float
  192. percent change in suppression index
  193. rfcsd: float
  194. percent change in preferred size
  195. smallrated: float
  196. percent change in response to small stimulus
  197. largerated: float
  198. percent change in response to large stimulus
  199. sis: list
  200. suppression indices under both condtitions
  201. rfcss: list
  202. preferred sizes under both conditions
  203. """
  204. # get patch_diameters
  205. patch_diameter = self.patch_diameter
  206. # init lists to store target values
  207. sis = []
  208. rfcss = []
  209. smallrates = []
  210. largerates = []
  211. # preferred size is defined by stimulus where an increase in one degree does not
  212. # increase response by 0.05%. This is analogous to how visTRN size tuning is analyzed
  213. perc_thres = 0.05
  214. # iterate over the two conditions
  215. for icurve in range(curves.shape[0]):
  216. # get x and y
  217. y = curves[1-icurve, :] # start with control condition
  218. x = patch_diameter
  219. # compute RF center size: check where 1deg in increase of visual stim fails to
  220. # increase resp by perc_thres (output 2 to deal with divide by 0 -> will set
  221. # first increase to 100%)
  222. rel_change = np.divide(y[1:], y[:-1], out=np.ones_like(y[1:])*2, where=y[:-1] != 0)
  223. # compute percent change
  224. perc_change = (rel_change - 1) * 100
  225. # check where percent change is lower than perc_thres
  226. max_resp = np.where(perc_change < perc_thres)[0]
  227. # receptive field center size is either first max_resp or last stimulus size
  228. if any(max_resp):
  229. rfcs = max_resp[0]
  230. else:
  231. rfcs = x[-1]
  232. if icurve == 0:
  233. # store rfcs of control curve
  234. cont_rfcs = rfcs
  235. # compute surround size
  236. rfss = int(x[-1].item())
  237. # determine modeled firing rates
  238. smallrate_cont = y[cont_rfcs]
  239. smallrate = y[rfcs]
  240. largerate = y[rfss]
  241. # compute suppression index, if si is smaller 0 because of small increase for large
  242. # stimuli, set it to 0
  243. si = max(((smallrate - largerate) / smallrate), 0)
  244. # store values
  245. sis.append(si)
  246. rfcss.append(rfcs)
  247. smallrates.append(smallrate_cont)
  248. largerates.append(largerate)
  249. # compute percent change
  250. sid = ((sis[0] / sis[1]) - 1) * 100
  251. rfcsd = ((rfcss[0] / rfcss[1]) - 1) * 100
  252. smallrated = ((smallrates[0] / smallrates[1]) - 1) * 100
  253. largerated = ((largerates[0] / largerates[1]) - 1) * 100
  254. return sid, rfcsd, smallrated, largerated, sis, rfcss
  255. def plot(self, figsize=(6, 6), inhib_fb=None, quant=False, ax=None):
  256. """Plot the two tuning curves for inhibitory feedback kernel width
  257. Parameters
  258. ----
  259. figsize: tuple
  260. figure size (width, height)
  261. inhib_fb: int
  262. inhibitory feedback kernel width
  263. quant: bool
  264. if true print info regarding
  265. ax: mpl axis
  266. axis for plotting
  267. """
  268. # simulate data
  269. curves = self.simulate(inhib_fb=inhib_fb)
  270. # get xvalues
  271. patch_diameter = self.patch_diameter
  272. # set colors
  273. colors = [optocolor, 'k']
  274. if ax is None:
  275. # init fig if doesnt exist
  276. fig, ax = plt.subplots(figsize=figsize)
  277. # plot curves
  278. for curve, color in zip(curves, colors):
  279. ax.plot(patch_diameter, curve, '-', color=color)
  280. # layout
  281. ax.set_ylabel("Normalized response")
  282. ax.set_xlabel("Diameter ($\degree$)")
  283. ax.set_xticks((0, 25, 50, 75))
  284. ax.set_xticklabels((0, 25, 50, 75))
  285. ax.set_xlim([0, 75])
  286. ax.set_ylim([0, 1.5])
  287. ax.spines['bottom'].set_bounds(0, 75)
  288. if quant:
  289. # plot information about differences
  290. # init variables
  291. title = "Mixed feedback"
  292. wavenumber = self.wavenumber
  293. # get quant measures
  294. sid, rfcsd, smallrated, largerated, sis, rfcss = self.characterize(curves=curves)
  295. # insert model results
  296. ax.text(50, 0.1, 'opto si = %0.2f' % sis[1])
  297. ax.text(50, 0.14, 'control si = %0.2f' % sis[0])
  298. ax.text(50, 0.18, 'perc change si = %0.2f' % sid)
  299. ax.text(50, 0.22, 'opto rfcs = %0.2f' % rfcss[1])
  300. ax.text(50, 0.26, 'control rfcs = %0.2f' % rfcss[0])
  301. ax.text(50, 0.3, 'perc change rfcs = %0.2f' % rfcsd)
  302. ax.text(50, 0.34, 'perc change smallrate = %0.2f' % smallrated)
  303. ax.text(50, 0.38, 'perc change largerate = %0.2f' % largerated)
  304. ax.text(50, 0.42, 'model')
  305. # add legends and titles
  306. ax.set_title(title)
  307. fig.text(0.39, 0.97, "Patch grating ({})".format(round(wavenumber.item(), 2)),
  308. fontsize=12)
  309. # plot preferred size
  310. for rfcs, color in zip(rfcss[::-1], colors):
  311. ax.axvline(rfcs, color=color)