OSCR

Intrinsic timing, not temporal prediction, underlies ramping dynamics in visual and parietal cortex during passive behavior.

Code ↔ Paper

2 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 2 matches
  1. [1] § MATERIALS AND METHODS › Population trajectory analysis ↔ 2AFC/Modules/reader.py, lines 643–699 · score 0.56 · Savitzky Golay filtering, polynomial, window, neural
  2. [2] § MATERIALS AND METHODS › Population trajectory analysis ↔ 2p_2AFC_reg_version/Modules/reader.py, lines 640–696 · score 0.56 · Savitzky Golay filtering, polynomial, window, neural

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 · 706 lines · 35 KB · no license · 1 match

  1. # Liberary for reading session data from multiple directories
  2. import os
  3. import numpy as np
  4. import h5py
  5. import shutil
  6. import pandas as pd
  7. import scipy.io as sio
  8. from scipy.signal import savgol_filter
  9. # First fuction to read for reading sessions data from multiple directories
  10. def read_ops(list_session_data_path):
  11. """
  12. Reads and processes operation data from multiple session data paths.
  13. This function loads operation data stored in 'ops.npy' files from a list of session
  14. directories, updates each operation dictionary with its corresponding session path,
  15. and returns a list of these operation dictionaries.
  16. Args:
  17. list_session_data_path (list): A list of strings, where each string is a path to
  18. a directory containing an 'ops.npy' file with operation data.
  19. Returns:
  20. list: A list of dictionaries, where each dictionary contains the operation data
  21. loaded from an 'ops.npy' file, with an additional 'save_path0' key pointing
  22. to the corresponding session directory.
  23. Notes:
  24. - The function uses `numpy.load` with `allow_pickle=True` to load the 'ops.npy'
  25. files, which are expected to contain pickled Python objects (dictionaries).
  26. - The 'ops.npy' file must exist in each session directory specified in
  27. `list_session_data_path`.
  28. - The function assumes that the loaded `ops` data is a dictionary and modifies it
  29. by adding or updating the 'save_path0' key with the session directory path.
  30. Example:
  31. >>> session_paths = ['/path/to/session1', '/path/to/session2']
  32. >>> ops_list = read_ops(session_paths)
  33. >>> print(ops_list[0]['save_path0'])
  34. '/path/to/session1'
  35. Raises:
  36. FileNotFoundError: If an 'ops.npy' file is not found in any of the specified
  37. session directories.
  38. ValueError: If the loaded 'ops.npy' file does not contain a dictionary.
  39. """
  40. list_ops = []
  41. for session_data_path in list_session_data_path:
  42. ops = np.load(
  43. os.path.join(session_data_path, 'ops.npy'),
  44. allow_pickle=True).item()
  45. ops['save_path0'] = os.path.join(session_data_path)
  46. list_ops.append(ops)
  47. return list_ops
  48. def create_memmap(data, dtype, mmap_path):
  49. """
  50. Creates a memory-mapped array from input data and saves it to a specified file path.
  51. This function creates a memory-mapped array with the same shape and data type as the
  52. input data, writes the input data to the memory-mapped file, and returns the
  53. memory-mapped array for further use.
  54. Args:
  55. data (numpy.ndarray): The input NumPy array whose data will be written to the
  56. memory-mapped file.
  57. dtype (numpy.dtype): The data type of the memory-mapped array.
  58. mmap_path (str): The file path where the memory-mapped array will be stored.
  59. Returns:
  60. numpy.memmap: A memory-mapped array with the specified shape and data type,
  61. containing the data from the input array.
  62. Notes:
  63. - The memory-mapped array is created in 'w+' mode, which creates a new file or
  64. overwrites an existing file for reading and writing.
  65. - The function uses `numpy.memmap` to create the memory-mapped array, which allows
  66. efficient handling of large arrays by mapping them directly to disk.
  67. - The input `data` is copied into the memory-mapped array using slice assignment.
  68. Example:
  69. >>> import numpy as np
  70. >>> data = np.array([[1, 2], [3, 4]], dtype=np.int32)
  71. >>> mmap_arr = create_memmap(data, np.int32, 'data.mmap')
  72. >>> print(mmap_arr)
  73. [[1 2]
  74. [3 4]]
  75. Raises:
  76. ValueError: If the `dtype` does not match the data type of the input `data` or if
  77. the `mmap_path` is invalid.
  78. OSError: If there are issues with file creation or access at `mmap_path`.
  79. """
  80. memmap_arr = np.memmap(mmap_path, dtype=dtype, mode='w+', shape=data.shape)
  81. memmap_arr[:] = data[...]
  82. return memmap_arr
  83. def get_memmap_path(ops, h5_file_name):
  84. """
  85. Generates paths for memory-mapped data and the corresponding HDF5 file.
  86. This function creates a directory for memory-mapped data based on the provided HDF5
  87. file name and constructs paths for both the memory-mapped directory and the HDF5 file.
  88. The memory-mapped directory is created under a 'memmap' subdirectory within the
  89. session's save path if it does not already exist.
  90. Args:
  91. ops (dict): A dictionary containing session metadata, including the key
  92. 'save_path0' which specifies the base directory path for the session.
  93. h5_file_name (str): The name of the HDF5 file (e.g., 'data.h5').
  94. Returns:
  95. tuple: A tuple containing two strings:
  96. - mm_path (str): The path to the memory-mapped data directory.
  97. - file_path (str): The path to the HDF5 file.
  98. Notes:
  99. - The memory-mapped directory is named after the HDF5 file name (without its
  100. extension) and is created under '<save_path0>/memmap/'.
  101. - If the memory-mapped directory does not exist, it is created automatically.
  102. - The function assumes that 'save_path0' exists in the `ops` dictionary and is a
  103. valid directory path.
  104. Example:
  105. >>> ops = {'save_path0': '/path/to/session'}
  106. >>> h5_file_name = 'data.h5'
  107. >>> mm_path, file_path = get_memmap_path(ops, h5_file_name)
  108. >>> print(mm_path)
  109. '/path/to/session/memmap/data'
  110. >>> print(file_path)
  111. '/path/to/session/data.h5'
  112. Raises:
  113. KeyError: If 'save_path0' is not present in the `ops` dictionary.
  114. OSError: If there are issues creating the memory-mapped directory or accessing
  115. the file system.
  116. """
  117. mm_folder_name, _ = os.path.splitext(h5_file_name)
  118. if not os.path.exists(os.path.join(ops['save_path0'], 'memmap', mm_folder_name)):
  119. os.makedirs(os.path.join(ops['save_path0'], 'memmap', mm_folder_name))
  120. mm_path = os.path.join(ops['save_path0'], 'memmap', mm_folder_name)
  121. file_path = os.path.join(ops['save_path0'], h5_file_name)
  122. return mm_path, file_path
  123. def read_masks(ops):
  124. """
  125. Reads mask-related data from an HDF5 file and creates memory-mapped arrays.
  126. This function retrieves paths for memory-mapped data and an HDF5 file using
  127. `get_memmap_path`, then loads specific datasets from the HDF5 file into memory-mapped
  128. arrays. It handles both functional and anatomical data, with anatomical data loaded
  129. only if the session has two channels.
  130. Args:
  131. ops (dict): A dictionary containing session metadata, including:
  132. - 'save_path0': The base directory path for the session.
  133. - 'nchannels': The number of channels in the session (e.g., 1 or 2).
  134. Returns:
  135. list: A list containing the following memory-mapped arrays (or None for anatomical
  136. data if `ops['nchannels'] != 2`):
  137. - labels (numpy.memmap): Array of integer labels (dtype: int8).
  138. - masks (numpy.memmap): Functional mask data (dtype: float32).
  139. - mean_func (numpy.memmap): Mean functional data (dtype: float32).
  140. - max_func (numpy.memmap): Maximum functional data (dtype: float32).
  141. - mean_anat (numpy.memmap or None): Mean anatomical data (dtype: float32) if
  142. `ops['nchannels'] == 2`, otherwise None.
  143. - masks_anat (numpy.memmap or None): Anatomical mask data (dtype: float32) if
  144. `ops['nchannels'] == 2`, otherwise None.
  145. Notes:
  146. - The function assumes the HDF5 file 'masks.h5' exists in the session directory
  147. specified by `ops['save_path0']` and contains the datasets 'labels',
  148. 'masks_func', 'mean_func', 'max_func', and optionally 'mean_anat' and
  149. 'masks_anat' for two-channel data.
  150. - Memory-mapped arrays are created using the `create_memmap` function and stored
  151. in a 'memmap' subdirectory under the session path.
  152. - The HDF5 file is accessed in read-only mode ('r') using `h5py.File`.
  153. Example:
  154. >>> ops = {'save_path0': '/path/to/session', 'nchannels': 2}
  155. >>> result = read_masks(ops)
  156. >>> print(result[0]) # Access the labels memory-mapped array
  157. <numpy.memmap object with dtype=int8>
  158. Raises:
  159. KeyError: If required keys ('save_path0', 'nchannels') are missing from `ops` or
  160. required datasets are missing from the HDF5 file.
  161. FileNotFoundError: If the 'masks.h5' file does not exist at the specified path.
  162. OSError: If there are issues creating memory-mapped files or accessing the file
  163. system.
  164. """
  165. mm_path, file_path = get_memmap_path(ops, 'masks.h5')
  166. with h5py.File(file_path, 'r') as f:
  167. labels = create_memmap(f['labels'], 'int8', os.path.join(mm_path, 'labels.mmap'))
  168. masks = create_memmap(f['masks_func'], 'float32', os.path.join(mm_path, 'masks_func.mmap'))
  169. mean_func = create_memmap(f['mean_func'], 'float32', os.path.join(mm_path, 'mean_func.mmap'))
  170. max_func = create_memmap(f['max_func'], 'float32', os.path.join(mm_path, 'max_func.mmap'))
  171. mean_anat = create_memmap(f['mean_anat'], 'float32', os.path.join(mm_path, 'mean_anat.mmap')) if ops['nchannels'] == 2 else None
  172. masks_anat = create_memmap(f['masks_anat'], 'float32', os.path.join(mm_path, 'masks_anat.mmap')) if ops['nchannels'] == 2 else None
  173. return [labels, masks, mean_func, max_func, mean_anat, masks_anat]
  174. def read_raw_voltages(ops):
  175. """
  176. Reads raw voltage data from an HDF5 file and creates memory-mapped arrays.
  177. This function retrieves paths for memory-mapped data and an HDF5 file using
  178. `get_memmap_path`, then loads specific voltage-related datasets from the HDF5 file
  179. into memory-mapped arrays. The datasets include timestamps and various voltage signals
  180. related to experimental events (e.g., visual stimuli, audio stimuli, imaging triggers).
  181. Args:
  182. ops (dict): A dictionary containing session metadata, including:
  183. - 'save_path0': The base directory path for the session.
  184. Returns:
  185. list: A list containing the following memory-mapped arrays:
  186. - vol_time (numpy.memmap): Timestamps for voltage signals (dtype: float32).
  187. - vol_start (numpy.memmap): Trial start trigger signals (dtype: int8).
  188. - vol_stim_vis (numpy.memmap): Visual stimulus signals (dtype: int8).
  189. - vol_img (numpy.memmap): Imaging trigger signals (dtype: int8).
  190. - vol_hifi (numpy.memmap): HiFi (audio) trigger signals (dtype: int8).
  191. - vol_stim_aud (numpy.memmap): Audio stimulus signals (dtype: float32).
  192. - vol_flir (numpy.memmap): FLIR camera trigger signals (dtype: int8).
  193. - vol_pmt (numpy.memmap): PMT (photomultiplier tube) signals (dtype: int8).
  194. - vol_led (numpy.memmap): LED trigger signals (dtype: int8).
  195. Notes:
  196. - The function assumes the HDF5 file 'raw_voltages.h5' exists in the session
  197. directory specified by `ops['save_path0']` and contains a 'raw' group with
  198. datasets: 'vol_time', 'vol_start', 'vol_stim_vis', 'vol_img', 'vol_hifi',
  199. 'vol_stim_aud', 'vol_flir', 'vol_pmt', and 'vol_led'.
  200. - Memory-mapped arrays are created using the `create_memmap` function and stored
  201. in a 'memmap' subdirectory under the session path.
  202. - The HDF5 file is accessed in read-only mode ('r') using `h5py.File`.
  203. Example:
  204. >>> ops = {'save_path0': '/path/to/session'}
  205. >>> voltages = read_raw_voltages(ops)
  206. >>> print(voltages[0]) # Access the vol_time memory-mapped array
  207. <numpy.memmap object with dtype=float32>
  208. Raises:
  209. KeyError: If required keys (e.g., 'save_path0') are missing from `ops` or if
  210. expected datasets are missing in the HDF5 file.
  211. FileNotFoundError: If the 'raw_voltages.h5' file does not exist at the specified
  212. path.
  213. OSError: If there are issues creating memory-mapped files or accessing the file
  214. system.
  215. """
  216. mm_path, file_path = get_memmap_path(ops, 'raw_voltages.h5')
  217. with h5py.File(file_path, 'r') as f:
  218. vol_time = create_memmap(f['raw']['vol_time'], 'float32', os.path.join(mm_path, 'vol_time.mmap'))
  219. vol_start = create_memmap(f['raw']['vol_start'], 'int8', os.path.join(mm_path, 'vol_start.mmap'))
  220. vol_stim_vis = create_memmap(f['raw']['vol_stim_vis'], 'int8', os.path.join(mm_path, 'vol_stim_vis.mmap'))
  221. vol_hifi = create_memmap(f['raw']['vol_hifi'], 'int8', os.path.join(mm_path, 'vol_hifi.mmap'))
  222. vol_img = create_memmap(f['raw']['vol_img'], 'int8', os.path.join(mm_path, 'vol_img.mmap'))
  223. vol_stim_aud = create_memmap(f['raw']['vol_stim_aud'], 'float32', os.path.join(mm_path, 'vol_stim_aud.mmap'))
  224. vol_flir = create_memmap(f['raw']['vol_flir'], 'int8', os.path.join(mm_path, 'vol_flir.mmap'))
  225. vol_pmt = create_memmap(f['raw']['vol_pmt'], 'int8', os.path.join(mm_path, 'vol_pmt.mmap'))
  226. vol_led = create_memmap(f['raw']['vol_led'], 'int8', os.path.join(mm_path, 'vol_led.mmap'))
  227. return [vol_time, vol_start, vol_stim_vis, vol_img,
  228. vol_hifi, vol_stim_aud, vol_flir,
  229. vol_pmt, vol_led]
  230. def read_dff(ops):
  231. """
  232. Reads dF/F (delta F over F) traces from an HDF5 file and creates a memory-mapped array.
  233. This function retrieves paths for memory-mapped data and an HDF5 file using
  234. `get_memmap_path`, then loads the dF/F dataset from the HDF5 file into a
  235. memory-mapped array.
  236. Args:
  237. ops (dict): A dictionary containing session metadata, including:
  238. - 'save_path0': The base directory path for the session.
  239. Returns:
  240. numpy.memmap: A memory-mapped array containing dF/F traces (dtype: float32).
  241. Notes:
  242. - The function assumes the HDF5 file 'dff.h5' exists in the session directory
  243. specified by `ops['save_path0']` and contains a 'dff' dataset.
  244. - The memory-mapped array is created using the `create_memmap` function and
  245. stored in a 'memmap' subdirectory under the session path.
  246. - The HDF5 file is accessed in read-only mode ('r') using `h5py.File`.
  247. Example:
  248. >>> ops = {'save_path0': '/path/to/session'}
  249. >>> dff_data = read_dff(ops)
  250. >>> print(dff_data) # Access the dF/F memory-mapped array
  251. <numpy.memmap object with dtype=float32>
  252. Raises:
  253. KeyError: If required keys (e.g., 'save_path0') are missing from `ops` or if the
  254. 'dff' dataset is missing in the HDF5 file.
  255. FileNotFoundError: If the 'dff.h5' file does not exist at the specified path.
  256. OSError: If there are issues creating the memory-mapped file or accessing the file
  257. system.
  258. """
  259. mm_path, file_path = get_memmap_path(ops, 'dff.h5')
  260. with h5py.File(file_path, 'r') as f:
  261. dff = create_memmap(f['dff'], 'float32', os.path.join(mm_path, 'dff.mmap'))
  262. return dff
  263. # Reading bpod from matlab file
  264. def read_bpod_mat_data(ops, session_start_time):
  265. """
  266. Reads and processes Bpod session data from a MATLAB file into a structured DataFrame.
  267. This function loads behavioral data from a 'bpod_session_data.mat' file, processes
  268. trial-related information (e.g., trial timings, outcomes, stimulus sequences, and
  269. licking events), and organizes it into a pandas DataFrame. It handles nested MATLAB
  270. structures and converts them into Python dictionaries, adjusts timestamps relative to
  271. a session start time, and labels trial outcomes and states.
  272. Args:
  273. ops (dict): A dictionary containing session metadata, including:
  274. - 'save_path0': The base directory path where the 'bpod_session_data.mat' file
  275. is located.
  276. session_start_time (float): The reference start time (in milliseconds) for
  277. correcting trial timestamps.
  278. Returns:
  279. pandas.DataFrame: A DataFrame containing trial-related data with the following
  280. columns:
  281. - time_trial_start (float): Trial start timestamps (ms, adjusted to session
  282. start).
  283. - time_trial_end (float): Trial end timestamps (ms, adjusted to session start).
  284. - trial_type (int): Trial type indicator (0-based, derived from raw data).
  285. - outcome (str): Trial outcome ('punish', 'reward', 'naive_punish',
  286. 'naive_reward', 'no_choose', or 'other').
  287. - state_window_choice (array): Timestamps for the 'WindowChoice' state [start, end].
  288. - state_reward (array): Timestamps for the 'Reward' state [start, end].
  289. - state_punish (array): Timestamps for the 'Punish' state [start, end].
  290. - stim_seq (array): Stimulus sequence timestamps [[BNC1High], [BNC1Low]].
  291. - isi (float): Inter-stimulus interval (ms) between BNC1High and BNC1Low.
  292. - lick (array): Licking events with [timestamps, direction, correctness, lick_type].
  293. Notes:
  294. - The function assumes the 'bpod_session_data.mat' file exists in the directory
  295. specified by `ops['save_path0']` and contains a 'SessionData' structure with
  296. fields like 'nTrials', 'RawEvents', 'TrialStartTimestamp', 'TrialEndTimestamp',
  297. and 'TrialTypes'.
  298. - Timestamps are converted to milliseconds and adjusted relative to the session
  299. start time.
  300. - Nested MATLAB structures are recursively converted to Python dictionaries using
  301. helper functions `_check_keys`, `_todict`, and `_tolist`.
  302. - Trial outcomes are determined by the `states_labeling` helper function based on
  303. the presence of specific states in the trial data.
  304. - Stimulus sequences and licking events are processed to handle missing or invalid
  305. data (e.g., NaN values for absent events).
  306. - The 'yicong_forever' string is used as a temporary placeholder to handle array
  307. length alignment and is removed before returning the DataFrame.
  308. Example:
  309. >>> ops = {'save_path0': '/path/to/session'}
  310. >>> session_start_time = 1000.0
  311. >>> df = read_bpod_mat_data(ops, session_start_time)
  312. >>> print(df[['time_trial_start', 'outcome']].head())
  313. time_trial_start outcome
  314. 0 1000.0 reward
  315. 1 1500.0 punish
  316. ...
  317. Raises:
  318. KeyError: If required keys (e.g., 'save_path0') are missing from `ops` or if
  319. expected fields are missing in the MATLAB file.
  320. FileNotFoundError: If the 'bpod_session_data.mat' file does not exist.
  321. ValueError: If the data structure in the MATLAB file is malformed or incompatible.
  322. """
  323. def _check_keys(d):
  324. """
  325. Recursively converts MATLAB structs to dictionaries.
  326. Args:
  327. d (dict): Input dictionary containing MATLAB struct objects.
  328. Returns:
  329. dict: Dictionary with MATLAB structs converted to nested dictionaries.
  330. """
  331. for key in d:
  332. if isinstance(d[key], sio.matlab.mat_struct):
  333. d[key] = _todict(d[key])
  334. return d
  335. def _todict(matobj):
  336. """
  337. Converts a MATLAB struct to a Python dictionary.
  338. Args:
  339. matobj (sio.matlab.mat_struct): MATLAB struct object.
  340. Returns:
  341. dict: Dictionary with field names as keys and converted values.
  342. """
  343. d = {}
  344. for strg in matobj._fieldnames:
  345. elem = matobj.__dict__[strg]
  346. if isinstance(elem, sio.matlab.mat_struct):
  347. d[strg] = _todict(elem)
  348. elif isinstance(elem, np.ndarray):
  349. d[strg] = _tolist(elem)
  350. else:
  351. d[strg] = elem
  352. return d
  353. def _tolist(ndarray):
  354. """
  355. Recursively converts NumPy arrays to lists, handling nested MATLAB structs.
  356. Args:
  357. ndarray (numpy.ndarray): Input NumPy array.
  358. Returns:
  359. list: List of converted elements.
  360. """
  361. elem_list = []
  362. for sub_elem in ndarray:
  363. if isinstance(sub_elem, sio.matlab.mat_struct):
  364. elem_list.append(_todict(sub_elem))
  365. elif isinstance(sub_elem, np.ndarray):
  366. elem_list.append(_tolist(sub_elem))
  367. else:
  368. elem_list.append(sub_elem)
  369. return elem_list
  370. def states_labeling(trial_states):
  371. """
  372. Labels trial outcomes based on trial states.
  373. Args:
  374. trial_states (dict): Dictionary of trial states and their timestamps.
  375. Returns:
  376. str: Outcome label ('punish', 'reward', 'naive_punish', 'naive_reward',
  377. 'no_choose', or 'other').
  378. """
  379. if 'Punish' in trial_states.keys() and not np.isnan(trial_states['Punish'][0]):
  380. outcome = 'punish'
  381. elif 'Reward' in trial_states.keys() and not np.isnan(trial_states['Reward'][0]):
  382. outcome = 'reward'
  383. elif 'PunishNaive' in trial_states.keys() and not np.isnan(trial_states['PunishNaive'][0]):
  384. outcome = 'naive_punish'
  385. elif 'RewardNaive' in trial_states.keys() and not np.isnan(trial_states['RewardNaive'][0]):
  386. outcome = 'naive_reward'
  387. elif 'DidNotChoose' in trial_states.keys() and not np.isnan(trial_states['DidNotChoose'][0]):
  388. outcome = 'no_choose'
  389. else:
  390. outcome = 'other'
  391. return outcome
  392. def get_state(trial_state_dict, target_state, trial_start):
  393. """
  394. Retrieves timestamps for a specific trial state.
  395. Args:
  396. trial_state_dict (dict): Dictionary of trial states.
  397. target_state (str): Name of the target state.
  398. trial_start (float): Trial start timestamp (ms).
  399. Returns:
  400. numpy.ndarray: Array of [start, end] timestamps (ms) or [NaN, NaN] if the
  401. state is not found.
  402. """
  403. if target_state in trial_state_dict:
  404. time_state = 1000 * np.array(trial_state_dict[target_state]) + trial_start
  405. else:
  406. time_state = np.array([np.nan, np.nan])
  407. return time_state
  408. # Read raw data from MATLAB file
  409. raw = sio.loadmat(
  410. os.path.join(ops['save_path0'], 'bpod_session_data.mat'),
  411. struct_as_record=False, squeeze_me=True)
  412. raw = _check_keys(raw)['SessionData']
  413. trial_labels = dict()
  414. n_trials = raw['nTrials']
  415. trial_states = [raw['RawEvents']['Trial'][ti]['States'] for ti in range(n_trials)]
  416. trial_events = [raw['RawEvents']['Trial'][ti]['Events'] for ti in range(n_trials)]
  417. # Trial start and end timestamps
  418. trial_labels['time_trial_start'] = 1000 * np.array(raw['TrialStartTimestamp']).reshape(-1)
  419. trial_labels['time_trial_end'] = 1000 * np.array(raw['TrialEndTimestamp']).reshape(-1)
  420. trial_labels['time_trial_end'] = trial_labels['time_trial_end'] - trial_labels['time_trial_start'][0] + session_start_time
  421. trial_labels['time_trial_start'] = trial_labels['time_trial_start'] - trial_labels['time_trial_start'][0] + session_start_time
  422. # Trial type Block type and outcome
  423. trial_labels['trial_type'] = np.array(raw['TrialTypes']).reshape(-1) - 1
  424. trial_labels['block_type'] = np.array(raw['BlockTypes']).reshape(-1)
  425. trial_labels['outcome'] = np.array([states_labeling(ts) for ts in trial_states], dtype='object')
  426. # Trial state timings
  427. trial_labels['state_window_choice'] = np.array([
  428. get_state(trial_states[ti], 'WindowChoice', trial_labels['time_trial_start'][ti])
  429. for ti in range(n_trials)] + ['yicong_forever'], dtype='object')[:-1]
  430. trial_labels['state_reward'] = np.array([
  431. get_state(trial_states[ti], 'Reward', trial_labels['time_trial_start'][ti])
  432. for ti in range(n_trials)] + ['yicong_forever'], dtype='object')[:-1]
  433. trial_labels['state_punish'] = np.array([
  434. get_state(trial_states[ti], 'Punish', trial_labels['time_trial_start'][ti])
  435. for ti in range(n_trials)] + ['yicong_forever'], dtype='object')[:-1]
  436. # Stimulus timing
  437. trial_isi = []
  438. trial_stim_seq = []
  439. for ti in range(n_trials):
  440. if ('BNC1High' in trial_events[ti].keys() and
  441. 'BNC1Low' in trial_events[ti].keys() and
  442. len(np.array(trial_events[ti]['BNC1High']).reshape(-1)) == 2 and
  443. len(np.array(trial_events[ti]['BNC1Low']).reshape(-1)) == 2):
  444. stim_seq = 1000 * np.array([trial_events[ti]['BNC1High'], trial_events[ti]['BNC1Low']]) + trial_labels['time_trial_start'][ti]
  445. stim_seq = np.transpose(stim_seq, [1, 0])
  446. isi = 1000 * np.array(trial_events[ti]['BNC1High'][1] - trial_events[ti]['BNC1Low'][0])
  447. else:
  448. stim_seq = np.array([[np.nan, np.nan], [np.nan, np.nan]])
  449. isi = np.nan
  450. trial_stim_seq.append(stim_seq)
  451. trial_isi.append(isi)
  452. trial_labels['stim_seq'] = np.array(trial_stim_seq + ['yicong_forever'], dtype='object')[:-1]
  453. trial_labels['isi'] = np.array(trial_isi + ['yicong_forever'], dtype='object')[:-1]
  454. # Licking events
  455. trial_lick = []
  456. for ti in range(n_trials):
  457. licking_events = []
  458. direction = []
  459. correctness = []
  460. if 'Port1In' in trial_events[ti].keys():
  461. lick_left = np.array(trial_events[ti]['Port1In']).reshape(-1)
  462. licking_events.append(lick_left)
  463. direction.append(np.zeros_like(lick_left))
  464. if trial_labels['trial_type'][ti] == 0:
  465. correctness.append(np.ones_like(lick_left))
  466. else:
  467. correctness.append(np.zeros_like(lick_left))
  468. if 'Port3In' in trial_events[ti].keys():
  469. lick_right = np.array(trial_events[ti]['Port3In']).reshape(-1)
  470. licking_events.append(lick_right)
  471. direction.append(np.ones_like(lick_right))
  472. if trial_labels['trial_type'][ti] == 1:
  473. correctness.append(np.ones_like(lick_right))
  474. else:
  475. correctness.append(np.zeros_like(lick_right))
  476. if len(licking_events) > 0:
  477. licking_events = 1000 * np.concatenate(licking_events).reshape(1, -1) + trial_labels['time_trial_start'][ti]
  478. direction = np.concatenate(direction).reshape(1, -1)
  479. correctness = np.concatenate(correctness).reshape(1, -1)
  480. lick = np.concatenate([licking_events, direction, correctness], axis=0)
  481. lick = lick[:, np.argsort(lick[0, :])]
  482. lick = lick[:, lick[0, :] >= trial_labels['state_window_choice'][ti][0]]
  483. if np.size(lick) != 0:
  484. lick_type = np.full(lick.shape[1], np.nan)
  485. lick_type[0] = 1
  486. if (not np.isnan(trial_labels['state_reward'][ti][1]) and
  487. len(lick_type) > 1):
  488. lick_type[1:][lick[0, 1:] > trial_labels['state_reward'][ti][0]] = 0
  489. lick_type = lick_type.reshape(1, -1)
  490. lick = np.concatenate([lick, lick_type], axis=0)
  491. else:
  492. lick = np.array([[np.nan], [np.nan], [np.nan], [np.nan]])
  493. else:
  494. lick = np.array([[np.nan], [np.nan], [np.nan], [np.nan]])
  495. trial_lick.append(lick)
  496. trial_labels['lick'] = np.array(trial_lick + ['yicong_forever'], dtype='object')[:-1]
  497. # Convert to DataFrame
  498. trial_labels = pd.DataFrame(trial_labels)
  499. return trial_labels
  500. # Readiing Trialized data
  501. def read_trial_label(ops):
  502. """Read trial label data from a CSV file into a pandas DataFrame.
  503. This function reads a CSV file containing trial label data and converts specific columns
  504. into numpy arrays with appropriate data types and shapes. Some columns containing array-like
  505. data are parsed from string representations back into numpy arrays. The processed data is
  506. returned as a structured pandas DataFrame.
  507. Args:
  508. ops (dict): Dictionary containing configuration options, including 'save_path0' for the
  509. directory where the 'trial_labels.csv' file is located.
  510. Returns:
  511. pandas.DataFrame: A DataFrame containing the trial label data with columns:
  512. - time_trial_start (float32): Start times of trials.
  513. - time_trial_end (float32): End times of trials.
  514. - trial_type (int8): Type of each trial.
  515. - outcome (object): Outcome of each trial.
  516. - state_window_choice (object): Array of choice window states per trial.
  517. - state_reward (object): Array of reward states per trial.
  518. - state_punish (object): Array of punishment states per trial.
  519. - stim_seq (object): 2D array of stimulus sequences per trial.
  520. - isi (float32): Inter-stimulus intervals.
  521. - lick (object): 2D array of lick data per trial.
  522. Notes:
  523. - The CSV file is expected to be located at ops['save_path0']/trial_labels.csv.
  524. - Columns with array-like data (e.g., state_window_choice, stim_seq, lick) are stored as
  525. strings in the CSV and are parsed back into numpy arrays with specified shapes.
  526. - The function uses a helper function, object_parse, to handle array parsing.
  527. - The 'yicong_forever' string is appended during parsing and then removed to maintain
  528. data integrity.
  529. """
  530. raw_csv = pd.read_csv(os.path.join(ops['save_path0'], 'trial_labels.csv'), index_col=0)
  531. # recover object numpy array from csv str.
  532. def object_parse(k, shape):
  533. arr = np.array(
  534. [np.fromstring(s.replace('[', '').replace(']', ''), sep=' ').reshape(shape)
  535. for s in raw_csv[k].to_list()] + ['yicong_forever'],
  536. dtype='object')[:-1]
  537. return arr
  538. # parse all array.
  539. time_trial_start = raw_csv['time_trial_start'].to_numpy(dtype='float32')
  540. time_trial_end = raw_csv['time_trial_end'].to_numpy(dtype='float32')
  541. trial_type = raw_csv['trial_type'].to_numpy(dtype='int8')
  542. block_type = raw_csv['block_type'].to_numpy(dtype='int8')
  543. outcome = raw_csv['outcome'].to_numpy(dtype='object')
  544. state_window_choice = object_parse('state_window_choice', [-1])
  545. state_reward = object_parse('state_reward', [-1])
  546. state_punish = object_parse('state_punish', [-1])
  547. stim_seq = object_parse('stim_seq', [-1, 2])
  548. isi = raw_csv['isi'].to_numpy(dtype='float32')
  549. lick = object_parse('lick', [4, -1])
  550. # convert to dataframe.
  551. trial_labels = pd.DataFrame({
  552. 'time_trial_start': time_trial_start,
  553. 'time_trial_end': time_trial_end,
  554. 'trial_type': trial_type,
  555. 'block_type': block_type,
  556. 'outcome': outcome,
  557. 'state_window_choice': state_window_choice,
  558. 'state_reward': state_reward,
  559. 'state_punish': state_punish,
  560. 'stim_seq': stim_seq,
  561. 'isi': isi,
  562. 'lick': lick,
  563. })
  564. return trial_labels
  565. def read_neural_trials(ops, smooth):
  566. """Read neural trial data from an HDF5 file and create memory-mapped arrays.
  567. This function reads neural trial data from an HDF5 file, including fluorescence signals,
  568. time points, and voltage signals, and optionally applies a Savitzky-Golay filter to smooth
  569. the fluorescence data. The data is stored as memory-mapped arrays for efficient access and
  570. returned as a dictionary. Trial labels are read using the read_trial_label function.
  571. Args:
  572. ops (dict): Dictionary containing configuration options, including 'save_path0' for the
  573. directory where the 'neural_trials.h5' file is located.
  574. smooth (bool): If True, apply Savitzky-Golay smoothing to the fluorescence data (dff).
  575. Returns:
  576. dict: A dictionary containing memory-mapped arrays and trial labels:
  577. - dff (numpy.memmap): Fluorescence signal data (float32).
  578. - time (numpy.memmap): Corrected neural time points (float32).
  579. - trial_labels (pandas.DataFrame): Trial label data from read_trial_label.
  580. - vol_time (numpy.memmap): Voltage signal time points (float32).
  581. - vol_stim_vis (numpy.memmap): Visual stimulation signals (int8).
  582. - vol_stim_aud (numpy.memmap): Auditory stimulation signals (float32).
  583. - vol_flir (numpy.memmap): FLIR camera signals (int8).
  584. - vol_pmt (numpy.memmap): Photomultiplier tube signals (int8).
  585. - vol_led (numpy.memmap): LED signals (int8).
  586. Notes:
  587. - The HDF5 file is expected to be located at ops['save_path0']/neural_trials.h5.
  588. - If smooth is True, a Savitzky-Golay filter is applied to the dff data with a window
  589. length of 9 and polynomial order of 3.
  590. - Memory-mapped files are created in a directory specified by get_memmap_path.
  591. - The function assumes the existence of helper functions: get_memmap_path,
  592. read_trial_label, and create_memmap.
  593. """
  594. mm_path, file_path = get_memmap_path(ops, 'neural_trials.h5')
  595. trial_labels = read_trial_label(ops)
  596. with h5py.File(file_path, 'r') as f:
  597. neural_trials = dict()
  598. dff = np.array(f['neural_trials']['dff'])
  599. if smooth:
  600. window_length = 9
  601. polyorder = 3
  602. dff = np.apply_along_axis(
  603. savgol_filter, 1, dff,
  604. window_length=window_length,
  605. polyorder=polyorder)
  606. else:
  607. pass
  608. neural_trials['dff'] = create_memmap(dff, 'float32', os.path.join(mm_path, 'dff.mmap'))
  609. neural_trials['time'] = create_memmap(f['neural_trials']['time'], 'float32', os.path.join(mm_path, 'time.mmap'))
  610. neural_trials['trial_labels'] = trial_labels
  611. neural_trials['vol_time'] = create_memmap(f['neural_trials']['vol_time'], 'float32', os.path.join(mm_path, 'vol_time.mmap'))
  612. neural_trials['vol_stim_vis'] = create_memmap(f['neural_trials']['vol_stim_vis'], 'int8', os.path.join(mm_path, 'vol_stim_vis.mmap'))
  613. neural_trials['vol_stim_aud'] = create_memmap(f['neural_trials']['vol_stim_aud'], 'float32', os.path.join(mm_path, 'vol_stim_aud.mmap'))
  614. neural_trials['vol_flir'] = create_memmap(f['neural_trials']['vol_flir'], 'int8', os.path.join(mm_path, 'vol_flir.mmap'))
  615. neural_trials['vol_pmt'] = create_memmap(f['neural_trials']['vol_pmt'], 'int8', os.path.join(mm_path, 'vol_pmt.mmap'))
  616. neural_trials['vol_led'] = create_memmap(f['neural_trials']['vol_led'], 'int8', os.path.join(mm_path, 'vol_led.mmap'))
  617. return neural_trials
  618. # clean memory mapping files.
  619. def clean_memap_path(ops):
  620. try:
  621. if os.path.exists(os.path.join(ops['save_path0'], 'memmap')):
  622. shutil.rmtree(os.path.join(ops['save_path0'], 'memmap'))
  623. except: pass

