OSCR

Central complex representations of self-movement are sufficient to compute wind direction in flight.

Code ↔ Paper

14 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 14 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § RESULTS › Generating and validating dynamic encoding models for PFNs ↔ Python/PFN_Model_Predictions/PFN_model.py, lines 567–655 · score 0.75 · steady state, single modality, Speed tuning, Direction tuning, rises, decays
  2. [2] § MATERIALS AND METHODS › Free-flight behavior recording and analysis › Heading estimation for measured and simulated trajectories ↔ Python/drosophila_body_orientation_predictor/Notebooks/drosophila_body_orientation_predictor.ipynb, lines 747–879 · score 0.67 · single wind direction, multiple wind direction, sine, thrust, rotational, Drosophila
  3. [3] § RESULTS › Generating and validating dynamic encoding models for PFNs ↔ MATLAB/FBmodel/model_code/on_off_de.m, the whole file · a weak match · score 0.66 · steady state, single modality, Speed tuning, rises, transient, PB
  4. [4] § MATERIALS AND METHODS › Free-flight behavior recording and analysis › Heading estimation for measured and simulated trajectories ↔ Python/drosophila_body_orientation_predictor/Notebooks/helper_functions.py, lines 274–394 · score 0.65 · single wind direction, multiple wind direction, sine, rotational, Drosophila, body
  5. [5] § RESULTS › Ambient wind direction is observable from PFN activity during saccadic flight turns ↔ MATLAB/FBmodel/model_code/FBmodel7.m, lines 1–45 · score 0.64 · egocentric optic flow, egocentric airflow direction, optic flow speed, frame, PFN, heading
  6. [6] § MATERIALS AND METHODS › Free-flight behavior recording and analysis › Dynamic encoding model for PFNs ↔ MATLAB/FBmodel/model_code/FBmodel7.m, lines 1–45 · score 0.63 · egocentric airflow, Bump position, optic flow, sinusoid, amplitude, PFNd
  7. [7] § RESULTS › Generating and validating dynamic encoding models for PFNs ↔ Python/PFN_Model_Predictions/PFN_model.py, lines 567–655 · score 0.61 · direction tuning curves, single modality, speed tuning, AF, coefficient, offset
  8. [8] § RESULTS › Generating and validating dynamic encoding models for PFNs ↔ MATLAB/FBmodel/model_code/on_off_de.m, the whole file · a weak match · score 0.58 · single modality, tuning curves, speed tuning, AF, coefficient, offset
  9. [9] § MATERIALS AND METHODS › Imaging data analysis ↔ MATLAB/Nagel2pNoRMCorre/2pCode/Register_ROIs-main/NoRMCorre/NoRMCorreSetParms.m, lines 1–65 · score 0.58 · NoRMCorre, motion correction, rigid, shifts, frames
  10. [10] § MATERIALS AND METHODS › Free-flight behavior recording and analysis › Artificial neural networks ↔ notebooks/data_correction_and_model_training.ipynb, lines 1567–1647 · score 0.57 · validation loss, Keras, hidden layer, dropout, training, epochs
  11. [11] § MATERIALS AND METHODS › Free-flight behavior recording and analysis › Artificial neural networks ↔ utils/utils.py, lines 976–1019 · score 0.56 · hidden layer, ReLU, adam, activation, Loss, MSE
  12. [12] § RESULTS › The wind speed–to–ground speed ratio defines two solution regimes in both the ANN and real flies ↔ Python/AnalyzeRealFliesVSANNs/extract_trajectories_from_orcoflashStupski.py, lines 19–88 · score 0.55 · wind tunnel, ground speed, Real flies, ambient, trajectories
  13. [13] § MATERIALS AND METHODS › Statistical testing ↔ MATLAB/Nagel2pNoRMCorre/2pCode/circ_kuipertest.m, lines 1–84 · score 0.54 · Kolmogorov Smirnov, philippberens, Kuiper, circular
  14. [14] § RESULTS › Ambient wind direction can be decoded from PFN representations by a simple feedforward ANN ↔ Python/drosophila_body_orientation_predictor/Notebooks/drosophila_body_orientation_predictor.ipynb, lines 1–114 · score 0.52 · body orientations, wind speeds, wind direction, training, predicted, flight

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

