OSCR

Neuropixels Opto: combining high-resolution electrophysiology and optogenetics.

Code ↔ Paper

5 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 5 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Methods › Activating local neural populations › Data processing ↔ spks/sorting.py, lines 74–133 · score 0.62 · phase shift, bandpass filter, Kilosort
  2. [2] § Methods › Spike sorting and quality metrics ↔ spks/clusters.py, lines 240–323 · score 0.61 · amplitude cutoff, presence ratio, ISI, clusters, metrics, spike
  3. [3] § Results › Optotagging nearby neurons ↔ spks/clusters.py, lines 240–323 · score 0.59 · amplitude cutoff, presence ratio, firing rates, ISI, metrics, waveforms
  4. [4] § Methods › Optotagging nearby neurons (AI) › Data processing ↔ spks/sorting.py, lines 74–133 · score 0.57 · phase shift, Kilosort, pipeline, preprocessing, raw, filter
  5. [5] § Methods › Optotagging nearby neurons (AI) › Data processing ↔ spks/postprocess.py, the whole file · a weak match · score 0.50 · waveform amplitudes, Kilosort, peak, location, spike

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 · 514 lines · 24 KB · no license · 2 matches

  1. # Run various spike sorters using spike interface precompiled docker images
  2. # This should allow running it on a fast disk and copying only the relevant files over
  3. # The functions and parameters should be exposed and contained in this module.
  4. from .utils import *
  5. from .raw import *
  6. def get_probename(filename):
  7. # get the probe name from a file (only works with spikeglx?)
  8. probename = re.search(r'\s*imec[0-9]*[a-z]?\s*',str(filename))
  9. if not probename is None:
  10. return str(probename.group()).strip('/').strip('.')
  11. else:
  12. return 'probe0'
  13. def get_sorting_folder_path(filename,
  14. sorting_results_path_rules = ['..','..','{sortname}','{probename}'],
  15. sorting_folder_dictionary = dict(sortname = 'sorting',
  16. probename = 'probe0')):
  17. '''
  18. Gets the sorting folder path from a defined rule.
  19. foldername = get_sorting_folder_path(filename)
  20. '''
  21. filename = Path(filename)
  22. if filename.is_file():
  23. foldername = filename.parent
  24. sorting_folder_dictionary['probename'] = get_probename(filename)
  25. sorting_results_path = foldername #FIXME: not defined if filename isn't file
  26. for f in sorting_results_path_rules:
  27. if f == '..':
  28. foldername = foldername.parent
  29. else:
  30. foldername = foldername.joinpath(f.format(**sorting_folder_dictionary))
  31. return foldername
  32. def move_sorting_results(
  33. scratch_folder,
  34. original_session_path,
  35. move_filtered_data = False,
  36. sorting_results_path_rules = ['..','..','{sortname}','{probename}'],
  37. sorting_folder_dictionary = dict(
  38. sortname = 'sorting',
  39. probename = 'probe0')):
  40. '''
  41. Move spike sorting results to a standardized folder
  42. '''
  43. sorting_results_path = get_sorting_folder_path(
  44. filename = original_session_path,
  45. sorting_folder_dictionary=sorting_folder_dictionary,
  46. sorting_results_path_rules=sorting_results_path_rules)
  47. files_to_copy = []
  48. extensions = ['.npy','.tsv','.hdf','.m','.mat','.py','.log']
  49. if move_filtered_data:
  50. extensions += ['filtered_recording.*.bin']
  51. for name in extensions:
  52. files_to_copy += scratch_folder.glob(f'*{name}')
  53. if not sorting_results_path.exists():
  54. sorting_results_path.mkdir(parents=True, exist_ok=True)
  55. for f in tqdm(files_to_copy,desc = 'Moving files'):
  56. if os.path.exists(sorting_results_path/f.name):
  57. os.remove(sorting_results_path/f.name) #need to delete files before moving to avoid weird permission error
  58. try:
  59. shutil.move(f,sorting_results_path/f.name)
  60. except Exception as err:
  61. print(err)
  62. print(f'FAILED to move {f} to {sorting_results_path}')
  63. return sorting_results_path
  64. def run_kilosort(sessionfiles = [],
  65. channels = None,
  66. foldername = None,
  67. temporary_folder = 'temporary',
  68. version = '4.0',
  69. sorting_results_path_rules = ['..','..','{sortname}','{probename}'],
  70. sorting_folder_dictionary = dict(sortname = None, probename = 'probe0'),
  71. do_post_processing = False,
  72. device = 'cuda',
  73. gpu_index = 0,
  74. split_shanks = False,
  75. motion_correction = True,
  76. dredge_motion_correction = False,
  77. thresholds = None,
  78. batch_size = int(60000),
  79. lowpass = 300.,
  80. highpass = 13000.,
  81. filter_pipeline_par = [dict(function = 'bandpass_filter_gpu',
  82. sampling_rate = 30000,
  83. lowpass = 300,
  84. highpass = 13000,
  85. return_gpu = True),
  86. dict(function = 'phase_shift_gpu',
  87. sample_shifts = None,
  88. return_gpu = True),
  89. dict(function = 'global_car_gpu',
  90. return_gpu = False)]):
  91. '''
  92. Runs kilosort given binary files, returns a folder with the results.
  93. '''
  94. using_scratch = False
  95. if foldername is None:
  96. foldername = create_temporary_folder(temporary_folder, prefix=f'ks{version}_sorting')
  97. using_scratch = True
  98. for f in filter_pipeline_par:
  99. if 'lowpass' in f.keys() and not lowpass is None:
  100. f['lowpass'] = lowpass
  101. if 'highpass' in f.keys() and not highpass is None:
  102. f['highpass'] = highpass
  103. tt = RawRecording(sessionfiles,device = device,
  104. filter_pipeline_par = filter_pipeline_par,
  105. return_preprocessed = True)
  106. dtype = 'int16'
  107. binaryfilepath = pjoin(foldername,'filtered_recording.{probename}.bin'.format(
  108. **sorting_folder_dictionary))
  109. output_folder = os.path.dirname(binaryfilepath)
  110. if not os.path.exists(output_folder):
  111. os.makedirs(output_folder)
  112. print(f'Exporting binary to {binaryfilepath}')
  113. if channels is None:
  114. channels = tt.channel_info.channel_idx.values
  115. print(tt.offsets,len(sessionfiles),len(sessionfiles)>1)
  116. print(sessionfiles)
  117. binaryfile,metadata = tt.to_binary(binaryfilepath,
  118. filter_pipeline_par = filter_pipeline_par,
  119. channels = channels)
  120. if dredge_motion_correction:
  121. from .raw import dredge_motion_correct_binary_file,dredge_motion_correct_across_sessions
  122. n_jobs = 10
  123. print(len(sessionfiles),len(sessionfiles)>1)
  124. if len(sessionfiles)>1:
  125. print('Using multisession dredge.')
  126. binaryfilepath = dredge_motion_correct_across_sessions(binaryfilepath=binaryfilepath,
  127. metadata = metadata,
  128. n_jobs = n_jobs)
  129. else:
  130. print('Using single session dredge.')
  131. binaryfilepath = dredge_motion_correct_binary_file(binaryfilepath,
  132. nchannels = metadata['nchannels'],
  133. sampling_rate = metadata['sampling_rate'],
  134. channel_coords = metadata['channel_coords'],
  135. channel_shank = metadata['channel_shank'],
  136. output_folder = foldername,
  137. overwrite = True,
  138. output_dtype = 'int16',
  139. n_jobs = n_jobs)
  140. dtype = 'int16'
  141. print('Motion corrected done with dredge so skipping kilosort motion.')
  142. motion_correction = 0
  143. del tt # RawRecording no longer needed
  144. channelmappath = pjoin(os.path.dirname(binaryfilepath),'chanMap.mat')
  145. opspath = pjoin(os.path.dirname(binaryfilepath),'ops.mat')
  146. if lowpass is None:
  147. lowpass = 300.
  148. nchannels = metadata['nchannels']
  149. coords = np.stack(metadata['channel_coords'])
  150. # make the channelmap file
  151. chanMap = dict(Nchannels = nchannels,
  152. connected = np.ones(nchannels,dtype=bool).T,
  153. xcoords = coords[:,0].astype(float),
  154. ycoords = coords[:,1].astype(float),
  155. chanMap = np.array(metadata['channel_idx'],dtype=np.int64)+1,
  156. chanMap0ind = np.array(metadata['channel_idx'],dtype=np.int64),
  157. kcoords = np.array(metadata['channel_shank'],dtype=float).T+1,
  158. fs = metadata['sampling_rate'])
  159. from scipy.io import savemat
  160. savemat(channelmappath, chanMap,appendmat = False)
  161. if version == '2.5':
  162. compiled_name = 'kilosort2_5'
  163. if thresholds is None:
  164. thresholds = [9.,3.]
  165. ops = dict(ops = dict(default_ks25_ops,
  166. NchanTOT=float(metadata['nchannels']),
  167. Nchan = float(len(metadata['channel_idx'])),
  168. fbinary = binaryfilepath,
  169. fproc = pjoin(output_folder,'temp_wh.dat'),
  170. chanMap = channelmappath,
  171. fs = metadata['sampling_rate'],
  172. doCorrection = int(motion_correction),
  173. fshigh = lowpass,
  174. Th = thresholds,
  175. GPU = gpu_index + 1)) # indices are one based ...
  176. matlabcommand = kilosort25_matlabcommand
  177. elif version == '3.0':
  178. compiled_name = 'kilosort3_0'
  179. if thresholds is None:
  180. thresholds = [9.,9.]
  181. ops = dict(ops = dict(default_ks30_ops,
  182. NchanTOT=float(metadata['nchannels']),
  183. Nchan = float(len(metadata['channel_idx'])),
  184. fbinary = binaryfilepath,
  185. fproc = pjoin(output_folder,'temp_wh.dat'),
  186. chanMap = channelmappath,
  187. fs = metadata['sampling_rate'],
  188. doCorrection = int(motion_correction),
  189. fshigh = lowpass,
  190. Th = thresholds,
  191. GPU = gpu_index + 1)) # indices are one based ...
  192. if version in ['2.5','3.0']:
  193. # save the files
  194. savemat(opspath, ops,appendmat = False)
  195. import shutil
  196. if not shutil.which(compiled_name) is None:
  197. os.system(f'{compiled_name} {output_folder}') # easier to kill than subprocess?
  198. else: # just run using a local installation..
  199. matlabfile = pjoin(output_folder,'run_ks.m')
  200. with open(matlabfile,'w') as f:
  201. f.write(matlabcommand.format(output_folder = output_folder))
  202. cmd = """matlab -nodisplay -nosplash -r "run('{0}');" """.format(matlabfile)
  203. os.system(cmd) # easier to kill than subprocess?
  204. elif version == '4.0':
  205. if thresholds is None:
  206. thresholds = [9.,8.]
  207. res = run_kilosort4(
  208. device = device,
  209. foldername = foldername,
  210. binaryfilepath = binaryfilepath,
  211. metadata = metadata,
  212. motion_correction = motion_correction,
  213. thresholds = thresholds,
  214. dtype = dtype,
  215. batch_size = batch_size)
  216. else:
  217. raise(OSError('Undefined version {version}'))
  218. if do_post_processing:
  219. foldername = kilosort_post_processing(
  220. foldername,
  221. sessionfiles,
  222. move = using_scratch,
  223. binaryfilepath = binaryfilepath,
  224. sorting_results_path_rules = sorting_results_path_rules,
  225. sorting_folder_dictionary = sorting_folder_dictionary)
  226. return foldername
  227. def run_kilosort4(device, foldername, binaryfilepath,
  228. metadata,
  229. dtype = 'int16',
  230. thresholds = None, # pass a list if needed
  231. do_car = False,
  232. batch_size = int(60000),
  233. motion_correction = 1):
  234. nchannels = metadata['nchannels']
  235. coords = np.stack(metadata['channel_coords'])
  236. yc = coords[:,1].astype(np.float32)
  237. xc = coords[:,0].astype(np.float32)
  238. fix_shanks = False # flag to fix the phy coords # this is now fixed on v4.04
  239. if fix_shanks:
  240. # lets stack the shanks... because kilosort 4.0 can not handle multiple shanks..
  241. previous = 0
  242. for shank in np.unique(metadata['channel_shank']):
  243. idx = np.where(metadata['channel_shank'] == shank)
  244. offset = np.max(yc[idx])
  245. yc[idx] = (yc[idx]-np.min(yc[idx])) + previous
  246. xc[idx] = (xc[idx]-np.min(xc[idx]))
  247. previous += 100 + offset
  248. probe = dict(n_chan = nchannels,
  249. xc = xc,
  250. yc = yc,
  251. chanMap = np.array(metadata['channel_idx'], dtype=int),
  252. kcoords = np.array(metadata['channel_shank'], dtype=int).T)
  253. from kilosort import run_kilosort
  254. settings = dict(fs = metadata['sampling_rate'],
  255. n_chan_bin = nchannels,
  256. batch_size = batch_size,
  257. data_dir = foldername)
  258. settings['nblocks'] = int(motion_correction)
  259. if not thresholds is None:
  260. settings['Th_universal'] = thresholds[0]
  261. settings['Th_learned'] = thresholds[1]
  262. res = run_kilosort(filename = binaryfilepath,
  263. results_dir = foldername,
  264. settings = settings,
  265. data_dtype = dtype,
  266. probe = probe,
  267. device = device,
  268. do_CAR = do_car,
  269. save_preprocessed_copy = False,
  270. save_extra_vars = True) # save pc_features
  271. if fix_shanks:
  272. ops = res[0]
  273. st = res[1]
  274. tF = res[3]
  275. ops['xc'],ops['yc'] = coords.astype(np.float32).T # recompute the spike positions
  276. from kilosort.postprocessing import compute_spike_positions
  277. positions = compute_spike_positions(st,tF,ops)
  278. np.save(Path(foldername)/'spike_positions.npy',np.vstack(positions).T) # overwrite spike positions
  279. np.save(Path(foldername)/'channel_positions.npy',coords.astype(float)) # save the correct channel positions
  280. del res
  281. free_gpu()
  282. return True
  283. def kilosort_post_processing(resultsfolder,
  284. sessionfolder,
  285. move = False,
  286. binaryfilepath = None,
  287. sorting_results_path_rules = ['..','..','{sortname}','{probename}'],
  288. sorting_folder_dictionary = dict(
  289. sortname = 'kilosort',
  290. probename = 'probe0'),
  291. max_n_spikes = 1000):
  292. '''
  293. Post processing for kilosort results
  294. 1. remove duplicates
  295. 2. compute_waveforms
  296. 3. store a sample of 1000 waveforms to disk
  297. 4. move the files to a new folder and if so delete the scratch folder
  298. '''
  299. # 1. remove duplicates
  300. from .clusters import Clusters
  301. resultsfolder = Path(resultsfolder)
  302. sp = Clusters(resultsfolder,get_metrics = False,get_waveforms=False,load_template_features = True)
  303. sp.remove_duplicate_spikes(overwrite_phy=True)
  304. del sp
  305. # 2. compute_waveforms and store to disk
  306. sp = Clusters(resultsfolder,get_metrics = False,get_waveforms=False)
  307. meta = load_dict_from_h5(list(resultsfolder.glob('filtered_recording.*.metadata.hdf'))[0])
  308. from .io import map_binary
  309. if binaryfilepath is None:
  310. binaryfilepath = list(resultsfolder.glob('filtered_recording.*.bin'))[0]
  311. data = map_binary(binaryfilepath,meta['nchannels'])
  312. # don't filter the waveforms because it was done before.
  313. sp.extract_waveforms(data,np.arange(meta['nchannels']),
  314. max_n_spikes=max_n_spikes,
  315. save_folder_path = resultsfolder,filter_par = None)
  316. del sp
  317. # Compute metrics and mean waveforms
  318. sp = Clusters(resultsfolder,get_metrics = True, get_waveforms=True, load_template_features = True)
  319. del sp # close so it can move
  320. # 4. move the files to a new folder
  321. if move:
  322. if type(sessionfolder) in [list]:
  323. folder = sessionfolder[0]
  324. print(f'Saving to {folder}')
  325. else:
  326. folder = sessionfolder
  327. outputfolder = move_sorting_results(
  328. resultsfolder,
  329. folder,
  330. sorting_results_path_rules = sorting_results_path_rules,
  331. sorting_folder_dictionary = sorting_folder_dictionary)
  332. # 5. delete the scratch
  333. shutil.rmtree(resultsfolder)
  334. resultsfolder = outputfolder
  335. return resultsfolder # Clusters(resultsfolder) to open
  336. from .io import list_spikeglx_binary_paths
  337. def sort_multiprobe_sessions(sessions,
  338. temporary_folder = '/scratch',
  339. method = 'kilosort2.5',
  340. sorting_results_path_rules = ['..','..','{sortname}','{probename}'],
  341. sorting_folder_dictionary = dict(
  342. sortname = 'kilosort', probename = 'probe0'),
  343. do_post_processing = True,
  344. move = True,
  345. device = 'cuda',
  346. gpu_index = 0,
  347. motion_correction = True):
  348. '''
  349. Sort multiprobe neuropixels recordings (will concatenate multiple sessions if a list is passed).
  350. '''
  351. if not type(sessions) is list:
  352. sessions = [sessions]
  353. tmp = [list_spikeglx_binary_paths(s) for s in sessions]
  354. all_probe_dirs = []
  355. for iprobe in range(len(tmp[0])):
  356. all_probe_dirs.append([t[iprobe][0] for t in tmp])
  357. results = []
  358. for probepath in all_probe_dirs:
  359. print('Running {1} on sessions {0}'.format(' ,'.join(probepath),method))
  360. sorting_folder_dictionary['probename'] = get_probename(probepath)
  361. results_folder = run_kilosort(sessionfiles = probepath,
  362. version = method.strip('kilosort'),
  363. temporary_folder = temporary_folder,
  364. sorting_results_path_rules = sorting_results_path_rules,
  365. sorting_folder_dictionary = sorting_folder_dictionary,
  366. do_post_processing = False,
  367. motion_correction = motion_correction,
  368. device=device, gpu_index = gpu_index)
  369. print('Completed {1} Results folder: {0}'.format(results_folder,method))
  370. if do_post_processing:
  371. results_folder = kilosort_post_processing(
  372. results_folder,
  373. probepath,
  374. sorting_results_path_rules = sorting_results_path_rules,
  375. sorting_folder_dictionary = sorting_folder_dictionary,
  376. move = move)
  377. print('Completed sorting for results folder: {0}'.format(results_folder))
  378. results.append(results_folder)
  379. return results
  380. kilosort25_matlabcommand = '''
  381. load(fullfile('{output_folder}','ops.mat'))
  382. load(fullfile('{output_folder}','chanMap.mat'))
  383. % This assumes kilosort 2.5 and all are installed and on the path
  384. % preprocess data to create temp_wh.dat
  385. rez = preprocessDataSub(ops);
  386. rez = datashift2(rez, ops.doCorrection); % last input is for shifting data
  387. % ORDER OF BATCHES IS NOW RANDOM, controlled by random number generator
  388. iseed = 1;
  389. rez = learnAndSolve8b(rez, iseed); % main tracking and template matching algorithm
  390. % OPTIONAL: remove double-counted spikes - solves issue in which individual spikes are assigned to multiple templates.
  391. % See issue 29: https://github.com/MouseLand/Kilosort/issues/29
  392. % rez = remove_ks2_duplicate_spikes(rez);
  393. rez = find_merges(rez, 1); % final merges
  394. rez = splitAllClusters(rez, 1); % final splits by SVD
  395. rez = set_cutoff(rez); % decide on cutoff
  396. rez.good = get_good_units(rez); % eliminate widely spread waveforms (likely noise)
  397. fprintf('found %d good units', sum(rez.good>0))
  398. fprintf('Saving results to Phy ')
  399. rezToPhy(rez, '{output_folder}'); % write to Phy
  400. exit(1);
  401. '''
  402. default_ks25_ops = dict(
  403. datatype = 'dat',
  404. trange = [0.,np.inf],
  405. CAR = 1.,
  406. nblocks = 5.,
  407. sig = 20.,
  408. lam = 10.,
  409. AUCsplit = 0.9,
  410. minFR = 1./50,
  411. momentum = [20.,400.],
  412. sigmaMask = 30.,
  413. ThPre = 8.,
  414. spkTh = -6.,
  415. reorder = 1.,
  416. nskip = 25.,
  417. nfilt_factor = 4.,
  418. ntbuff = 64.,
  419. NT = 65600.,
  420. whiteningRange = 32.,
  421. nSkipCov = 25.,
  422. scaleproc = 200.,
  423. nPCs = 3,
  424. useRam = 0,
  425. nt0 = 61.)
  426. default_ks30_ops = dict(
  427. datatype = 'dat',
  428. trange = [0.,np.inf],
  429. CAR = 1.,
  430. nblocks = 5.,
  431. sig = 20.,
  432. lam = 20.,
  433. AUCsplit = 0.8,
  434. minFR = 1./50,
  435. momentum = [20.,400.],
  436. sigmaMask = 30.,
  437. ThPre = 8.,
  438. spkTh = -6.,
  439. reorder = 1.,
  440. nskip = 25.,
  441. nfilt_factor = 4.,
  442. ntbuff = 64.,
  443. NT = 65600.,
  444. whiteningRange = 32.,
  445. nSkipCov = 25.,
  446. scaleproc = 200.,
  447. nPCs = 3,
  448. useRam = 0,
  449. nt0 = 61.)
  450. class SpikeSorting(object):
  451. def __init__(raw_files, output_folder,
  452. filter_pipeline_par = [dict(function = 'bandpass_filter_gpu',
  453. sampling_rate = 30000,
  454. lowpass = 300,
  455. highpass = 10000,
  456. return_gpu = False),
  457. dict(function = 'global_car_gpu',
  458. return_gpu = True)],
  459. temporary_folder = None, motion_correction = True, **kwargs):
  460. ''' Run a spike sorter.
  461. 1) Creates the output folder.
  462. 2) Concatenates the input files.
  463. 3) Writes a json file with the onsets and offsets of each file, the channelmap
  464. 4) Downloads a sorter image and runs it in the temporaty folder
  465. 5) Copies the files to the output folder and cleans the temporary folder
  466. THIS IS A PLACEHOLDER FOR NOW.
  467. '''
  468. pass