reader.py at commit 1c0f5de, no license · at the source

Overview

  1. Wallace H. Coulter Department of Biomedical Engineering, Georgia Institute of Technology and Emory University, Atlanta, GA 30332, USA
  2. School of Biological Sciences, Georgia Institute of Technology, Atlanta, GA, 30332, USA
Journal: Science advances, volume 12, issue 34, article eaed6417
Dates: received 4 November 2025; accepted 14 July 2026; published online 21 August 2026; in print August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1126/sciadv.aed6417 · PMID 42627925 · PMCID PMC13496207 · OpenAlex W7203947945
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism), systems (subfield)
Methods: Smoothing, state filtering, decompositions, Machine learning, Statistics, Preprocessing, Connectivity, fMRI & imaging, Single-unit activity, calcium imaging
MeSH: Parietal Lobe*, Visual Cortex*, Animals, Mice, Neurons, Photic Stimulation, Time Factors (* major topic)
Journal subjects: Neuroscience
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 135 references in the paper

Abstract

Ramping neural activity is widely interpreted as a signature of predictive processing, but whether these signals truly reflect predictions or instead emerge from sensory mechanisms remains unclear. To address this question, we used two-photon calcium imaging across multiple cell types in visual and parietal cortex, while awake mice passively received repeated audiovisual stimuli presented under distinct temporal structures. Neurons segregated into two broad response classes: stimulus-activated (ramp-down) and stimulus-inhibited (ramp-up) populations with diverse temporal kinetics. Multiple findings argued against a predictive interpretation: Ramping activity was present in naïve animals, neural responses changed immediately after short/long interval transitions, and unexpected stimulus timings elicited nearly identical responses in predictable and irregular contexts. Population analyses further showed that ramping reflected relaxation from stimulus-evoked activity rather than anticipatory buildup. Heterogeneous kinetics generated a robust population code for elapsed time. Together, these findings show that neural ramps during passive stimulation arise from stimulus-evoked dynamics that intrinsically generate temporal signals, rather than from temporal predictive processing.

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

