OpenScope’s Vision2Hippocampus Dataset#

Introduction#

Extensive research shows that visual cortical neurons respond to specific stimuli, e.g. the primary visual cortical neurons respond to bars of light with specific orientation. In contrast, the hippocampal neurons are thought to encode not specific stimuli but instead represent abstract concepts such as space, time and events. How does this happen? Specifically, how does the representation of simple visual stimuli evolve from the thalamus, which is a synapse away from the retina, through primary visual cortex, higher order visual areas and all the way to hippocampus, that is farthest removed from the retina? That is the primary goal of this OpenScope project. We note that while visual cortical responses are commonly measured in stationary animals, with precise stimulus and behavioral control; hippocampal responses are measured in freely behaving rats where multisensory stimuli and multimodal behaviors contribute, but they are difficult to measure or controlled. Hence, recent research has pivoted to measuring hippocampal responses in virtual realty (VR)1–4. Although it was believed that hippocampal place cells in VR are similar to those in the real world, a careful and direct comparison showed that this is not the case5. In fact, contrary to common beliefs, when the multisensory and multimodal stimuli commonly present in the real world are eliminated in VR, these experiments revealed that vision and locomotion alone are not sufficient to generate hippocampal spatial selectivity6. Further, hippocampal neurons showed clear direction selectivity that was directly controlled by visual cues in VR7. Thus, elimination of nonspecific cues using VR revealed a growing similarity between hippocampal and visual cortical response to specific stimuli and that were behaviorally relevant8. To further confirm the sensory origins of hippocampal responses, subsequent experiments eliminated any specific behaviors, e.g. rewards, memory, navigation etc. and measured hippocampal responses in body- or head-fixed rodents to reveal strong selectivity to a moving bar of light9 with naso-temporal magnification commonly seen in V1. Further, careful analysis of prior OpenScope data from Allen Institute revealed that a black-and-white silent movie generated robust responses in the hippocampus that were related to the simultaneously measured visual cortical ones10.

Experiment design#

Two main categories of visual stimuli were presented–

  1. Simple visual motion, elicited by basic stimuli, like bars of light.

  2. Complex stimuli

Among each class, the following variants were used – Simple bars of light –

  1. Vertical bar, moving left right (naso-temporal)

  2. Horizontal bar, moving up and down and other variants

Complex visual stimuli – A 20 second clip of an eagle swooping down was used as the central “natural movie”, which was shown to all mice in the dataset. In 5 mice, another “natural movie” involving squirrels was used along with the eagle movie.

Examples of movies presented#

a) Simple stimuli : image.png

b) Complex stimuli : image-2.png

References#

  1. Holscher, C. et al. Rats are able to navigate in virtual environments. The Journal of experimental biology 208, 561–9 (2005).

  2. Harvey, C. D., Collman, F., Dombeck, D. A. & Tank, D. W. Intracellular dynamics of hippocampal place cells during virtual navigation. Nature 461, 941–946 (2009).

  3. Chen, G., King, J. a, Burgess, N. & O’Keefe, J. How vision and movement combine in the hippocampal place code. Proc Natl Acad Sci U S A 110, 378–383 (2013).

  4. Cushman, J. D. et al. Multisensory Control of Multimodal Behavior: Do the Legs Know What the Tongue Is Doing? PLoS ONE 8, e80465 (2013).

  5. Ravassard, P. et al. Multisensory control of hippocampal spatiotemporal selectivity. Science (New York, N.Y.) 340, 1342–6 (2013).

  6. Aghajan, Z. M. et al. Impaired spatial selectivity and intact phase precession in two-dimensional virtual reality. Nature neuroscience 18, 121–128 (2015).

  7. Acharya, L., Aghajan, Z. M., Vuong, C., Moore, J. J. & Mehta, M. R. Causal Influence of Visual Cues on Hippocampal Directional Selectivity. Cell 164, 197–207 (2016).

  8. Moore, J. J., Cushman, J. D., Acharya, L., Popeney, B. & Mehta, M. R. Linking hippocampal multiplexed tuning, Hebbian plasticity and navigation. Nature (2021) doi:10.1038/s41586-021-03989-z.

  9. Purandare, C. S. et al. Moving bar of light evokes vectorial spatial selectivity in the immobile rat hippocampus. doi:10.1038/s41586-022-04404-x.

  10. Purandare, C. & Mehta, M. Mega-scale movie-fields in the mouse visuo-hippocampal network. eLife 12, RP85069 (2023).

Environment Setup#

⚠️Note: If running on a new environment, run this cell once and then restart the kernel⚠️

try:
    from databook_utils.dandi_utils import dandi_download_open
except:
    !git clone https://github.com/AllenInstitute/openscope_databook.git
    %cd openscope_databook
    %pip install -e .
    %cd docs/projects
import os

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from math import floor, ceil, isclose
from PIL import Image

The Experiment#