sorting.py at commit 451691a, no license · at the source

Overview

  1. Allen Institute for Neural Dynamics,Seattle, WA USA
  2. UCL Institute of Ophthalmology, University College London,London, UK
  3. Department of Neurobiology and Biophysics, University of Washington,Seattle, WA USA
  4. Janelia Research Campus, Howard Hughes Medical Institute,Ashburn, VA USA
  5. Department of Biomedical Engineering, Johns Hopkins University,Baltimore, MD USA
  6. IMEC,Leuven, Belgium
  7. Allen Institute for Brain Science,Seattle, WA USA
  8. Wolfson Institute for Biomedical Research, University College London,London, UK
  9. Allen Institute MindScope Program,Seattle, WA USA
Institutions: Allen Institute for Neural Dynamics (United States); University College London (United Kingdom); University of Washington (United States); Howard Hughes Medical Institute (United States); Johns Hopkins University (United States); IMEC (Belgium); Allen Institute for Brain Science (United States); Allen Institute (United States)
Journal: Nature methods, volume 23, issue 6, pages 1207-1216
Dates: received 21 February 2025; accepted 24 March 2026; published online 1 June 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41592-026-03076-z · PMID 42225964 · PMCID PMC13259958 · OpenAlex W4407229385
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: extracellular electrophysiology (units, LFP) (modality), mouse (organism), systems (subfield)
Methods: Spectral & time-frequency, Preprocessing, Evoked potentials, fMRI & imaging, Single-unit activity, calcium imaging, Connectivity
Keywords: Extracellular recording, Neurophysiology, Optogenetics
MeSH: Electrophysiology*, Neurons*, Optogenetics*, Animals, Mice (* major topic)
Topic: Photoreceptor and optogenetics research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 8 papers (Europe PMC); 99 references in the paper
Research resources: RRID:AB_10000240, RRID:AB_2534096, Alexa Fluor 647 RRID:AB_2535809, RRID:AB_2665811