Repository

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

najafi-laboratory/2p_imaging

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 1c0f5de0cc6b9e430169e1a5a27f643dac8f7114, 16 September 2026
Languages: Python (372), Jupyter (26), Shell (17)
Size: 681 files, 415 scripts
Software Heritage: not archived
Found in: “Data, code, and materials availability:”
Holds: README, environment (environment-preprocessing-qc.yml, pyproject.toml, requirements-docs.txt, tracking/environment.yml, utils_2p/environment-preprocessing-qc-suite2p-0x.yml, utils_2p/environment-preprocessing-qc-suite2p-1x.yml, utils_2p/environment-processing-suite2p-0x.yml, utils_2p/environment-processing-suite2p-1x.yml, visualize_2p_video_202412/requirements.txt), tests, continuous integration, documentation, 26 notebooks
Not found: license file, CITATION.cff
Tools: NumPy (57 files), Matplotlib (41 files), SciPy (25 files), pandas (23 files), h5py (20 files), scikit-learn (16 files), seaborn (16 files), scikit-image (6 files), Plotly (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
63 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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 63 scripts, each with its path and the digest of its content;
  • 2 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

Datasets cited

Data, code, and materials availability

All data and code needed to evaluate and reproduce the results in this paper are present in the paper and/or the Supplementary Materials. This study did not generate new materials. Data used in this paper are available at Zenodo: https://doi.org/10.5281/zenodo.20350578. Code for data processing and analysis is publicly available on the Najafi Lab GitHub page: https://github.com/najafi-laboratory/2p_imaging/tree/main/passive_interval_oddball_202412.

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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 7 MeSH terms, 4 funders, 133 references.

Cite

This paper

Huang, Y., Shamsnia, A., Chen, M., Wu, S., Stamm, T., Medico, S., & Najafi, F. (2026). Intrinsic timing, not temporal prediction, underlies ramping dynamics in visual and parietal cortex during passive behavior. Science advances, 12(34), eaed6417. https://doi.org/10.1126/sciadv.aed6417

BibTeX

@article{huang2026intrinsic,
author = {Huang, Yicong and Shamsnia, Ali and Chen, Mengze and Wu, Shuang and Stamm, Timothy and Medico, Sophie and Najafi, Farzaneh},
title = {{Intrinsic timing, not temporal prediction, underlies ramping dynamics in visual and parietal cortex during passive behavior}},
journal = {Science advances},
year = {2026},
month = aug,
volume = {12},
number = {34},
pages = {eaed6417},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aed6417},
url = {https://doi.org/10.1126/sciadv.aed6417},
pmid = {42627925},
pmcid = {PMC13496207}
}

RIS

TY - JOUR
AU - Huang, Yicong
AU - Shamsnia, Ali
AU - Chen, Mengze
AU - Wu, Shuang
AU - Stamm, Timothy
AU - Medico, Sophie
AU - Najafi, Farzaneh
TI - Intrinsic timing, not temporal prediction, underlies ramping dynamics in visual and parietal cortex during passive behavior
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/08/21
VL - 12
IS - 34
SP - eaed6417
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aed6417
UR - https://doi.org/10.1126/sciadv.aed6417
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aed6417",
"type": "article-journal",
"title": "Intrinsic timing, not temporal prediction, underlies ramping dynamics in visual and parietal cortex during passive behavior",
"container-title": "Science advances",
"author": [
{
"family": "Huang",
"given": "Yicong"
},
{
"family": "Shamsnia",
"given": "Ali"
},
{
"family": "Chen",
"given": "Mengze"
},
{
"family": "Wu",
"given": "Shuang"
},
{
"family": "Stamm",
"given": "Timothy"
},
{
"family": "Medico",
"given": "Sophie"
},
{
"family": "Najafi",
"given": "Farzaneh"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "34",
"page": "eaed6417",
"DOI": "10.1126/sciadv.aed6417",
"PMID": "42627925",
"PMCID": "PMC13496207",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aed6417",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
21
]
]
}
}

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-73999-w [code]
Independence and coherence in temporal sequence computation across the fronto-parietal network.
Journal: Nature communications
In common: scikit-learn, SciPy, Matplotlib, 1 other tool, systems, mouse, 10 references
[2] doi:10.1038/s41593-026-02255-7 [code]
Neural circuits encode prior knowledge of temporal statistics.
Journal: Nature neuroscience
In common: Plotly, scikit-learn, pandas, 3 other tools, systems, mouse, 5 references
[3] doi:10.1038/s41586-026-10528-1 [code]
A critical initialization for biological neural networks.
Journal: Nature
In common: scikit-learn, SciPy, Matplotlib, 1 other tool, mouse, 8 references
[4] doi:10.1016/j.neuron.2026.07.016 [code]
Inferring brain-wide interactions using data-constrained recurrent neural network models.
Journal: Neuron
In common: Matplotlib, NumPy, systems, mouse, 8 references
[5] doi:10.1038/s41593-026-02350-9 [code]
Probing inter-areal computations with a two-photon holographic mesoscope.
Journal: Nature neuroscience
In common: h5py, SciPy, NumPy, systems, mouse, 6 references
[6] doi:10.1371/journal.pbio.3003915 [code]
Noise-invariant representations of sound emerge along the canonical cortical hierarchy.
Journal: PLoS biology
In common: scikit-image, h5py, seaborn, 5 other tools, mouse, 3 references
[7] doi:10.1371/journal.pone.0356243 [code]
Functional organization and natural scene responses across mouse visual cortical areas revealed with encoding manifolds.
Journal: PloS one
In common: Plotly, scikit-learn, pandas, 3 other tools, mouse, 4 references
[8] doi:10.1371/journal.pbio.3003831 [code]
Disinhibitory signaling enables flexible coding of top-down information in cortical networks.
Journal: PLoS biology
In common: h5py, scikit-learn, pandas, 3 other tools, systems, mouse, 4 references
[9] doi:10.1038/s41467-026-71725-0 [code]
Interactions across hemispheres in prefrontal cortex reflect global cognitive processing.
Journal: Nature communications
In common: scikit-learn, pandas, SciPy, 2 other tools, 6 references
[10] doi:10.1523/jneurosci.0987-25.2026 [code]
Cell-Type-Specific Synaptic Scaling Mechanisms Differentially Contribute to Associative Learning.
Journal: The Journal of neuroscience : the official journal of the Society for Neuroscience
In common: seaborn, pandas, SciPy, 2 other tools, 5 references

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.