session_files = pd.read_csv("../../data/vippo_sessions.csv")
session_files
identifier size path session_time sub_name sub_sex sub_age sub_genotype probes stim types #_units session_length
0 8ae65111-a130-47fc-a108-55e695374739 2448964467 sub-692077/sub-692077_ses-1300222049.nwb 2023-09-28 00:00:00-07:00 692077 F 89 wt/wt {'probeB', 'probeC', 'probeF', 'probeE', 'prob... {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 3091 7152.145567
1 d3cfc0e4-eaa6-4cc0-b1de-9ed257cf0009 2237699442 sub-695435/sub-695435_ses-1309235849.nwb 2023-11-07 00:00:00-08:00 695435 M 109 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 2251 7174.624310
2 24c71323-0446-4a06-83fa-f62ae21aba3e 2457471091 sub-715811/sub-715811_ses-1328842209.nwb 2024-02-06 00:00:00-08:00 715811 F 106 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2239 8117.399747
3 72e60080-722f-47f1-b8c0-91f9be1d5ef6 3073270590 sub-702134/sub-702134_ses-1324803287.nwb 2024-01-18 00:00:00-08:00 702134 F 135 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 3477 8124.426097
4 9b14e3b4-5d3e-4121-ae5e-ced7bc92af4e 2892209972 sub-702135/sub-702135_ses-1324561527.nwb 2024-01-17 00:00:00-08:00 702135 F 134 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2960 8125.099337
5 9b1bed43-3edf-40f9-a172-eb4c33883166 2923746400 sub-714614/sub-714614_ses-1327183358.nwb 2024-01-30 00:00:00-08:00 714614 F 106 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 3460 8113.779827
6 9c8ef583-a612-4e7b-90d9-c834806e84e2 2643094635 sub-715814/sub-715814_ses-1329090859.nwb 2024-02-07 00:00:00-08:00 715814 M 107 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2294 8116.172547
7 6e66329c-2b95-403e-bfcf-1cb8ff2691c4 3301910681 sub-714626/sub-714626_ses-1327655771.nwb 2024-02-01 00:00:00-08:00 714626 M 108 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 3046 8112.885197
8 1d25f344-b65d-4050-838e-bbf3904b0b04 3123949067 sub-714624/sub-714624_ses-1327374064.nwb 2024-01-31 00:00:00-08:00 714624 M 107 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 3605 8109.671497
9 ad8c2946-86af-47f2-9f04-19def80b2d9b 2833451306 sub-716465/sub-716465_ses-1330689294.nwb 2024-02-14 00:00:00-08:00 716465 M 107 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2602 8108.817027
10 9418589d-ee3a-4837-9cce-6cf84524f31a 2334526282 sub-716464/sub-716464_ses-1330382682.nwb 2024-02-13 00:00:00-08:00 716464 M 106 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2273 8110.698787
11 b5f0f3ce-a287-44a1-bf36-1d4d38534c8e 2540301812 sub-717441/sub-717441_ses-1332089263.nwb 2024-02-20 00:00:00-08:00 717441 F 99 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2243 8119.381567
12 ea3c40b4-b30f-47b8-92dc-bf5b74bddf78 2618315276 sub-719667/sub-719667_ses-1333741475.nwb 2024-02-27 00:00:00-08:00 719667 F 99 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'Stim01_SAC_Wd15_Vel2_White_loop_presentation... 2897 8385.106047
13 bdf7f7c0-32f9-4940-b841-11d0c080fa11 2550947461 sub-717439/sub-717439_ses-1332563048.nwb 2024-02-22 00:00:00-08:00 717439 M 101 wt/wt {'probeD', 'probeB', 'probeC', 'probeF', 'prob... {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2344 8117.585127
14 708adcdf-9785-4486-83db-55bae9fab3ef 2517103015 sub-717437/sub-717437_ses-1333971345.nwb 2024-02-28 00:00:00-08:00 717437 M 107 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'Stim01_SAC_Wd15_Vel2_White_loop_presentation... 2530 8382.822087
15 c16722d5-02c8-4fe4-b28d-be4c82091a62 1706916742 sub-719666/sub-719666_ses-1335486174.nwb 2024-03-05 00:00:00-08:00 719666 M 106 wt/wt {'probeB', 'probeD', 'probeC', 'probeF'} {'Stim01_SAC_Wd15_Vel2_White_loop_presentation... 1434 8413.343797
16 3a18da6d-a5fe-4f24-a5f1-b50a94a88345 798648285 sub-717438/sub-717438_ses-1334311030.nwb 2024-02-29 00:00:00-08:00 717438 M 108 wt/wt {'probeF'} {'Stim01_SAC_Wd15_Vel2_White_loop_presentation... 487 8379.578657
17 1fe81fa8-8151-4655-b39c-c38061e6e996 1747561795 sub-695764/sub-695764_ses-1311204385.nwb 2023-11-15 00:00:00-08:00 695764 F 113 wt/wt {'probeB', 'probeA', 'probeD'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 1940 7488.315567
18 3307a5f7-97aa-44fa-b298-ec4f86047e59 1555236201 sub-699321/sub-699321_ses-1312636156.nwb 2023-11-21 00:00:00-08:00 699321 F 95 wt/wt {'probeB', 'probeA', 'probeD'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 1640 7490.660227
19 97878bcd-4bda-44e4-b4f9-17489b56ca7d 1929898655 sub-695762/sub-695762_ses-1317448357.nwb 2023-12-13 00:00:00-08:00 695762 F 141 wt/wt {'probeB', 'probeA', 'probeD'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 1918 7487.871757
20 ad37746c-e32b-4301-9a6b-527b9763e595 2185603730 sub-695763/sub-695763_ses-1317661297.nwb 2023-12-14 00:00:00-08:00 695763 F 142 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 2860 7500.953257
21 16cac2a8-138e-4474-b4b8-624deef7a033 2082205355 sub-714615/sub-714615_ses-1325748772.nwb 2024-01-23 00:00:00-08:00 714615 F 99 wt/wt {'probeB', 'probeA', 'probeF'} {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2738 8218.034317
22 5acf575f-9fb2-4c80-b80e-892a4c6f720c 1840809674 sub-699322/sub-699322_ses-1317198704.nwb 2023-12-12 00:00:00-08:00 699322 F 116 wt/wt {'probeD', 'probeA', 'probeF'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 1833 7554.599297
23 79fd93db-83d4-4562-bd4e-def83c27ef75 2555260068 sub-714612/sub-714612_ses-1325995398.nwb 2024-01-24 00:00:00-08:00 714612 M 100 wt/wt {'probeB', 'probeF', 'probeA', 'probeD'} {'Stim04_SAC_Wd15_Vel2_Black_loop_presentation... 2342 8098.457047
24 053cd3f0-2d6f-436e-8d15-238f2d676605 1886052673 sub-717438/sub-717438_ses-1334311030_ecephys.nwb 2024-02-29 00:00:00-08:00 717438 M 108 wt/wt {'probeF'} set() 0 -1.000000
25 fbcd4fe5-7107-41b2-b154-b67f783f23dc 2251848036 sub-692072/sub-692072_ses-1298465622.nwb 2023-09-21 00:00:00-07:00 692072 M 82 wt/wt {'probeB', 'probeE', 'probeA', 'probeF'} {'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_present... 2764 7157.149566
m_count = len(session_files["sub_sex"][session_files["sub_sex"] == "M"])
f_count = len(session_files["sub_sex"][session_files["sub_sex"] == "F"])
sst_count = len(session_files[session_files["sub_genotype"].str.count("Sst") >= 1])
pval_count = len(session_files[session_files["sub_genotype"].str.count("Pval") >= 1])
wt_count = len(session_files[session_files["sub_genotype"].str.count("wt/wt") >= 1])

print("Dandiset Overview:")
print(len(session_files), "files")
print(len(set(session_files["sub_name"])), "subjects", m_count, "males,", f_count, "females")
print(sst_count, "sst,", pval_count, "pval,", wt_count, "wt")
Dandiset Overview:
26 files
25 subjects 13 males, 13 females
0 sst, 0 pval, 26 wt

Downloading Ecephys File#

dandiset_id = "000690"
dandi_filepath = "sub-692072/sub-692072_ses-1298465622.nwb"
download_loc = "."
dandi_api_key = os.environ["DANDI_API_KEY"]
# This can sometimes take a while depending on the size of the file
io = dandi_download_open(dandiset_id, dandi_filepath, download_loc, dandi_api_key=dandi_api_key)
nwb = io.read()
A newer version (0.67.1) of dandi/dandi-cli is available. You are using 0.61.2
File already exists
Opening file
c:\Users\carter.peene\Desktop\Projects\openscope_databook\databook_env\lib\site-packages\hdmf\utils.py:668: UserWarning: Ignoring cached namespace 'hdmf-common' version 1.6.0 because version 1.8.0 is already loaded.
  return func(args[0], **pargs)
c:\Users\carter.peene\Desktop\Projects\openscope_databook\databook_env\lib\site-packages\hdmf\utils.py:668: UserWarning: Ignoring cached namespace 'hdmf-experimental' version 0.3.0 because version 0.5.0 is already loaded.
  return func(args[0], **pargs)

Showing Probe Tracks#

The images below were rendered using the Visualizing Neuropixel Probe Locations notebook. The probes are using the Common Coordinate Framework (CCF). The experiment uses six probes labeled A-F to target various regions.

sagittal_view = Image.open("../../data/images/vippo_probes_sagittal.png")
dorsal_view = Image.open("../../data/images/vippo_probes_dorsal.png")
transverse_view = Image.open("../../data/images/vippo_probes_transverse.png")
fig, axes = plt.subplots(1,3,figsize=(20,60))

axes[0].imshow(sagittal_view)
axes[1].imshow(dorsal_view)
axes[2].imshow(transverse_view)
for ax in axes:
    ax.axis("off")
../_images/43bd9d704a0914208840d73c86439191780ba8ca82901c79e68c2407948eda2d.png

Extracting Units Spikes#

Below, the Units table is retrieved from the file. It contains many metrics for every putative neuronal unit, printed below. For the analysis in this notebook, we are only interested in the spike_times attribute. This is an array of timestamps that a spike is measured for each unit. For more information on the various unit metrics, see Visualizing Unit Quality Metrics. From this table, the Units used in this notebook are selected if they have ‘good’ quality rather than ‘noise’, and if they belong in one of the regions of the primary visual cortex.

units = nwb.units
units[:10]
recovery_slope l_ratio d_prime max_drift firing_rate isi_violations presence_ratio spread velocity_above repolarization_slope ... PT_ratio snr nn_hit_rate cumulative_drift amplitude_cutoff silhouette_score local_index spike_times spike_amplitudes waveform_mean
id
12 -0.140852 0.000057 6.233920 31.97 1.992914 1.224279 0.99 50.0 -0.343384 0.576991 ... 0.596259 2.713103 0.995333 728.41 0.498130 -1.000000 0 [22.76992282041222, 22.783856106625358, 22.812... [0.00017990238915862748, 0.0001158438415850443... [[0.0, 0.3712799999999996, 0.2856749999999997,...
13 -0.120064 0.000212 6.380643 33.91 1.516987 0.100634 0.99 30.0 -0.343384 0.818315 ... 0.350053 4.435071 0.992157 452.22 0.005924 0.187985 1 [22.86008918215036, 22.966555488764637, 35.524... [0.0002144826054165842, 0.0002642146285980156,... [[0.0, -0.43894499999999814, -0.71662499999999...
14 -0.061008 0.001185 4.884140 56.95 0.703265 2.528098 0.99 40.0 0.206030 0.241781 ... 0.707848 2.236984 0.929936 658.63 0.500000 0.151979 2 [22.71692299964932, 22.775156136047297, 33.697... [0.00019346088549011372, 0.0001325490015848021... [[0.0, -0.30556500000000075, -0.73787999999999...
15 -0.149691 0.002038 5.300020 6.65 0.047692 20.360357 0.89 30.0 0.343384 0.620252 ... 0.635339 2.293410 0.370370 0.00 0.160257 -1.000000 3 [33.70428584210749, 131.43085534614167, 558.25... [0.00012903155020274154, 0.0001204155056725696... [[0.0, 0.14145077720207022, 0.8673963730569932...
16 -0.060737 0.001665 3.732261 39.00 0.766648 0.078791 0.99 30.0 -4.807370 0.416142 ... 0.722337 2.292777 0.961187 804.07 0.006804 0.098607 4 [33.670485956413415, 35.54281295782981, 36.159... [0.00012879827888614498, 0.0001422417048717127... [[0.0, -0.3408599999999984, -0.251549999999999...
17 -0.049657 0.000373 4.709497 29.89 1.727398 0.046559 0.99 30.0 0.000000 0.868783 ... 0.260276 3.844790 0.991696 368.11 0.004070 0.265589 5 [20.942295667827928, 22.715523004383883, 22.71... [0.00017015089162517638, 0.0001707744923264808... [[0.0, -1.160835000000001, -0.6550050000000008...
18 -0.043094 0.020139 3.875737 74.63 28.778887 0.054798 0.99 70.0 -0.206030 0.565132 ... 0.235380 2.835290 0.904667 374.82 0.031949 0.235098 6 [21.40562743424573, 22.70858969449792, 22.7189... [0.00027740872559489914, 0.0001854297104938351... [[0.0, -0.32175000000000087, -0.57954000000000...
19 -0.142276 0.013372 4.586741 16.11 0.192743 0.000000 0.23 50.0 0.343384 0.789448 ... 0.423055 3.698435 0.154762 39.25 0.021344 -1.000000 7 [614.5419882065542, 1230.2982391486003, 1247.0... [0.0002817193886807515, 0.00034835102540897326... [[0.0, -1.4075100000000011, -1.464840000000001...
20 -0.088705 0.024535 3.589084 83.15 3.554133 0.021996 0.93 50.0 0.343384 0.558968 ... 0.687279 1.877844 0.836000 395.03 0.005054 0.181590 8 [291.4390142235864, 291.4497141874008, 291.688... [0.00021363707722923971, 0.0001794111687378251... [[0.0, 0.9748050000000008, 2.442375, 2.3462400...
21 -0.063367 0.026352 2.811219 81.15 2.383342 0.244578 0.95 50.0 0.000000 0.351896 ... 0.433562 2.194935 0.744318 413.43 0.378081 0.037392 9 [141.24085550358953, 232.9827785800948, 293.68... [0.00013368688760067714, 0.0001396842146963983... [[0.0, -0.18564000000000025, 0.109785000000000...

10 rows × 29 columns

nwb.electrodes[:10]
location group group_name probe_vertical_position probe_horizontal_position probe_id local_index valid_data x y z imp filtering
id
0 PF probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 20 43 0 0 True 7466.0 3423.0 6680.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
1 PF probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 20 11 0 1 True 7465.0 3414.0 6682.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
2 PF probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 40 59 0 2 True 7465.0 3406.0 6685.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
3 PF probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 40 27 0 3 True 7464.0 3397.0 6687.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
4 PF probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 60 43 0 4 True 7464.0 3388.0 6690.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
5 TH probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 60 11 0 5 True 7463.0 3380.0 6693.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
6 TH probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 80 59 0 6 True 7462.0 3371.0 6695.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
7 TH probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 80 27 0 7 True 7462.0 3362.0 6698.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
8 TH probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 100 43 0 8 True 7461.0 3354.0 6700.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
9 TH probeA abc.EcephysElectrodeGroup at 0x22828407... probeA 100 11 0 9 True 7461.0 3345.0 6703.0 NaN AP band: 500 Hz high-pass; LFP band: 1000 Hz l...
# select electrodes
channel_locations = {}
channel_probes = {}
channel_idxs = {}

electrodes = nwb.electrodes
for i in range(len(electrodes)):
    channel_id = electrodes["id"][i]
    location = electrodes["location"][i]
    local_idx = electrodes["local_index"][i]
    probe = electrodes["group_name"][i]
    channel_locations[channel_id] = location
    channel_probes[channel_id] = probe
    channel_idxs[channel_id] = local_idx

# function aligns location information from electrodes table with channel id from the units table
def get_unit_location(row):
    return channel_locations[int(row.peak_channel_id)]

def get_unit_probe(row):
    return channel_probes[int(row.peak_channel_id)]

print(set(get_unit_location(row) for row in units))
print(set(channel_probes.values()))
{'VISa6b', 'RSPagl1', 'CA1', 'VPM', 'RSPagl2/3', 'VISli5', 'VPL', 'RSPagl5', 'RT', 'MOp5', 'MOp2/3', 'VISli4', 'DG-mo', 'SSp-bfd2/3', 'VISli6b', 'LP', 'VISa6a', 'TH', 'MOp6b', 'root', 'CP', 'VISli6a', 'PF', 'SSp-bfd6a', 'DG-sg', 'RSPagl6a', 'SSp-bfd4', 'DG-po', 'HPF', 'SSp-bfd5', 'SUB', 'VISli1', 'MOp6a', 'VISli2/3'}
{'probeB', 'probeA', 'probeF', 'probeE'}
### selecting units spike times

brain_regions = ["VISli2/3", "VISa6a", "VISli1", "VISli5", "VISa6b", "VISli6a", "VISli6b", "VISli4"]

# select units based if they have 'good' quality and exists in one of the specified brain_regions
units_spike_times = []
for row in units:
    # if get_unit_location(row) in brain_regions and row.quality.item() == "good":
    if get_unit_location(row) in brain_regions and row.quality.item() == "good":
        units_spike_times.append(row.spike_times.item())

units_spike_times = np.array(units_spike_times, dtype=object)
print(len(units_spike_times))
311

Session Timeline#

# extract epoch times from stim table where stimulus rows have a different 'block' than following row
# returns list of epochs, where an epoch is of the form (stimulus name, stimulus block, start time, stop time)
def extract_epochs(stim_name, stim_table, epochs):
    
    # specify a current epoch stop and start time
    epoch_start = stim_table.start_time[0]
    epoch_stop = stim_table.stop_time[0]

    # for each row, try to extend current epoch stop_time
    for i in range(len(stim_table)):
        this_block = stim_table.stimulus_block[i]
        # if end of table, end the current epoch
        if i+1 >= len(stim_table):
            epochs.append((stim_name, this_block, epoch_start, epoch_stop))
            break
            
        next_block = stim_table.stimulus_block[i+1]
        # if next row is the same stim block, push back epoch_stop time
        if next_block == this_block:
            epoch_stop = stim_table.stop_time[i+1]
        # otherwise, end the current epoch, start new epoch
        else:
            epochs.append((stim_name, this_block, epoch_start, epoch_stop))
            epoch_start = stim_table.start_time[i+1]
            epoch_stop = stim_table.stop_time[i+1]
    
    return epochs
# extract epochs from all valid stimulus tables
epochs = []
for stim_name in nwb.intervals.keys():
    stim_table = nwb.intervals[stim_name]
    try:
        epochs = extract_epochs(stim_name, stim_table, epochs)
    except:
        continue

# manually add optotagging epoch since the table is stored separately
# opto_stim_table = nwb.processing["optotagging"]["optogenetic_stimulation"]
# opto_epoch = ("optogenetic_stimulation", 1.0, opto_stim_table.start_time[0], opto_stim_table.stop_time[-1])
# epochs.append(opto_epoch)

# epochs take the form (stimulus name, stimulus block, start time, stop time)
print(len(epochs))
epochs.sort(key=lambda x: x[2])
for epoch in epochs:
    print(epoch)
21
('SAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 0.0, 113.10293015389608, 593.538290153896)
('SAC_Wd15_Vel2_Bndry2_Cntst0_loop_presentations', 1.0, 593.538290153896, 877.776270153896)
('SAC_Wd15_Vel2_Bndry3_Cntst0_loop_presentations', 2.0, 877.776270153896, 1250.0879201538962)
('SAC_Wd15_Vel8_Bndry1_Cntst0_loop_presentations', 3.0, 1250.0879201538962, 2210.909000153896)
('SAC_Wd45_Vel2_Bndry1_Cntst0_loop_presentations', 4.0, 2210.909000153896, 2451.110230153896)
('SAC_Wd15_Vel2_Bndry1_Cntst1_loop_presentations', 5.0, 2451.110230153896, 2691.311240153896)
('SAC_Wd15_Vel2_Bndry2_Cntst0_oneway_presentations', 6.0, 2691.311240153896, 2833.430250153896)
('UD_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 7.0, 2833.430250153896, 3313.832440153896)
('Ring_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 8.0, 3313.832440153896, 3794.234680153896)
('Disk_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 9.0, 3794.234680153896, 4034.435770153896)
('curl_Wd15_Vel2_Bndry1_Cntst0_oneway_presentations', 10.0, 4034.435770153896, 4154.536360153897)
('acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_presentations', 11.0, 4154.536360153897, 4274.636890153897)
('GreenSAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 12.0, 4274.636890153897, 4514.837970153896)
('Disco2SAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 13.0, 4514.837970153896, 4995.2401701538965)
('natmovie_CricketsOnARock_540x960Full_584x460Active_presentations', 14.0, 4995.2401701538965, 5235.474590153897)
('natmovie_EagleSwooping1_540x960Full_584x460Active_presentations', 15.0, 5235.474590153897, 5475.675670153896)
('natmovie_EagleSwooping2_540x960Full_584x460Active_presentations', 16.0, 5475.675670153896, 5715.876700153896)
('natmovie_SnakeOnARoad_540x960Full_584x460Active_presentations', 17.0, 5715.876700153896, 5956.0777301538965)
('natmovie_Squirreland3Mice_540x960Full_584x460Active_presentations', 18.0, 5956.0777301538965, 6196.312110153896)
('receptive_field_block_presentations', 19.0, 6196.312110153896, 6676.714190153896)
('receptive_field_block_presentations', 20.0, 6676.714190153896, 7157.149566495826)
time_start = floor(min([epoch[2] for epoch in epochs]))
time_end = ceil(max([epoch[3] for epoch in epochs]))
all_units_spike_times = np.concatenate(units_spike_times).ravel()
print(time_start, time_end)

# make histogram of unit spikes per second over specified timeframe
time_bin_edges = np.linspace(time_start, time_end, (time_end-time_start))
hist, bins = np.histogram(all_units_spike_times, bins=time_bin_edges)
113 7158
# generate plot of spike histogram with colored epoch intervals and legend
fig, ax = plt.subplots(figsize=(15,5))

# assign unique color to each stimulus name
stim_names = list({epoch[0] for epoch in epochs})
colors = plt.cm.rainbow(np.linspace(0,1,len(stim_names)))
stim_color_map = {stim_names[i]:colors[i] for i in range(len(stim_names))}

epoch_key = {}
height = max(hist)
# draw colored rectangles for each epoch
for epoch in epochs:
    stim_name, stim_block, epoch_start, epoch_end = epoch
    color = stim_color_map[stim_name]
    rec = ax.add_patch(mpl.patches.Rectangle((epoch_start, 0), epoch_end-epoch_start, height, alpha=0.2, facecolor=color))
    epoch_key[stim_name] = rec

ax.set_xlim(time_start, time_end)
ax.set_ylim(-0.1, height+0.1)
ax.set_xlabel("time (s)")
ax.set_ylabel("# spikes")
ax.set_title("All Unit Spikes Per Second Throughout Epochs")

fig.legend(epoch_key.values(), epoch_key.keys(), loc="lower right", bbox_to_anchor=(1.3, 0.25))
ax.plot(bins[:-1], hist)
[<matplotlib.lines.Line2D at 0x213840567d0>]
../_images/fc36e27fd27a845f7ba2633917ef15a811c186f5f4e14796560d0aa10384f7be.png

Extracting Stimulus Times#

nwb.intervals.keys()
dict_keys(['Disco2SAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'Disk_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'GreenSAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'Ring_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'SAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'SAC_Wd15_Vel2_Bndry1_Cntst1_loop_presentations', 'SAC_Wd15_Vel2_Bndry2_Cntst0_loop_presentations', 'SAC_Wd15_Vel2_Bndry2_Cntst0_oneway_presentations', 'SAC_Wd15_Vel2_Bndry3_Cntst0_loop_presentations', 'SAC_Wd15_Vel8_Bndry1_Cntst0_loop_presentations', 'SAC_Wd45_Vel2_Bndry1_Cntst0_loop_presentations', 'UD_Wd15_Vel2_Bndry1_Cntst0_loop_presentations', 'acurl_Wd15_Vel2_Bndry1_Cntst0_oneway_presentations', 'curl_Wd15_Vel2_Bndry1_Cntst0_oneway_presentations', 'invalid_times', 'natmovie_CricketsOnARock_540x960Full_584x460Active_presentations', 'natmovie_EagleSwooping1_540x960Full_584x460Active_presentations', 'natmovie_EagleSwooping2_540x960Full_584x460Active_presentations', 'natmovie_SnakeOnARoad_540x960Full_584x460Active_presentations', 'natmovie_Squirreland3Mice_540x960Full_584x460Active_presentations', 'receptive_field_block_presentations'])
stim_table = nwb.intervals["SAC_Wd15_Vel2_Bndry1_Cntst0_loop_presentations"]
print(np.mean(np.diff(stim_table.start_time)))
print({elem for elem in stim_table.orientation if not np.isnan(elem)})
0.01668178338976802
{0.0}
stim_table[:10]
start_time stop_time stimulus_name stimulus_block frame color contrast opacity orientation size units stimulus_index tags timeseries
id
0 113.102930 113.119610 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 0.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(0, 1, timestamps pynwb.base.TimeSeries at 0x...
1 113.119610 113.136289 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 1.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(1, 1, timestamps pynwb.base.TimeSeries at 0x...
2 113.136289 113.152969 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 2.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(2, 1, timestamps pynwb.base.TimeSeries at 0x...
3 113.152969 113.169648 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 3.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(3, 1, timestamps pynwb.base.TimeSeries at 0x...
4 113.169648 113.186328 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 4.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(4, 1, timestamps pynwb.base.TimeSeries at 0x...
5 113.186328 113.203007 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 5.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(5, 1, timestamps pynwb.base.TimeSeries at 0x...
6 113.203007 113.219687 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 6.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(6, 1, timestamps pynwb.base.TimeSeries at 0x...
7 113.219687 113.236366 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 7.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(7, 1, timestamps pynwb.base.TimeSeries at 0x...
8 113.236366 113.253046 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 8.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(8, 1, timestamps pynwb.base.TimeSeries at 0x...
9 113.253046 113.269725 SAC_Wd15_Vel2_Bndry1_Cntst0_loop 0.0 9.0 [1.0, 1.0, 1.0] 1.0 1.0 0.0 [1920.0, 1080.0] pix 0.0 [stimulus_time_interval] [(9, 1, timestamps pynwb.base.TimeSeries at 0x...
# select times where there is a local oddball
# stim_select = lambda prev_row, row, next_row: prev_row.orientation.item() == 135.0 and row.orientation.item() == 45.0 and np.isnan(next_row.orientation.item())
stim_select = lambda row: float(row.frame) == 40.0
stim_times = [float(stim_table[i].start_time) for i in range(len(stim_table)) if stim_select(stim_table[i])]
print(len(stim_times))
120

Generating Spike Matrix#

# bin size for counting spikes
time_resolution = 0.005

# start and end times (relative to the stimulus at 0 seconds) that we want to examine and align spikes to
window_start_time = -0.25
window_end_time = 0.5
def get_spike_matrix(units_spike_times, stim_times, bin_edges, time_resolution):
    n_units = len(units_spike_times)
    n_trials = len(stim_times)

    # 3D spike matrix to be populated with spike counts
    spike_matrix = np.zeros((n_units, n_trials, len(bin_edges)))

    # populate 3D spike matrix for each unit for each stimulus trial by counting spikes into bins
    for unit_idx in range(n_units):
        spike_times = units_spike_times[unit_idx]

        for stim_idx, stim_time in enumerate(stim_times):
            # get spike times that fall within the bin's time range relative to the stim time        
            first_bin_time = stim_time + bin_edges[0]
            last_bin_time = stim_time + bin_edges[-1]
            first_spike_in_range, last_spike_in_range = np.searchsorted(spike_times, [first_bin_time, last_bin_time])
            spike_times_in_range = spike_times[first_spike_in_range:last_spike_in_range]

            # convert spike times into relative time bin indices
            bin_indices = ((spike_times_in_range - (first_bin_time)) / time_resolution).astype(int)
            
            # mark that there is a spike at these bin times for this unit on this stim trial
            for bin_idx in bin_indices:
                spike_matrix[unit_idx, stim_idx, bin_idx] += 1

    return spike_matrix
# time bins used
n_bins = int((window_end_time - window_start_time) / time_resolution)
bin_edges = np.linspace(window_start_time, window_end_time, n_bins, endpoint=True)

# calculate baseline and stimulus interval indices for use later
stimulus_onset_idx = int(-bin_edges[0] / time_resolution)

spike_matrix = get_spike_matrix(units_spike_times, stim_times, bin_edges, time_resolution)

print(spike_matrix.shape)
(311, 120, 150)

Showing Response Windows#

After generating spike matrices, we can view the PSTHs for each unit.

def show_response(ax, window, window_start_time, window_end_time, aspect="auto", vmin=None, vmax=None, yticklabels=[], skipticks=1, xlabel="Time (s)", ylabel="ROI", cbar=True, cbar_label=None):
    if len(window) == 0:
        print("Input data has length 0; Nothing to display")
        return

    img = ax.imshow(window, aspect=aspect, extent=[window_start_time, window_end_time, 0, len(window)], interpolation="none", vmin=vmin, vmax=vmax)
    if cbar:
        ax.colorbar(img, shrink=0.5, label=cbar_label)

    ax.plot([0,0],[0, len(window)], ":", color="white", linewidth=1.0)

    if len(yticklabels) != 0:
        ax.set_yticks(range(len(yticklabels)))
        ax.set_yticklabels(yticklabels, fontsize=8)

        n_ticks = len(yticklabels[::skipticks])
        ax.yaxis.set_major_locator(plt.MaxNLocator(n_ticks))

    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
def show_many_responses(windows, rows, cols, window_idxs=None, title=None, subplot_title="", xlabel=None, ylabel=None, cbar_label=None, vmin=0, vmax=2):
    if window_idxs is None:
        window_idxs = range(len(windows))
    windows = windows[window_idxs]
    
    # handle case with no input data
    if len(windows) == 0:
        print("Input data has length 0; Nothing to display")
        return
    # handle cases when there aren't enough windows for number of rows
    if len(windows) < rows*cols:
        rows = (len(windows) // cols) + 1

    fig, axes = plt.subplots(rows, cols, figsize=(2*cols, 3*rows), layout="constrained")
    # handle case when there's only one row
    if len(axes.shape) == 1:
        axes = axes.reshape((1, axes.shape[0]))

    for i in range(rows*cols):
        ax_row = int(i // cols)
        ax_col = i % cols
        ax = axes[ax_row][ax_col]
        
        if i > len(windows)-1:
            ax.set_visible(False)
            continue

        window = windows[i]
        show_response(ax, window, window_start_time, window_end_time, xlabel=xlabel, ylabel=ylabel, cbar=False, vmin=vmin, vmax=vmax)
        ax.set_title(f"{subplot_title} {window_idxs[i]}")
        if ax_row != rows-1:
            ax.get_xaxis().set_visible(False)
        if ax_col != 0:
            ax.get_yaxis().set_visible(False)

    fig.suptitle(title)
    norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
    colorbar = fig.colorbar(mpl.cm.ScalarMappable(norm=norm), ax=axes, shrink=2/cols, label=cbar_label)
show_many_responses(spike_matrix, 5, 10)
../_images/fa2cffff10777ba5f3578e19f9b25a9a10340215e9cdf34742873353343048b6.png

Selecting Responsive Cells#

As discussed in Statistically Testing 2P Responses to Stimulus, the criteria used to select for responsive cells can have a significant impact. Here, the simple criterion is to select units whose post-stimulus z-scores are greater than 1 or less than -1.

def select_cells(spike_matrix, stimulus_onset_idx):
    baseline_means = np.mean(spike_matrix[:,:,:stimulus_onset_idx], axis=2)
    mean_baseline_means = np.mean(baseline_means, axis=1)
    std_baseline_means = np.std(baseline_means, axis=1)

    response_means = np.mean(spike_matrix[:,:,stimulus_onset_idx:], axis=2)
    mean_response_means = np.mean(response_means, axis=1)

    unit_z_scores = (mean_response_means - mean_baseline_means) / std_baseline_means
    return np.where(np.logical_or(unit_z_scores > 1, unit_z_scores < -1))[0]
selected_idxs = select_cells(spike_matrix, stimulus_onset_idx)
show_many_responses(spike_matrix[selected_idxs], 5, 10)
C:\Users\carter.peene\AppData\Local\Temp\ipykernel_30496\1852969462.py:9: RuntimeWarning: divide by zero encountered in divide
  unit_z_scores = (mean_response_means - mean_baseline_means) / std_baseline_means
C:\Users\carter.peene\AppData\Local\Temp\ipykernel_30496\1852969462.py:9: RuntimeWarning: invalid value encountered in divide
  unit_z_scores = (mean_response_means - mean_baseline_means) / std_baseline_means
../_images/243458e67164022fd0b9fc4f7961c4418acfc97fc023a3758d105dfde489f622.png

Receptive Fields#

rf_stim_table = nwb.intervals["receptive_field_block_presentations"].to_dataframe()
rf_stim_table[:10]
start_time stop_time stimulus_name stimulus_block color contrast mask opacity orientation size units stimulus_index temporal_frequency spatial_frequency x_position y_position phase tags timeseries
id
0 6196.312110 6196.562318 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 -10.0 -30.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364680, 1, timestamps pynwb.base.TimeSeries ...
1 6196.562318 6196.812525 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 20.0 -30.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364681, 1, timestamps pynwb.base.TimeSeries ...
2 6196.812525 6197.062733 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 45.0 [20.0, 20.0] deg 19.0 4.0 0.08 -10.0 -30.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364682, 1, timestamps pynwb.base.TimeSeries ...
3 6197.062733 6197.312941 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 30.0 10.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364683, 1, timestamps pynwb.base.TimeSeries ...
4 6197.312941 6197.563156 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 45.0 [20.0, 20.0] deg 19.0 4.0 0.08 20.0 40.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364684, 1, timestamps pynwb.base.TimeSeries ...
5 6197.563156 6197.813370 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 -40.0 10.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364685, 1, timestamps pynwb.base.TimeSeries ...
6 6197.813370 6198.063585 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 90.0 [20.0, 20.0] deg 19.0 4.0 0.08 20.0 30.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364686, 1, timestamps pynwb.base.TimeSeries ...
7 6198.063585 6198.313800 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 20.0 40.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364687, 1, timestamps pynwb.base.TimeSeries ...
8 6198.313800 6198.564005 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 45.0 [20.0, 20.0] deg 19.0 4.0 0.08 -40.0 -30.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364688, 1, timestamps pynwb.base.TimeSeries ...
9 6198.564005 6198.814210 receptive_field_block 19.0 [1.0, 1.0, 1.0] 0.8 circle 1.0 0.0 [20.0, 20.0] deg 19.0 4.0 0.08 0.0 -40.0 [26231.93333333, 26231.93333333] [stimulus_time_interval] [(364689, 1, timestamps pynwb.base.TimeSeries ...
### get x and y coordinates of gabors displayed to build receptive field

xs = np.sort(list(set(rf_stim_table.x_position)))
ys = np.sort(list(set(rf_stim_table.y_position)))
field_units = rf_stim_table.units[0]
print(xs)
print(ys)
print(field_units)
[-40. -30. -20. -10.   0.  10.  20.  30.  40.]
[-40. -30. -20. -10.   0.  10.  20.  30.  40.]
deg
### get receptive field of a unit using its spike times and the stim table

def get_rf(spike_times):
    # creates 2D array that stores response spike counts for each coordinate of the receptive field
    unit_rf = np.zeros([ys.size, xs.size])
    # for every x and y coordinate in the field
    for xi, x in enumerate(xs):
        for yi, y in enumerate(ys):
            
            # for this coordinate of the rf, count all the times that this neuron responds to a stimulus time with a spike
            stim_times = rf_stim_table[(rf_stim_table.x_position == x) & (rf_stim_table.y_position == y)].start_time
            response_spike_count = 0
            for stim_time in stim_times:
                # any spike within 0.2 seconds after stim time is considered a response
                start_idx, end_idx = np.searchsorted(spike_times, [stim_time, stim_time+0.2])
                response_spike_count += end_idx-start_idx

            unit_rf[yi, xi] = response_spike_count
    
    return unit_rf
### compute receptive fields for each unit in selected units

rfs = []
# for idx in selected_idxs:
for idx in range(spike_matrix.shape[0]):
    these_spike_times = units_spike_times[idx]
    rfs.append(get_rf(these_spike_times))
### display the receptive fields for each unit in a 2D plot

def display_rfs(rfs, n_cols=10):
    if len(rfs) == 0:
        print("No receptive fields provided. Nothing to display")
        return

    n_rows = len(rfs) // n_cols
    fig, axes = plt.subplots(n_rows+1, 10)
    fig.set_size_inches(12, n_rows+1)

    # handle case where there's <= n_cols rfs
    if len(axes.shape) == 1:
        axes = axes.reshape((1, axes.shape[0]))

    for irf, rf in enumerate(rfs):
        ax_row = int(irf/10)
        ax_col = irf%10
        axes[ax_row][ax_col].imshow(rf, origin="lower")
    for ax in axes.flat[1:]:
        ax.axis('off')

    # making axis labels for first receptive field
    axes[0][0].set_xlabel(field_units)
    axes[0][0].set_ylabel(field_units)
    axes[0][0].xaxis.set_label_position("top") 
    axes[0][0].xaxis.set_ticks_position("top")
    axes[0][0].set_xticks(range(len(xs)), xs, rotation=90, fontsize=6)
    axes[0][0].set_yticks(range(len(ys)), ys, fontsize=6)
    [l.set_visible(False) for (i,l) in enumerate(axes[0][0].xaxis.get_ticklabels()) if i % 2 != 0]
    [l.set_visible(False) for (i,l) in enumerate(axes[0][0].yaxis.get_ticklabels()) if i % 2 != 0]
display_rfs(rfs)
../_images/baaa690e36f3deebb255bc79d3420216834e0cadebebc2ca5e510695a175362d.png