Abstract

High-resolution extracellular electrophysiology is the gold standard for recording spikes from distributed neural populations and is especially powerful when combined with optogenetics for manipulation of specific cell types with high temporal resolution. We integrated these approaches into prototype Neuropixels Opto probes, which combine electronic and photonic circuits. These devices pack 960 electrical recording sites and two sets of 14 light emitters onto a 70-μm-wide, 1-cm-long shank, allowing spatially addressable optogenetic stimulation with blue and red light. In mouse cortex, Neuropixels Opto probes delivered high-quality recordings together with spatially addressable optogenetics, differentially activating or silencing neurons at distinct cortical depths. In the mouse striatum and other deep structures, Neuropixels Opto probes delivered efficient optotagging, facilitating the identification of two cell types in parallel. Neuropixels Opto probes represent a promising tool for recording, identifying and manipulating neuronal populations.

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

Repositories

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

spkware/spks

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 451691a558650c80696a6871377f33986f3abb2f, 18 March 2026
Languages: Python (15), Jupyter (4)
Size: 21 files, 19 scripts
Software Heritage: not archived
Found in: the text, “Data processing”
Holds: README, environment (setup.py), 4 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: SciPy (6 files), Matplotlib (4 files), h5py (3 files), NumPy (3 files), PyTorch (2 files), Kilosort (1 file), pandas (1 file), SpikeInterface (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
20 files

AllenInstitute/MIES

License: BSD-2-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 5e6410391e0a92988c37df39c3ea435bd96af058, 17 September 2026
Languages: Shell (28), C/C++ (14), Python (2), C++ (2), C (1)
Size: 1,065 files, 47 scripts
Software Heritage: archived
Found in: the text, “In vitro electrophysiology and optogenetic stimu”
Holds: README, license file, environment (tools/documentation/Dockerfile, tools/documentation/requirements.in, tools/documentation/requirements.txt, tools/ftp-upload/Dockerfile, tools/nwb-read-tests/Dockerfile, tools/nwb-read-tests/requirements.in, tools/nwb-read-tests/requirements.txt, tools/pre-commit/Dockerfile, tools/pre-commit/requirements.in, tools/pre-commit/requirements.txt, tools/report-generator/Dockerfile), tests, continuous integration, documentation
Not found: CITATION.cff
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
49 files

codeocean.allenneuraldynamics.org/capsule/1593868

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Code 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)