Python · 802 lines · 31 KB · GPL-3.0 · 2 matches

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. #import figurefirst as fifi
  4. import utils
  5. from utils import wrapToPi
  6. class PFN:
  7. """ Model of PFN neural responses to air speed/direction & optic flow speed/direction.
  8. Will generate sinusoidal bumps for the compass/EB and in the PB and FB (for PFNs).
  9. Takes parameters previously determined from fitting for time-courses
  10. of PFN calcium intensity and bump position in the PB.
  11. """
  12. def __init__(self, PFNd_params=None, PFNv_params=None, PFNpc_params=None, PFNa_params=None):
  13. """ Initialize the model.
  14. PFNd_params:
  15. PFNv_params:
  16. PFNpc_params:
  17. PFNa_params:
  18. """
  19. # Set parameters
  20. if PFNd_params is None:
  21. PFNd_params = np.array([[0.5, 1.0, 1.4, 0.88, 0.4, 1.23, 0.0, 1.0],
  22. [0.4, 1.0, 1.4, 0.38, 1.0, 1.35, 0.0, 0.0]]) #0.4
  23. if PFNv_params is None:
  24. PFNv_params = np.array([[0.0, 1.0, -1.74, 0.88, 0.4, 1.23, 0.0, 1.0], # v params [0,0] = 0.5 fitted
  25. [0.4, 1.0, -1.74, 0.38, 1.0, 1.35, 0.0, 0.0]]) #0.4
  26. #if PFNpc_params is None:
  27. # PFNpc_params = np.array([[1.24, 0.01, 0.97, 2.6, 0.0, 1.77, -0.3, 1.0],
  28. # [0.01, 1.00, 2.10, 15.0, 0.6, 2.00, 0.0, 1.0]]) # pc params [1,0] = 0.01 fitted
  29. if PFNpc_params is None:
  30. PFNpc_params = np.array([[1.16, 0.01, 0.97, 2.6, 0.0, 1.77, -0.3, 1.0],
  31. [1.0, 0.01, 0.2, 2.6, 0.0, 6, -0.3, 1.0],
  32. [0.01, 1.0, 2.1, 15.0, 0.6, 2.0, 0.0, 1.0], # pc params [2,0] = 0.01 fitted
  33. [0.0, 1.0, 2.1, 15.0, 0.6, 2.0, 0.0, 0.0]])
  34. if PFNa_params is None:
  35. PFNa_params = np.array([[1.34, 0.57, 1.12, 0.48, 0.22, -0.24, 1.57, 1.0],
  36. [0.2, -0.3, 1.12, 0.48, 1.0, -0.24, 1.57, 0.0]]) # a params [1,0] = 0.2 fitted
  37. # Store model parameters for each neuron type
  38. self.model_param = {'PFNd': PFNd_params,
  39. 'PFNv': PFNv_params,
  40. 'PFNpc': PFNpc_params,
  41. 'PFNa': PFNa_params}
  42. # Set inputs
  43. self.tsim = np.array(0.0)
  44. self.phi = np.array(0.0)
  45. self.gamma = np.array(0.0)
  46. self.a = np.array(0.0)
  47. self.psi = np.array(0.0)
  48. self.g = np.array(0.0)
  49. self.initcond = np.zeros((9,2))
  50. # Outputs
  51. self.res = {}
  52. self.heatmap = {}
  53. self.allpts = {}
  54. self.new_inits = np.zeros((9,2))
  55. def run(self, tsim=None, phi=None, a=None, gamma=None, g=None, psi=None, initcond=None, unwrap=False):
  56. """ Set inputs & run.
  57. tsim: time [s]
  58. phi: heading angle [rad]
  59. a: air speed [m/s]
  60. gamma: air speed direction [rad]
  61. g: ground speed [m/s]
  62. psi: ground speed direction [rad]
  63. """
  64. # Set inputs
  65. self.tsim = tsim
  66. self.phi = phi #np.zeros_like(phi)
  67. self.gamma = gamma
  68. self.a = a
  69. self.psi = psi
  70. self.g = g
  71. # Run model
  72. self.stims = {'t': self.tsim,
  73. 'heading': self.phi,
  74. 'Atheta': self.gamma,
  75. 'Amag': self.a,
  76. 'Otheta': self.psi,
  77. 'Omag': self.g,
  78. }
  79. # Set initial conditions
  80. if initcond is not None:
  81. # print('not None')
  82. self.initcond = initcond
  83. else:
  84. print('No initials')
  85. # Run model
  86. self.res, self.heatmap, self.new_inits = FBmodel_obs(self.stims,
  87. self.model_param['PFNd'],
  88. self.model_param['PFNv'],
  89. self.model_param['PFNpc'],
  90. self.model_param['PFNa'],
  91. self.initcond
  92. )
  93. # Unwrap bump position
  94. if unwrap:
  95. wrap_data = ['PFNd_bumpFB', 'PFNv_bumpFB', 'PFNpc_bumpFB', 'PFNa_bumpFB']
  96. for w in wrap_data:
  97. self.res[w] = np.unwrap(self.res[w])
  98. def runstep(self, tsim=None, phi=None, a=None, gamma=None, g=None, psi=None, unwrap=False, start=None, win=None, initcond=None):
  99. """ Set inputs & run FBmodel_obs for a portion of the model.
  100. tsim: time [s]
  101. phi: heading angle [rad]
  102. a: air speed [m/s]
  103. gamma: air speed direction [rad]
  104. g: ground speed [m/s]
  105. psi: ground speed direction [rad]
  106. start_fin: start and end frames for window
  107. """
  108. # Set inputs
  109. self.tsim = tsim
  110. self.phi = phi
  111. self.gamma = gamma
  112. self.a = a
  113. self.psi = psi
  114. self.g = g
  115. self.start = start
  116. self.win = win
  117. self.initcond = initcond
  118. # Run model
  119. self.stims = {'t': self.tsim[self.start:self.start+self.win],
  120. 'heading': self.phi[self.start:self.start+self.win],
  121. 'Atheta': self.gamma[self.start:self.start+self.win],
  122. 'Amag': self.a[self.start:self.start+self.win],
  123. 'Otheta': self.psi[self.start:self.start+self.win],
  124. 'Omag': self.g[self.start:self.start+self.win],
  125. }
  126. self.res,self.new_inits = FBmodel_obs(self.stims,
  127. self.model_param['PFNd'],
  128. self.model_param['PFNv'],
  129. self.model_param['PFNpc'],
  130. self.model_param['PFNa'],
  131. #self.initcond
  132. )
  133. # Unwrap bump position
  134. if unwrap:
  135. wrap_data = ['PFNd_bumpFB', 'PFNv_bumpFB', 'PFNpc_bumpFB', 'PFNa_bumpFB']
  136. for w in wrap_data:
  137. self.res[w] = np.unwrap(self.res[w])
  138. def plot_stimulus(self, fig_size=1):
  139. stim_keys = list(self.stims.keys())
  140. n_k = len(stim_keys)
  141. fig, ax = plt.subplots(n_k-1,1, figsize=( fig_size * 2, fig_size * 1.5 * n_k), sharex=True, dpi=100)
  142. for n, k in enumerate(stim_keys[1:]):
  143. ax[n].plot(self.stims['t'], self.stims[k])
  144. ax[n].set_ylabel(k)
  145. ax[n].set_yticks((min(self.stims[k])-5,max(self.stims[k]+5)))
  146. ax[0].set_ylim(-np.pi,np.pi)
  147. ax[1].set_ylim(-np.pi,np.pi)
  148. ax[3].set_ylim(-np.pi,np.pi)
  149. def plot_bump(self, fig_size=1):
  150. """ Plot bump position & amplitude over time for each neuron type.
  151. """
  152. keys = list(self.res.keys())
  153. n_key = len(keys)
  154. n_col = 4
  155. n_row = np.ceil(n_key / n_col).astype(int)
  156. plot_order = np.hstack((np.arange(1, n_key, step=1), np.array(0)))
  157. fig, ax = plt.subplots(n_row, n_col, figsize=(fig_size * 3 * n_col, fig_size * 2 * n_row), sharex=True)
  158. for n, k in enumerate(plot_order):
  159. ax.flat[n].plot(self.stims['t'], self.res[keys[k]].T)
  160. ax.flat[n].set_title(keys[k])
  161. for a in ax.flat:
  162. a.set_xticks(np.arange(0, self.stims['t'][-1], step=10))
  163. for y in [4,5,6,7,8,9,10,11,24,25,26,27,28]:
  164. ax.flat[y].set_yticks(np.arange(-4, 4.1, step=1))
  165. for y2 in [0,1,2,3,12,13,14,15,16,17,18,19,20,21,22,23]:
  166. ax.flat[y2].set_yticks(np.arange(-0.5, 3.1, step = 1))
  167. fig.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0.6, hspace=0.4)
  168. def plot_heatmap(self, fig_size=7, cmap='jet'):
  169. """ Plot bump position & amplitude as a heatmap over time for each neuron type.
  170. """
  171. fig, ax = plt.subplots(1, 9, figsize=(fig_size, 1.2 * fig_size), sharex=True, sharey=True, dpi=100)
  172. pos = ax[0].imshow(self.heatmap['EPG'], vmin=0, vmax=1.2, cmap=cmap)
  173. ax[1].imshow(self.heatmap['PFNdL_pb'], vmin=0, vmax=1.2, cmap=cmap)
  174. ax[2].imshow(self.heatmap['PFNdR_pb'], vmin=0, vmax=1.2, cmap=cmap)
  175. ax[3].imshow(self.heatmap['PFNvL_pb'], vmin=0, vmax=1.2, cmap=cmap)
  176. ax[4].imshow(self.heatmap['PFNvR_pb'], vmin=0, vmax=1.2, cmap=cmap)
  177. ax[5].imshow(self.heatmap['PFNpcL_pb'], vmin=0, vmax=1.2, cmap=cmap)
  178. ax[6].imshow(self.heatmap['PFNpcR_pb'], vmin=0, vmax=1.2, cmap=cmap)
  179. ax[7].imshow(self.heatmap['PFNaL_pb'], vmin=0, vmax=1.2, cmap=cmap)
  180. ax[8].imshow(self.heatmap['PFNaR_pb'], vmin=0, vmax=1.2, cmap=cmap)
  181. ax[0].set_title('EPG')
  182. fig.colorbar(pos,ax=ax[0])
  183. ax[1].set_title('PFNd PB')
  184. ax[3].set_title('PFNv PB')
  185. ax[5].set_title('PFNpc PB')
  186. ax[7].set_title('PFNa PB')
  187. for a in ax.flat:
  188. a.set_aspect(0.5)
  189. a.set_xlim(0, 16)
  190. #fifi.mpl_functions.adjust_spines(a, [])
  191. # a.set_tick_params(left=None, right=None, top=None, bottom=None)
  192. def FBmodel_obs(gust_res, p_PFNd, p_PFNv, p_PFNpc, p_PFNa, initcond=None):
  193. # like FBmodel_observ() in Matlab
  194. # initcond is a dict with the following keys:
  195. # dL, dR, vL, vR, pcL, pcR, aL, aR
  196. # each key references a 1x2 (tuple? array?) with col=0 AF and col=1 OF initial values
  197. p_bump = 2
  198. heading = gust_res['heading']
  199. thva = gust_res['Atheta']
  200. spda = gust_res['Amag']
  201. thvo = gust_res['Otheta']
  202. spdo = gust_res['Omag']
  203. t = gust_res['t']
  204. if initcond is None:
  205. print('it is None')
  206. AFinput = np.array([thva[0],spda[0]])
  207. OFinput = np.array([thvo[0],spdo[0]])
  208. bumpinput = np.array([heading[0],thva[0],spda[0]])
  209. initcond = BuildSSInitial(p_PFNd, p_PFNv,
  210. p_PFNpc, p_PFNa,
  211. AFinput,
  212. OFinput,
  213. bumpinput
  214. )
  215. #print(initcond)
  216. Rinputs = np.vstack((thva, spda, thvo, spdo, t))
  217. Linputs = np.vstack((-thva, spda, -thvo, spdo, t))
  218. # If you want to make PFNv unresponsive to airflow, run the following line:
  219. #p_PFNv[0,0] = 0
  220. new = np.zeros((18, len(t)))
  221. PFNd_amp = np.zeros((2, len(t)))
  222. PFNd_amp[0, :],new[0,:],new[1,:] = PFNd_integ(p_PFNd, Linputs,initcond[0,:])
  223. PFNd_amp[1, :],new[2,:],new[3,:] = PFNd_integ(p_PFNd, Rinputs,initcond[1,:])
  224. PFNv_amp = np.zeros((2, len(t)))
  225. PFNv_amp[0, :],new[4,:],new[5,:] = PFNd_integ(p_PFNv, Linputs,initcond[2,:])
  226. PFNv_amp[1, :],new[6,:],new[7,:] = PFNd_integ(p_PFNv, Rinputs,initcond[3,:])
  227. PFNpc_amp = np.zeros((2, len(t)))
  228. PFNpc_amp[0, :],new[8,:],new[9,:] = PFNpc_integ2(p_PFNpc, Linputs,initcond[4,:])
  229. PFNpc_amp[1, :],new[10,:],new[11,:] = PFNpc_integ2(p_PFNpc, Rinputs,initcond[5,:])
  230. PFNa_amp = np.zeros((2, len(t)))
  231. PFNa_amp[0, :],new[12,:],new[13,:] = PFNa_integ(p_PFNa, Linputs,initcond[6,:])
  232. PFNa_amp[1, :],new[14,:],new[15,:] = PFNa_integ(p_PFNa, Rinputs,initcond[7,:])
  233. # Bump movement code.
  234. # This will allow us to test the information in the system if bump movement
  235. # is driven by heading, airflow direction, both, or neither.
  236. ha = np.zeros_like(spda)
  237. for x in range(len(ha)):
  238. if spda[x]>0: ha[x]=1
  239. # If airflow drives bump movement, use bumpmdl_de(); if not, use np.zeros().
  240. # bumppos = bumpmdl_de(p_bump, thva, t, ha,initcond[8,0]) # comment out for noAF
  241. bumppos = np.zeros((len(t),)) # comment in for noAF
  242. #print(bumppos)
  243. new[16,:] = bumppos.copy()
  244. new[17,:] = bumppos.copy()
  245. ## If heading drives bump movement: (otherwise, comment out)
  246. bumppos = bumppos - heading # bumppos in radians
  247. PFNd_bump = np.zeros((2, len(t)))
  248. # PFNd_bump[0, :] = wrapToPi(bumppos + np.pi/4)
  249. # PFNd_bump[1, :] = wrapToPi(bumppos - np.pi/4)
  250. PFNd_bump[0, :] = wrapToPi(bumppos)
  251. PFNd_bump[1, :] = wrapToPi(bumppos)
  252. PFNv_bump = np.zeros((2, len(t)))
  253. # PFNv_bump[0, :] = wrapToPi(bumppos + np.pi/4)
  254. # PFNv_bump[1, :] = wrapToPi(bumppos - np.pi/4)
  255. PFNv_bump[0, :] = wrapToPi(bumppos)
  256. PFNv_bump[1, :] = wrapToPi(bumppos)
  257. PFNpc_bump = np.zeros((2, len(t)))
  258. # If PFNpc bump moves like other PFN bumps, use:
  259. #bumppos_pc = bumppos.copy()
  260. # If PFNpc bump doesn't move with heading, use one of the next two lines:
  261. #bumppos_pc = new[16,:]
  262. #bumppos_pc = bumpmdl_de(p_bump,thva,t,ha,initcond[8,0])
  263. # If PFNpc bump doesn't move with airflow, use:
  264. #bumppos_pc = -heading
  265. # If PFNpc bump doesn't move with airflow or heading, use:
  266. bumppos_pc = np.zeros_like(bumppos)
  267. # PFNpc_bump[0, :] = wrapToPi(bumppos_pc + np.pi/4)
  268. # PFNpc_bump[1, :] = wrapToPi(bumppos_pc - np.pi/4)
  269. PFNpc_bump[0, :] = wrapToPi(bumppos_pc)
  270. PFNpc_bump[1, :] = wrapToPi(bumppos_pc)
  271. PFNa_bump = np.zeros((2, len(t)))
  272. # PFNa_bump[0, :] = bumppos + np.pi/4
  273. # PFNa_bump[1, :] = bumppos - np.pi/4
  274. PFNa_bump[0, :] = bumppos
  275. PFNa_bump[1, :] = bumppos
  276. for i,v in enumerate(wrapToPi(thva)):
  277. if spda[i]>0:
  278. if v>0.78 or v<-2.35:
  279. PFNa_bump[0, i] = PFNa_bump[0, i]+np.pi
  280. elif v<-0.78 or v>2.35:
  281. PFNa_bump[1, i] = PFNa_bump[1, i]+np.pi
  282. PFNa_bump[0, :] = wrapToPi(PFNa_bump[0, :])
  283. PFNa_bump[1, :] = wrapToPi(PFNa_bump[1, :])
  284. res = {'bump': wrapToPi(bumppos),
  285. 'PFNd_amp': PFNd_amp,
  286. 'PFNv_amp': PFNv_amp,
  287. 'PFNpc_amp': PFNpc_amp,
  288. 'PFNa_amp': PFNa_amp,
  289. 'PFNd_bumpL': PFNd_bump[0,:],
  290. 'PFNd_bumpR': PFNd_bump[1,:],
  291. 'PFNv_bumpL': PFNv_bump[0,:],
  292. 'PFNv_bumpR': PFNv_bump[1,:],
  293. 'PFNpc_bumpL': PFNpc_bump[0,:],
  294. 'PFNpc_bumpR': PFNpc_bump[1,:],
  295. 'PFNa_bumpL': PFNa_bump[0,:],
  296. 'PFNa_bumpR': PFNa_bump[1,:],
  297. 'PFNd_ampL': PFNd_amp[0, :],
  298. 'PFNd_ampR': PFNd_amp[1, :],
  299. 'PFNv_ampL': PFNv_amp[0, :],
  300. 'PFNv_ampR': PFNv_amp[1, :],
  301. 'PFNpc_ampL': PFNpc_amp[0, :],
  302. 'PFNpc_ampR': PFNpc_amp[1, :],
  303. 'PFNa_ampL': PFNa_amp[0, :],
  304. 'PFNa_ampR': PFNa_amp[1, :],
  305. }
  306. PFNd_bumpFB, PFNd_ampFB = sumPFNvecs(PFNd_bump, PFNd_amp)
  307. PFNv_bumpFB, PFNv_ampFB = sumPFNvecs(PFNv_bump, PFNv_amp)
  308. PFNpc_bumpFB, PFNpc_ampFB = sumPFNvecs(PFNpc_bump, PFNpc_amp)
  309. PFNa_bumpFB, PFNa_ampFB = sumPFNvecs(PFNa_bump, PFNa_amp)
  310. res['PFNd_ampFB'] = PFNd_ampFB
  311. res['PFNv_ampFB'] = PFNv_ampFB
  312. res['PFNpc_ampFB'] = PFNpc_ampFB
  313. res['PFNa_ampFB'] = PFNa_ampFB
  314. res['PFNd_bumpFB'] = PFNd_bumpFB
  315. res['PFNv_bumpFB'] = PFNv_bumpFB
  316. res['PFNpc_bumpFB'] = PFNpc_bumpFB
  317. res['PFNa_bumpFB'] = PFNa_bumpFB
  318. new = new[:,-1]
  319. new_inits = new.reshape((9,2))
  320. # Heatmaps
  321. #angle_map = np.arange(-135, 181, step=45)
  322. angle_map = np.arange(-np.pi,np.pi,step=np.pi/4)
  323. n_angles = angle_map.shape[0]
  324. n_sim = t.shape[0]
  325. EPG = np.zeros((n_angles, n_sim))
  326. PFNdL_pb = np.zeros((n_angles, n_sim))
  327. PFNdR_pb = np.zeros((n_angles, n_sim))
  328. PFNvL_pb = np.zeros((n_angles, n_sim))
  329. PFNvR_pb = np.zeros((n_angles, n_sim))
  330. PFNpcL_pb = np.zeros((n_angles, n_sim))
  331. PFNpcR_pb = np.zeros((n_angles, n_sim))
  332. PFNaL_pb = np.zeros((n_angles, n_sim))
  333. PFNaR_pb = np.zeros((n_angles, n_sim))
  334. for i in range(len(t)):
  335. EPG[:, i] = 0.5 + 0.5 * np.cos(angle_map - bumppos[i])
  336. PFNdL_pb[:, i] = (PFNd_amp[0, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNd_bumpL'][i]))
  337. PFNdR_pb[:, i] = (PFNd_amp[1, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNd_bumpR'][i]))
  338. PFNvL_pb[:, i] = (PFNv_amp[0, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNv_bumpL'][i]))
  339. PFNvR_pb[:, i] = (PFNv_amp[1, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNv_bumpR'][i]))
  340. PFNpcL_pb[:, i] = (PFNpc_amp[0, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNpc_bumpL'][i]))
  341. PFNpcR_pb[:, i] = (PFNpc_amp[1, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNpc_bumpR'][i]))
  342. PFNaL_pb[:, i] = (PFNa_amp[0, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNa_bumpL'][i]))
  343. PFNaR_pb[:, i] = (PFNa_amp[1, i] + 0.5) * (0.5 + 0.5 * np.cos(angle_map - res['PFNa_bumpR'][i]))
  344. heatmap = {}
  345. heatmap['EPG'] = np.vstack((EPG,EPG[0, :])).T
  346. heatmap['PFNdL_pb'] = np.vstack((PFNdL_pb,PFNdL_pb[0, :])).T
  347. heatmap['PFNdR_pb'] = np.vstack((PFNdR_pb,PFNdR_pb[0, :])).T
  348. heatmap['PFNvL_pb'] = np.vstack((PFNvL_pb,PFNvL_pb[0, :])).T
  349. heatmap['PFNvR_pb'] = np.vstack((PFNvR_pb,PFNvR_pb[0, :])).T
  350. heatmap['PFNpcL_pb'] = np.vstack((PFNpcL_pb,PFNpcL_pb[0, :])).T
  351. heatmap['PFNpcR_pb'] = np.vstack((PFNpcR_pb,PFNpcR_pb[0, :])).T
  352. heatmap['PFNaL_pb'] = np.vstack((PFNaL_pb,PFNaL_pb[0, :])).T
  353. heatmap['PFNaR_pb'] = np.vstack((PFNaR_pb,PFNaR_pb[0, :])).T
  354. return res, heatmap, new_inits
  355. def circshift(arr, shift): # don't need this, just np.roll?
  356. """
  357. Circularly shift the elements of the array by the specified shift amount.
  358. Parameters:
  359. arr (array-like): The input array.
  360. shift (int): The number of positions by which elements are shifted.
  361. Returns:
  362. np.ndarray: The circularly shifted array.
  363. """
  364. n = len(arr)
  365. shift = shift % n # Ensure shift is within the range [0, n)
  366. return np.concatenate((arr[-shift:], arr[:-shift]))
  367. def bumpmdl_de(params, thetavec, t, h, initcond):
  368. # output radians
  369. tau = 2#np.atleast_2d(params)[0]
  370. res = np.zeros_like(t)
  371. #res[0] = thetavec[0]*h[0]
  372. res[0] = initcond
  373. for i in range(len(t) - 1):
  374. coeff = -(utils.wrapToPi(np.array(thetavec[i] * h[i])))
  375. res[i + 1] = res[i] + (coeff - res[i]) * ((t[i + 1] - t[i]) / tau)
  376. return res
  377. def A_response_de(params, inputs, initcond):
  378. # This function generates a vector of intensity values over time for one
  379. # half of the PB innervated by PFNa, to a sequence of single-modality experience (either AF or OF).
  380. # Setup with arguments.
  381. # inputs are sensory info
  382. thetavec = inputs[0, :] # radians, (+) is ipsi to the PB half
  383. speedvec = np.fmax(np.zeros_like(inputs[1,:]),inputs[1, :]) # cm/s, zero-floored
  384. t = inputs[2, :] # seconds
  385. # Parameters
  386. a = params[0] # like Amp
  387. c = params[1] # coeff for second cosine term
  388. prefdir = params[2] # in rads; parameter formerly known as theta0
  389. b = params[3] # like offset
  390. r = params[4] # offset to arrive at steady-state
  391. d = params[5] # amplitude of steady-state driven by direction tuning
  392. tau = params[6] # time constant
  393. flip = params[7] # for rising OF response =0 vs. falling AF response !=0
  394. #% Set anchoring values for numerical solution: C is like max value,
  395. # T is full tau expression, res(1) is the initial result
  396. # (assumed to be the steady state if the first timestep inputs were held constant).
  397. C = a * (1 - np.exp(-speedvec)) * (np.cos(thetavec - prefdir) ** 2 + c * np.cos(thetavec - prefdir + np.pi) + b)
  398. T = tau
  399. ratio = r + d * np.cos(thetavec - prefdir)
  400. res = np.zeros_like(t)
  401. #res[0] = C[0]
  402. res[0] = initcond
  403. # Handle direction of response (rise/decay).
  404. if flip != 0: # if AF response curve
  405. res[0] = C[0]-initcond
  406. ratio = 1 - ratio
  407. # Calculate numerical solution using the given inputs and parameters.
  408. for i in range(len(t) - 1):
  409. dt = (t[i + 1] - t[i])
  410. res[i + 1] = (res[i] + ((ratio[i]) * C[i] - res[i]) * (dt / (T)))
  411. r = res
  412. if flip != 0: # if AF response curve
  413. # res = np.maximum(0, C - res)
  414. res = np.abs(C - res)
  415. r = C-r
  416. return res, r
  417. def D_response_de(params, inputs, initcond):
  418. # This function generates a vector of intensity values over time for one
  419. # half of the PB innervated by PFNd, to a sequence of single-modality experience (either AF or OF).
  420. # Setup with arguments.
  421. # inputs are sensory info
  422. thetavec = inputs[0, :] # radians, (+) is ipsi to the PB half
  423. speedvec = np.fmax(np.zeros_like(inputs[1,:]),inputs[1, :]) # cm/s, zero-floored
  424. t = inputs[2, :] # seconds
  425. # Parameters
  426. a = params[0] # like Amp
  427. c = params[1] # speed coefficient for max, =1 for non-speed-tuned PFNs
  428. prefdir = params[2] # in rads; parameter formerly known as theta0
  429. b = params[3] # like offset
  430. ratio = abs(params[4]) # ratio of steady-state to max amp; =0 for steady-state=0;
  431. # ratio=1 for PFNd OF
  432. tau = params[5] # time constant (offset)
  433. tauslope = params[6] # speed coefficient for tau
  434. flip = params[7] # for rising OF response =0 vs. falling AF response !=0
  435. # Set anchoring values for numerical solution: C is like max value, T is
  436. # full tau expression, res(1) is the initial result (assumed to be the
  437. # steady state if the first timestep inputs were held constant).
  438. C = a * (1 - np.exp(c * -speedvec)) * (np.cos(thetavec - prefdir) + b)
  439. T = tau + tauslope * np.exp(speedvec / 100)
  440. res = np.zeros_like(t)
  441. #res[0] = C[0]
  442. res[0] = initcond
  443. if flip != 0: # if AF response curve
  444. res[0] = C[0]-initcond
  445. ratio = 1 - ratio
  446. # Calculate numerical solution using the given inputs and parameters.
  447. for i in range(len(t) - 1):
  448. dt = (t[i + 1] - t[i])
  449. res[i + 1] = (res[i] + ((ratio) * C[i] - res[i]) * (dt / (T[i])))
  450. r = res
  451. # Handle negative values for AF response curves.
  452. if flip != 0: # if AF response curve
  453. res = np.maximum(0, C - res)
  454. # res = np.abs(C - res)
  455. r = C-r
  456. return res, r
  457. def PC_response_de(params, inputs, initcond):
  458. # This function generates a vector of intensity values over time for one
  459. # half of the PB to a sequence of single-modality experience (either AF or
  460. # OF). It can handle PFNpc and PFNa offset responses if given the correct
  461. # parameters (see 'script_20240404.m')
  462. # 'params' is a 2x8 matrix where first row is for onset/stimulus responses
  463. # and second row is for offset responses.
  464. # REFERENCE: on_off_de.m in MatLab
  465. # Setup with arguments.
  466. # inputs are sensory info
  467. thetavec = inputs[0, :] # radians, (+) is ipsi to the PB half
  468. speedvec = np.fmax(np.zeros_like(inputs[1,:]),inputs[1, :]) # cm/s, zero-floored
  469. t = inputs[2, :] # seconds
  470. ## Alt: get stimulus OFF periods (speed==0) and change speedvec to produce desired results
  471. offs = np.zeros_like(t)
  472. offinds = [i for i in range(len(speedvec)) if speedvec[i]==0]
  473. offs[offinds] = 1
  474. dspd = np.diff(speedvec)
  475. offs = np.zeros_like(t)
  476. for i in range(len(speedvec)-1):
  477. if dspd[i] !=0:
  478. speedvec[i+1] = speedvec[i]+(0.5*abs(dspd[i]))
  479. else:
  480. speedvec[i+1] = speedvec[i]
  481. # Parameters
  482. # a is like Amp
  483. a = np.ones_like(t)*params[0,0]
  484. a[np.nonzero(offs)[0]] = params[1,0]
  485. # c is speed coefficient for max, =1 for non-speed-tuned PFNs
  486. c = np.ones_like(t)*params[0,1] ## comment out `*params[0,1]` for no speed tuning
  487. c[np.nonzero(offs)[0]] = params[1,1] ## comment this line out entirely for no speed tuning
  488. # prefdir is in rads; parameter formerly known as theta0
  489. prefdir = np.ones_like(t)*params[0,2]
  490. prefdir[np.nonzero(offs)[0]] = params[1,2]
  491. # b is the tuning curve offset/shift term
  492. b = np.ones_like(t)*params[0,3]
  493. b[np.nonzero(offs)[0]] = params[1,3]
  494. # ratio is of steady-state to max amp; =0 for steady-state=0; ratio=1 for PFNd OF
  495. ratio = np.ones_like(t)*np.abs(params[0,4])
  496. ratio[np.nonzero(offs)[0]] = params[1,4]
  497. # tau is time constant with no dependency
  498. tau = np.ones_like(t)*params[0,5]
  499. tau[np.nonzero(offs)[0]] = params[1,5]
  500. # tauslope is speed coefficient for tau
  501. tauslope = np.ones_like(t)*params[0,6]
  502. tauslope[np.nonzero(offs)[0]] = params[1,6]
  503. # flip is to indicate whether activity is rising OF response =0 vs. falling AF response !=0
  504. flip = np.ones_like(t)*params[0,7]
  505. flip[np.nonzero(offs)[0]] = params[1,7]
  506. # Set anchoring values for numerical solution: C is like max value, T is
  507. # full tau expression, res[0] is the initial result (assumed to be the
  508. # steady state if the first timestep inputs were held constant).
  509. ## uncomment the following line and comment out the `C = ...` line below it if PFNpc is direction tuned.
  510. C = np.multiply(a, np.multiply((1 - np.exp(np.multiply(c, -speedvec))), (np.cos(thetavec - prefdir) + b)))
  511. ## comment out above line and use the following line instead if no PFNpc direction tuning
  512. #C = np.multiply(a, np.multiply((1 - np.exp(np.multiply(c, -speedvec))), (np.cos(0 - prefdir) + b)))
  513. T = tau + np.multiply(tauslope, np.exp(-speedvec))
  514. res = np.zeros_like(t)
  515. res[0] = initcond
  516. # Handle direction of response (rise/decay).
  517. for i in range(len(t)):
  518. if flip[i] != 0: # if AF response curve
  519. res[i] = C[i]-initcond
  520. ratio[i] = 1 - ratio[i]
  521. # Calculate numerical solution using the given inputs and parameters.
  522. for i in range(len(t) - 1):
  523. dt = (t[i + 1] - t[i])
  524. res[i + 1] = (res[i] + ((ratio[i]) * C[i] - res[i]) * (dt / (T[i])))
  525. r = res
  526. # Handle negative values for AF response curves.
  527. for i in range(len(t)):
  528. if flip[i] != 0:
  529. res[i] = np.abs(C[i] - res[i])
  530. #res[i] = C[i]-res[i]
  531. r[i] = r[i]
  532. return res, r
  533. def sumPFNvecs(PFN_bumps, PFN_amps):
  534. # assumes PFN_bumps is already pi/4-rad shifted.
  535. adj = PFN_amps[0, :] * np.cos(PFN_bumps[0, :]) + PFN_amps[1, :] * np.cos(PFN_bumps[1, :])
  536. opp = PFN_amps[0, :] * np.sin(PFN_bumps[0, :]) + PFN_amps[1, :] * np.sin(PFN_bumps[1, :])
  537. va = np.arctan2(opp, adj)
  538. vm = np.sqrt(opp ** 2 + adj ** 2)
  539. return va, vm
  540. def PFNd_integ(params, inputs, initcond):
  541. # params = array where first row is AF, second row is OF
  542. # inputs = array where:
  543. # [ AF directions;
  544. # AF speeds;
  545. # OF directions;
  546. # OF speeds;
  547. # time ]
  548. if len(initcond.shape)==1:
  549. AF, a = D_response_de(params[0, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0])
  550. OF, o = D_response_de(params[1, :], inputs[2:5, :], initcond[1])
  551. else:
  552. AF, a = D_response_de(params[0, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0,:])
  553. OF, o = D_response_de(params[1, :], inputs[2:5, :], initcond[1,:])
  554. res = AF + OF # comment out for noAF or noOF
  555. # res = OF # comment in for noAF
  556. # res = AF # comment in for noOF
  557. return res, a, o
  558. def PFNa_integ(params, inputs, initcond):
  559. # params = array where first row is AF, second row is OF
  560. # inputs = array where:
  561. # [ AF directions;
  562. # AF speeds;
  563. # OF directions;
  564. # OF speeds;
  565. # time ]
  566. if len(initcond.shape)==1:
  567. AF, a = A_response_de(params[0, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0])
  568. OF, o = A_response_de(params[1, :], inputs[2:5, :], initcond[1])
  569. else:
  570. AF, a = A_response_de(params[0, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0,:])
  571. OF, o = A_response_de(params[1, :], inputs[2:5, :], initcond[1,:])
  572. # Instantiate result
  573. res = np.zeros(inputs.shape[1])
  574. # Where OF response exists, insert into result
  575. OFinds = np.where(OF)[0] # comment out for noOF
  576. res[OFinds] = OF[OFinds] # comment out for noOF
  577. # Where AF or AFOF response exists, insert into result (overwriting OF
  578. # except where OF is alone).
  579. AFinds = np.where(AF)[0] # comment out for noAF
  580. res[AFinds] = AF[AFinds] # comment out for noAF
  581. return res, a, o
  582. def PFNpc_integ2(params, inputs, initcond):
  583. if len(initcond.shape)==1:
  584. AF, a = PC_response_de(params[0:2, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0])
  585. OF, o = PC_response_de(params[2:, :], inputs[2:5, :], initcond[1])
  586. else:
  587. AF, a = PC_response_de(params[0:2, :], np.vstack((inputs[0:2, :], inputs[4, :])), initcond[0,:])
  588. OF, o = PC_response_de(params[2:, :], inputs[2:5, :], initcond[1,:])
  589. res = np.zeros(inputs.shape[1])
  590. OF_inds = np.where(OF)[0] # comment out for noOF
  591. res[OF_inds] = OF[OF_inds] # comment out for noOF
  592. AF_inds = np.where(AF)[0] # comment out for noAF
  593. res[AF_inds] = AF[AF_inds] # comment out for noAF
  594. return res, a, o
  595. def SteadyStateInitial_amp(params,inputs,isPFNa=False):
  596. # params is the parameter vector for one half of the PB to one stimulus modality, e.g. p_PFNd[0,:]
  597. # inputs is a vector of len=2, the starting direction (0) and speed (1) for the modality of interest
  598. # returns the steady-state value of the amplitude model for the given inputs
  599. if isPFNa:
  600. C = params[0] * (1 - np.exp(-inputs[1])) * (np.cos(inputs[0] - params[2]) ** 2 + params[1] * np.cos(inputs[0] - params[2] + np.pi) + params[3])
  601. ratio = params[4] + params[5] * np.cos(inputs[0] - params[2])
  602. ss = ratio * C
  603. else:
  604. C = params[0] * (1 - np.exp(params[1] * -inputs[1])) * (np.cos(inputs[0] - params[2]) + params[3])
  605. ss = params[4] * C
  606. return ss
  607. def SteadyStateInitial_bump(inputs):
  608. ## This is a holdover from when bump position is sensitive to airflow direction
  609. ## inputs is a vector of len=3, for heading direction, airflow direction, and airspeed
  610. #h = -inputs[0]
  611. if inputs[2]!=0:
  612. ss = -inputs[1]
  613. else:
  614. ss = 0
  615. ss=0 # set to zero if no influence of airflow on bump position. (Heading added in later in the model)
  616. return ss
  617. def BuildSSInitial(p_PFNd, p_PFNv, p_PFNpc, p_PFNa, input_AF, input_OF, bump_inputs):
  618. # if you don't want PFNv responses to airflow, run the following line:
  619. #p_PFNv[0,0] = 0
  620. #p_PFNpc[1,0] = 0 # no pc response to optic flow
  621. Raf = input_AF
  622. Laf = [-(input_AF[0]),input_AF[1]]
  623. Rof = input_OF
  624. Lof = [-(input_OF[0]),input_OF[1]]
  625. ssinit = np.zeros((9,2))
  626. ssinit[0,0] = SteadyStateInitial_amp(p_PFNd[0,:],Laf) # comment out for noAF
  627. ssinit[0,1] = SteadyStateInitial_amp(p_PFNd[1,:],Lof) # comment out for noOF
  628. ssinit[1,0] = SteadyStateInitial_amp(p_PFNd[0,:],Raf) # comment out for noAF
  629. ssinit[1,1] = SteadyStateInitial_amp(p_PFNd[1,:],Rof) # comment out for noOF
  630. ssinit[2,0] = SteadyStateInitial_amp(p_PFNv[0,:],Laf) # comment out for noAF
  631. ssinit[2,1] = SteadyStateInitial_amp(p_PFNv[1,:],Lof) # comment out for noOF
  632. ssinit[3,0] = SteadyStateInitial_amp(p_PFNv[0,:],Raf) # comment out for noAF
  633. ssinit[3,1] = SteadyStateInitial_amp(p_PFNv[1,:],Rof) # comment out for noOF
  634. ssinit[4,0] = SteadyStateInitial_amp(p_PFNpc[0,:],Laf) # comment out for noAF
  635. ssinit[4,1] = SteadyStateInitial_amp(p_PFNpc[1,:],Lof) # comment out for noOF
  636. ssinit[5,0] = SteadyStateInitial_amp(p_PFNpc[0,:],Raf) # comment out for noAF
  637. ssinit[5,1] = SteadyStateInitial_amp(p_PFNpc[1,:],Rof) # comment out for noOF
  638. ssinit[6,0] = SteadyStateInitial_amp(p_PFNa[0,:],Laf,True) # comment out for noAF
  639. ssinit[6,1] = SteadyStateInitial_amp(p_PFNa[1,:],Lof,True) # comment out for noOF
  640. ssinit[7,0] = SteadyStateInitial_amp(p_PFNa[0,:],Raf,True) # comment out for noAF
  641. ssinit[7,1] = SteadyStateInitial_amp(p_PFNa[1,:],Rof,True) # comment out for noOF
  642. ssinit[8,0] = SteadyStateInitial_bump(bump_inputs)
  643. ssinit[8,1] = SteadyStateInitial_bump(bump_inputs)
  644. return ssinit

PFN_model.py at commit c500bd6, under GPL-3.0 · at the source

Overview

  1. Department of Neuroscience, NYU Grossman School of Medicine, New York, NY 10016, USA
  2. Department of Mechanical Engineering, University of Nevada Reno, Reno, NV 89512, USA
Institutions: New York University (United States); University of Nevada, Reno (United States)
Journal: Science advances, volume 12, issue 35, article eaeh7220
Dates: received 1 April 2026; accepted 20 July 2026; published online 28 August 2026; in print August 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1126/sciadv.aeh7220 · PMID 42664356 · PMCID PMC13524059 · OpenAlex W7168277023
Open access: gold, a free copy (OpenAlex)
Status: code verified
Methods: Spectral & time-frequency, Connectivity, Evoked potentials, Statistics, fMRI & imaging, Single-unit activity, calcium imaging, Physiology & signal measures
Topic: Neurobiology and Insect Physiology Research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Funding: NINDS NIH HHS (R01 NS127129, R01 NS136555); NIDA NIH HHS (R34 DA059500)
Citations: not cited yet (Europe PMC); 102 references in the paper

Abstract

Flying flies can determine ambient wind direction in flight, but what neural representations might support this behavior are unclear. Ambient wind acting on a flying fly creates distinct patterns of airflow and optic flow. Here, we used two-photon imaging to characterize encoding of these two variables across columnar inputs to the fly navigation center, called PFNs. We find tuning for airflow direction and speed across many PFN types but only optic flow direction tuning in limited types. We do not observe tuning to optic flow speed. We build and validate an encoding model that enables simulation of PFN representations during real and simulated flight maneuvers. We show that these representations are sufficient to decode ambient wind direction both theoretically and using a simple feedforward ANN. Our work shows how a compact multisensory representation of self-motion could be used to infer a property of the external world that cannot be directly measured by a single sensory system.

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

Repositories

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

Zenodo 20558337

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 2 files
Software Heritage: not checked
Found in: “Data, code, and materials availability:”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)

nagellab/Mayetal2026

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: c500bd65728dd7bd2aba29e2a66b0e4f83a258eb, 13 July 2026
Languages: MATLAB (1072), C (71), Python (26), Jupyter (19), JavaScript (9), C/C++ (4), Java (2)
Size: 1,541 files, 1,203 scripts
Software Heritage: not archived
Found in: “Data, code, and materials availability:”
Holds: license file, environment (Python/drosophila_body_orientation_predictor/Dockerfile, Python/drosophila_body_orientation_predictor/requirements.txt), tests, documentation, 10 notebooks
Not found: README, CITATION.cff, continuous integration
Tools: NumPy (36 files), Statistics and Machine Learning Toolbox (33 files), CircStat (32 files), Matplotlib (29 files), pandas (29 files), Image Processing Toolbox (28 files), SciPy (28 files), shadedErrorBar (13 files), Keras (8 files), Signal Processing Toolbox (8 files), Parallel Computing Toolbox (6 files), seaborn (6 files), scikit-learn (4 files), TensorFlow (4 files), SymPy (3 files), Curve Fitting Toolbox (1 file), Optimization Toolbox (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
1,204 files

nehalsinghmangat/drosophila_body_orientation_predictor

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 00ad4ef6015d6cc751018c6ad966b1b2e4da50cc, 15 September 2026
Languages: Python (2), Jupyter (1)
Size: 116 files, 3 scripts
Software Heritage: not archived
Found in: “Data, code, and materials availability:”
Holds: README, license file, CITATION.cff, environment (requirements.txt), 1 notebook
Not found: tests, continuous integration, documentation
Tools: Matplotlib (3 files), NumPy (3 files), Keras (2 files), pandas (2 files), SciPy (2 files), scikit-learn (1 file), statsmodels (1 file), SymPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
5 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:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 1,206 scripts, each with its path and the digest of its content;
  • 14 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, code, and materials availability

All data and code needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. All data collected and code generated in this manuscript are publicly available on Zenodo (https://doi.org/10.5281/zenodo.20558337). All code generated for this manuscript (encoding model and observability) and CAD files for fly holders are available at GitHub at https://github.com/nagellab/Mayetal2026. Code used to create and train the naturalistic heading directions was forked from https://github.com/nehalsinghmangat/drosophila_body_orientation_predictor. No new materials were generated during the study.

Reproduced under the paper's license (CC BY-NC), 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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 2 funders, 87 references.

Cite

This paper

May, C. E., Cellini, B., Stupski, S. D., Lopez, A. P., Mangat, N., van Breugel, F., & Nagel, K. I. (2026). Central complex representations of self-movement are sufficient to compute wind direction in flight. Science advances, 12(35), eaeh7220. https://doi.org/10.1126/sciadv.aeh7220

BibTeX

@article{may2026central,
author = {May, Christina E and Cellini, Benjamin and Stupski, S David and Lopez, Austin P and Mangat, Nehal and van Breugel, Floris and Nagel, Katherine I},
title = {{Central complex representations of self-movement are sufficient to compute wind direction in flight}},
journal = {Science advances},
year = {2026},
month = aug,
volume = {12},
number = {35},
pages = {eaeh7220},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aeh7220},
url = {https://doi.org/10.1126/sciadv.aeh7220},
pmid = {42664356},
pmcid = {PMC13524059}
}

RIS

TY - JOUR
AU - May, Christina E
AU - Cellini, Benjamin
AU - Stupski, S David
AU - Lopez, Austin P
AU - Mangat, Nehal
AU - van Breugel, Floris
AU - Nagel, Katherine I
TI - Central complex representations of self-movement are sufficient to compute wind direction in flight
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/08/28
VL - 12
IS - 35
SP - eaeh7220
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aeh7220
UR - https://doi.org/10.1126/sciadv.aeh7220
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aeh7220",
"type": "article-journal",
"title": "Central complex representations of self-movement are sufficient to compute wind direction in flight",
"container-title": "Science advances",
"author": [
{
"family": "May",
"given": "Christina E"
},
{
"family": "Cellini",
"given": "Benjamin"
},
{
"family": "Stupski",
"given": "S David"
},
{
"family": "Lopez",
"given": "Austin P"
},
{
"family": "Mangat",
"given": "Nehal"
},
{
"family": "van Breugel",
"given": "Floris"
},
{
"family": "Nagel",
"given": "Katherine I"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "35",
"page": "eaeh7220",
"DOI": "10.1126/sciadv.aeh7220",
"PMID": "42664356",
"PMCID": "PMC13524059",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aeh7220",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
28
]
]
}
}

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.1038/s41467-026-75945-2 [code]
Neural dynamics for working memory and evidence integration during olfactory navigation in Drosophila.
Journal: Nature communications
In common: shadedErrorBar, Curve Fitting Toolbox, Image Processing Toolbox, 2 other tools, 13 references, author Katherine I Nagel
[2] doi:10.1038/s41586-026-10827-7 [code]
A vector-based strategy for olfactory navigation in Drosophila.
Journal: Nature
In common: pandas, SciPy, Matplotlib, 1 other tool, 16 references
[3] doi:10.3389/fnsys.2026.1822122 [code]
Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.
Journal: Frontiers in systems neuroscience
In common: Keras, TensorFlow, seaborn, 5 other tools, 9 references
[4] doi:10.1038/s41586-026-10735-w [code]
Distributed control circuits across a brain-and-cord connectome.
Journal: Nature
In common: seaborn, scikit-learn, pandas, 3 other tools, 11 references
[5] doi:10.1016/j.neuron.2026.03.034 [code]
Dentate gyrus interneurons modulate winner-take-all network dynamics in freely behaving mice.
Journal: Neuron
In common: CircStat, shadedErrorBar, Curve Fitting Toolbox, 11 other tools
[6] doi:10.1073/pnas.2609141123
Odor tracking in flying &lt;i&gt;Drosophila&lt;/i&gt; requires visual reafference and compass neurons.
Journal: Proceedings of the National Academy of Sciences of the United States of America
In common: 11 references
[7] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: Curve Fitting Toolbox, Keras, Optimization Toolbox, 11 other tools
[8] doi:10.1038/s41467-026-73106-z [code]
Respiratory pauses highlight sleep architecture in mice.
Journal: Nature communications
In common: CircStat, Curve Fitting Toolbox, Keras, 9 other tools
[9] doi:10.1002/hbm.70602 [code]
Neuroimaging Correlates of Post-Stroke Pain After Ischemic Stroke: Secondary Analysis of the INSPiRE-TMS Trial.
Journal: Human brain mapping
In common: Curve Fitting Toolbox, Keras, Optimization Toolbox, 10 other tools
[10] doi:10.1111/ene.70678 [code]
Who Falls After a Stroke? Evidence From a Prospective Stroke Cohort.
Journal: European journal of neurology
In common: Curve Fitting Toolbox, Keras, Optimization Toolbox, 10 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.