Code availability

The code used in this study is available in open repositories under open access licenses. Code for the electrical and optical characterizations (Fig. 2 and Extended Data Figs. 2b–e and 3a,b) is available via Figshare at 10.5522/04/31268383 (ref. 97). Code for the experiments demonstrating recording and activation of local neural populations (Fig. 3 and Extended Data Figs. 4 and 5) is available via Zenodo at 10.5281/zenodo.18461445 (ref. 99). Code for the spatially resolved neural inactivations (Fig. 4 and Extended Data Figs. 6 and 7) is available via Figshare at 10.6084/m9.figshare.31271761 (ref. 98). Code for the subcortical optotagging experiments (Figs. 5 and 6 and Extended Data Figs. 3c–f, 8 and 10) is available via Code Ocean at https://codeocean.allenneuraldynamics.org/capsule/1593868/tree/v1.

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

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;
  • 66 scripts, each with its path and the digest of its content;
  • 5 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 availability

The data from this study are available in open repositories under open access licenses. Data from the electrical and optical characterizations (Fig. 2 and Extended Data Figs. 2b–e and 3a,b) are available via Figshare at 10.5522/04/31268383 (ref. 97). Data from the experiments demonstrating recording and activation of local neural populations (Fig. 3 and Extended Data Figs. 4 and 5) are available via Zenodo at 10.5281/zenodo.18461445. Data from the spatially resolved neural inactivations (Fig. 4 and Extended Data Figs. 6 and 7) are available via Figshare at 10.6084/m9.figshare.31271761 (ref. 98). Data from the subcortical optotagging experiments (Figs. 5 and 6 and Extended Data Figs. 3c–f, 8 and 10) are available via Code Ocean at https://codeocean.allenneuraldynamics.org/capsule/1593868/tree/v1.

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 2, 28 September 2026

  • Publisher: n/a → Nature Portfolio

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 29 authors, 3 keywords, 5 MeSH terms, 7 funders, 94 references, 4 RRIDs.

Cite

This paper

Lakunina, A. A., Socha, K. Z., Ladd, A. E., Bowen, A. J., Chen, S., Colonell, J., Doshi, A., Karsh, B., Krumin, M., Kulik, P., Li, A. J., Neutens, P., O’Callaghan, J., Olsen, M., Putzeys, J., Bai Reddy, C., Tilmans, H. A. C., Vargas, S., Welkenhuysen, M., . . . Carandini, M. (2026). Neuropixels Opto: combining high-resolution electrophysiology and optogenetics. Nature methods, 23(6), 1207-1216. https://doi.org/10.1038/s41592-026-03076-z

BibTeX

@article{lakunina2026neuropixels,
author = {Lakunina, Anna A. and Socha, Karolina Z. and Ladd, Alexander E. and Bowen, Anna J. and Chen, Susu and Colonell, Jennifer and Doshi, Anjal and Karsh, Bill and Krumin, Michael and Kulik, Pavel and Li, Anna J. and Neutens, Pieter and O’Callaghan, John and Olsen, Meghan and Putzeys, Jan and Bai Reddy, Charu and Tilmans, Harrie A. C. and Vargas, Sara and Welkenhuysen, Marleen and Ye, Zhiwen and Häusser, Michael and Koch, Christof and Ting, Jonathan T. and Dutta, Barundeb and Harris, Timothy D. and Steinmetz, Nicholas A. and Svoboda, Karel and Siegle, Joshua H. and Carandini, Matteo},
title = {{Neuropixels Opto: combining high-resolution electrophysiology and optogenetics}},
journal = {Nature methods},
year = {2026},
month = jun,
volume = {23},
number = {6},
pages = {1207--1216},
publisher = {Nature Portfolio},
issn = {1548-7091},
doi = {10.1038/s41592-026-03076-z},
url = {https://doi.org/10.1038/s41592-026-03076-z},
pmid = {42225964},
pmcid = {PMC13259958}
}

RIS

TY - JOUR
AU - Lakunina, Anna A.
AU - Socha, Karolina Z.
AU - Ladd, Alexander E.
AU - Bowen, Anna J.
AU - Chen, Susu
AU - Colonell, Jennifer
AU - Doshi, Anjal
AU - Karsh, Bill
AU - Krumin, Michael
AU - Kulik, Pavel
AU - Li, Anna J.
AU - Neutens, Pieter
AU - O’Callaghan, John
AU - Olsen, Meghan
AU - Putzeys, Jan
AU - Bai Reddy, Charu
AU - Tilmans, Harrie A. C.
AU - Vargas, Sara
AU - Welkenhuysen, Marleen
AU - Ye, Zhiwen
AU - Häusser, Michael
AU - Koch, Christof
AU - Ting, Jonathan T.
AU - Dutta, Barundeb
AU - Harris, Timothy D.
AU - Steinmetz, Nicholas A.
AU - Svoboda, Karel
AU - Siegle, Joshua H.
AU - Carandini, Matteo
TI - Neuropixels Opto: combining high-resolution electrophysiology and optogenetics
T2 - Nature methods
J2 - Nat Methods
PY - 2026
DA - 2026/06/01
VL - 23
IS - 6
SP - 1207
EP - 1216
SN - 1548-7091
PB - Nature Portfolio
DO - 10.1038/s41592-026-03076-z
UR - https://doi.org/10.1038/s41592-026-03076-z
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41592-026-03076-z",
"type": "article-journal",
"title": "Neuropixels Opto: combining high-resolution electrophysiology and optogenetics",
"container-title": "Nature methods",
"author": [
{
"family": "Lakunina",
"given": "Anna A."
},
{
"family": "Socha",
"given": "Karolina Z."
},
{
"family": "Ladd",
"given": "Alexander E."
},
{
"family": "Bowen",
"given": "Anna J."
},
{
"family": "Chen",
"given": "Susu"
},
{
"family": "Colonell",
"given": "Jennifer"
},
{
"family": "Doshi",
"given": "Anjal"
},
{
"family": "Karsh",
"given": "Bill"
},
{
"family": "Krumin",
"given": "Michael"
},
{
"family": "Kulik",
"given": "Pavel"
},
{
"family": "Li",
"given": "Anna J."
},
{
"family": "Neutens",
"given": "Pieter"
},
{
"family": "O’Callaghan",
"given": "John"
},
{
"family": "Olsen",
"given": "Meghan"
},
{
"family": "Putzeys",
"given": "Jan"
},
{
"family": "Bai Reddy",
"given": "Charu"
},
{
"family": "Tilmans",
"given": "Harrie A. C."
},
{
"family": "Vargas",
"given": "Sara"
},
{
"family": "Welkenhuysen",
"given": "Marleen"
},
{
"family": "Ye",
"given": "Zhiwen"
},
{
"family": "Häusser",
"given": "Michael"
},
{
"family": "Koch",
"given": "Christof"
},
{
"family": "Ting",
"given": "Jonathan T."
},
{
"family": "Dutta",
"given": "Barundeb"
},
{
"family": "Harris",
"given": "Timothy D."
},
{
"family": "Steinmetz",
"given": "Nicholas A."
},
{
"family": "Svoboda",
"given": "Karel"
},
{
"family": "Siegle",
"given": "Joshua H."
},
{
"family": "Carandini",
"given": "Matteo"
}
],
"container-title-short": "Nat Methods",
"volume": "23",
"issue": "6",
"page": "1207-1216",
"DOI": "10.1038/s41592-026-03076-z",
"PMID": "42225964",
"PMCID": "PMC13259958",
"ISSN": "1548-7091",
"publisher": "Nature Portfolio",
"URL": "https://doi.org/10.1038/s41592-026-03076-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
1
]
]
}
}

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.7554/elife.110170 [code]
Efficient and reproducible pipelines for spike sorting large-scale electrophysiology data.
Journal: eLife
In common: SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), pandas, 1 other tool, extracellular electrophysiology (units, LFP), mouse, 7 references, 2 authors
[2] doi:10.1038/s41467-026-71331-0 [code]
A multimodal approach for visualizing and identifying electrophysiological cell types in vivo.
Journal: Nature communications
In common: PyTorch, pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP), mouse, 5 references, 2 authors
[3] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: Kilosort, SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), 6 other tools, extracellular electrophysiology (units, LFP), mouse, 5 references
[4] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: Kilosort, SpikeInterface, Neurodata Without Borders (PyNWB, MatNWB), 6 other tools, systems, mouse, 2 references
[5] doi:10.1016/j.patter.2026.101590 [code]
Density-based longitudinal neuron tracking in high-density electrophysiological recordings.
Journal: Patterns (New York, N.Y.)
In common: Kilosort, SpikeInterface, h5py, 5 other tools, extracellular electrophysiology (units, LFP), 4 references
[6] doi:10.1016/j.crmeth.2026.101421 [code]
EthoPy provides an accessible platform for reproducible behavioral neuroscience.
Journal: Cell reports methods
In common: Neurodata Without Borders (PyNWB, MatNWB), h5py, pandas, 3 other tools, mouse, 7 references
[7] doi:10.1038/s41467-026-71664-w [code]
Dorsal prefrontal cortex drives perseverative behavior in mice.
Journal: Nature communications
In common: Kilosort, NumPy, systems, mouse, 8 references
[8] doi:10.7554/elife.109717 [code]
Retrosplenial cortex enables context-dependent goal-directed sensorimotor transformation.
Journal: eLife
In common: Kilosort, Neurodata Without Borders (PyNWB, MatNWB), h5py, 4 other tools, systems, mouse, 3 references
[9] doi:10.1038/s41467-026-76581-6 [code]
Thalamocortical bursts encode reward contingencies and drive associative learning.
Journal: Nature communications
In common: Kilosort, h5py, pandas, 3 other tools, systems, mouse, 4 references
[10] doi:10.1016/j.celrep.2026.117420 [code]
Neural population dynamics of direct electrical stimulation of neocortex.
Journal: Cell reports
In common: Neurodata Without Borders (PyNWB, MatNWB), pandas, SciPy, 2 other tools, extracellular electrophysiology (units, LFP), systems, mouse, 4 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.