{ "cells": [ { "cell_type": "markdown", "id": "32647951-7dcf-4553-b42a-5d8508d95cf9", "metadata": {}, "source": [ "\"Open   " ] }, { "cell_type": "markdown", "id": "40761f4a-e1a9-4b67-9730-5d554d380e91", "metadata": {}, "source": [ "# Data Explorer" ] }, { "cell_type": "markdown", "id": "f07451ff-cf56-4763-b4ac-bd099ba1f145", "metadata": {}, "source": [ "\n", "# Table of Contents\n", "\n", "- [Introduction](#intro)\n", "- [Setup](#setup)\n", "- [Part I. Process Data](#one)\n", "- [Part II. Spike-Triggered Voltage](#two)\n", "- [Part III. PSP amplitudes](#three)\n" ] }, { "cell_type": "markdown", "id": "38b445ff-7cf7-442e-bb25-83afe6d5a3fb", "metadata": {}, "source": [ "\n", "# Synaptic Connectivity\n", "\n", "\n" ] }, { "cell_type": "markdown", "id": "488d476b-9387-43d7-b4bf-0528d9f4950a", "metadata": {}, "source": [ "\n", "# Setup\n", "\n", "[toc](#toc)" ] }, { "cell_type": "markdown", "id": "b94f344a-8c2a-4080-b972-8e7a30c91e66", "metadata": {}, "source": [ "Import and define functions" ] }, { "cell_type": "code", "execution_count": null, "id": "563bc67c-8936-49fa-ac9c-5e51f3f6fc89", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Run this code cell to import packages and define functions \n", "import numpy as np\n", "import pandas as pd\n", "import plotly.express as px\n", "import plotly.graph_objects as go\n", "from plotly.subplots import make_subplots\n", "from scipy import ndimage\n", "from scipy.optimize import curve_fit\n", "from scipy.signal import hilbert,medfilt,resample, find_peaks, unit_impulse\n", "import seaborn as sns\n", "from datetime import datetime,timezone,timedelta\n", "pal = sns.color_palette(n_colors=15)\n", "pal = pal.as_hex()\n", "import matplotlib.pyplot as plt\n", "import random\n", "from sklearn.decomposition import PCA\n", "from sklearn.cluster import KMeans\n", "\n", "from pathlib import Path\n", "\n", "from matplotlib.ticker import (AutoMinorLocator, MultipleLocator)\n", "from ipywidgets import widgets, interact, interactive, interactive_output\n", "%config InlineBackend.figure_format = 'retina'\n", "plt.style.use(\"https://raw.githubusercontent.com/NeuromatchAcademy/course-content/master/nma.mplstyle\")\n", "\n", "print('Task completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))\n" ] }, { "cell_type": "markdown", "id": "d0ec2161-a892-4f83-904d-c69de977cff1", "metadata": {}, "source": [ "Mount Google Drive" ] }, { "cell_type": "code", "execution_count": null, "id": "a9e89f46-51f1-485c-a8d2-f36703e2da44", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Run this cell to mount your Google Drive.\n", "\n", "from google.colab import drive\n", "drive.mount('/content/drive')\n", "\n", "print('Task completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "markdown", "id": "b0520411-162e-4891-ac66-fb797be49a1f", "metadata": {}, "source": [ "## Import data \n", "\n", "Import data digitized with *Nidaq USB6211* and recorded using *Bonsai-rx* as a *.bin* file\n", "\n", "If you would like sample this Data Explorer, but do not have data, you can download the following examples (two channels digitized at 40000). Channel 0 is the signal measured from N3 and Channel 1 is the signal measured from the Superficial Flexor muscle. \n" ] }, { "cell_type": "code", "execution_count": null, "id": "cbb9bad0-84d7-49f1-8a61-5e61bdea8231", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\" }\n", "\n", "#@markdown Specify the file path \n", "#@markdown to your recorded data on Drive (find the filepath in the colab file manager:\n", "\n", "filepath = \"full filepath goes here\" #@param \n", "# filepath = '/Volumes/Untitled/BIOL247/data/crayfish-synaptic-connectivity/KP_20221113/spont-simult_A4_2_good-2input.bin' \n", "\n", "#@markdown Specify the sampling rate and number of channels recorded.\n", "\n", "sampling_rate = None #@param\n", "number_channels = None #@param\n", "\n", "# downsample = False #@param\n", "# newfs = 10000 #@param\n", "\n", "#@markdown After you have filled out all form fields, \n", "#@markdown run this code cell to load the data. \n", "\n", "filepath = Path(filepath)\n", "\n", "# No need to edit below this line\n", "#################################\n", "data = np.fromfile(Path(filepath), dtype = np.float64)\n", "data = data.reshape(-1,number_channels)\n", "data_dur = np.shape(data)[0]/sampling_rate\n", "print('duration of recording was %0.2f seconds' %data_dur)\n", "\n", "fs = sampling_rate\n", "# if downsample:\n", " ## newfs = 10000 #downsample emg data\n", " # chunksize = int(sampling_rate/newfs)\n", " # data = data[0::chunksize,:]\n", " # fs = int(np.shape(data)[0]/data_dur)\n", "\n", "time = np.linspace(0,data_dur,np.shape(data)[0])\n", "\n", "print('Data upload completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "markdown", "id": "32c9bf59-0b3a-461c-ad68-139ef6ecbe82", "metadata": {}, "source": [ "## Visualize Data\n", "\n", "Use this visualization tool if desired to get a sanity check that the data you thought you imported is actually the data that got imported." ] }, { "cell_type": "code", "execution_count": null, "id": "761d7c99-21f4-4ef0-821c-7b68f80da6b5", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this code cell to plot imported data.
\n", "#@markdown Use the range slider to scroll through the data in time.\n", "#@markdown Use the channel slider to choose which channel to plot\n", "#@markdown Be patient with the range refresh... the more data you are plotting the slower it will be. \n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,data_dur),\n", " step= 0.5,\n", " readout=True,\n", " continuous_update=False,\n", " description='Time Range (s)')\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_chan = widgets.IntSlider(\n", " min=0,\n", " max=number_channels-1,\n", " value=0,\n", " step= 1,\n", " continuous_update=False,\n", " description='channel')\n", "slider_chan.layout.width = '300px'\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(x,chan):\n", " fig, ax = plt.subplots(figsize=(10,5),num=1); #specify figure number so that it does not keep creating new ones\n", " starti = int(x[0]*fs)\n", " stopi = int(x[1]*fs)\n", " ax.plot(time[starti:stopi], data[starti:stopi,chan])\n", "\n", "w = interact(update_plot, x=slider_xrange, chan=slider_chan);" ] }, { "cell_type": "markdown", "id": "f264e6ef-f1f7-4711-b30c-c14376a99509", "metadata": {}, "source": [ "For a more extensive ***RAW*** Data Explorer than the one provided in the above figure, use the [DataExplorer.py](https://raw.githubusercontent.com/neurologic/Neurophysiology-Lab/main/howto/Data-Explorer.py) application found in the [howto section](https://neurologic.github.io/Neurophysiology-Lab/howto/Dash-Data-Explorer.html) of the course website." ] }, { "cell_type": "markdown", "id": "7c79dbf5-af08-401a-a78e-fd519d26e921", "metadata": {}, "source": [ "## Define Bouts\n", "\n", "To efficiently assess your data with this analysis, make sure to exclude any raw data that does not have a clean (low-noise) signal. For the simultaneously recorded pre- and post-synaptic signals, make sure to exclude raw data in which the post-synaptic electrode was not stably in the cell. The more data you are able to include, the better your spike sorting results will be." ] }, { "cell_type": "code", "execution_count": null, "id": "f8648f94-5d73-48ab-ae81-18882f8c31e4", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown For this experiment, the entire file should be one long bout, \n", "#@markdown but if there were regions that something got messed up or that you want to exclude, you can specify bouts with good data.\n", "#@markdown Specify the list of bout ranges as follows: [[start of bout 0, end of bout 0],[start 1, end 1],...]]
\n", "\n", "\n", "bouts_list = [[None,None]] #@param\n", "\n", "#@markdown Then run this code cell to programatically define the list of bouts as 'bouts_list'." ] }, { "cell_type": "markdown", "id": "b7b40cda-55dd-4604-afb7-89e5d0731983", "metadata": {}, "source": [ "\n", "# Part I. Spike Sorting\n", "\n", "Python has built-in algorithms for detecting \"peaks\" in a signal. However, it will detect *all* peaks. Therefore, the function takes in arguments that specify parameters for minimum height that can count as a peak and a minimum acceptible interval between independent peaks. \n", "\n", "First, we will detect all the peaks on the N3 channel. This will give the time of each peak whose amplitude falls between the two given thresholds (putative motor neuron spikes). \n", "\n", "***Use the Dash Data Explorer to visualize the spike waveform shapes. This will enable you to determine which \"polarity\" is optimal for peak detection in your recording.***" ] }, { "cell_type": "code", "execution_count": null, "id": "41801308-ab9e-4827-a57c-671780fd8313", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Indicate which channel has the N3 signal with motor neuron spikes.\n", "\n", "spike_channel = None #@param\n", "\n", "#@markdown Then, run the code cell to create an interactive plot with a slider to scroll \n", "#@markdown through the raw data and set an upper and lower peak detection threshold.\n", "#@markdown You can set the polarity of the peak detection: upward (1) or downward (-1) peaks. \n", "#@markdown Peak times (according to your threshold) will be plotted using red markers.
\n", "#@markdown Only the signal within the previously specified bout times will be processed (shaded gray regions in the plot).\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,data_dur),\n", " step=0.01,\n", " readout_format='.2f',\n", " continuous_update=False,\n", " readout=True,\n", " description='xrange (s)'\n", ")\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_yrange = widgets.FloatRangeSlider(\n", " min=np.min(data[:,spike_channel])-0.1,\n", " max=np.max(data[:,spike_channel])+0.1,\n", " value=[np.min(data[:,spike_channel])-0.1,np.max(data[:,spike_channel])+0.1],\n", " step=0.01,\n", " readout_format='.2f',\n", " continuous_update=False,\n", " readout=True,\n", " description='yrange'\n", ")\n", "slider_yrange.layout.width = '600px'\n", "\n", "\n", "\n", "slider_threshold_low = widgets.FloatSlider(\n", " min=0,\n", " max=np.max([np.max(data[:,spike_channel]),np.abs(np.min(data[:,spike_channel]))])+0.1,\n", " value=0,\n", " step=0.001,\n", " readout_format='.3f',\n", " continuous_update=False,\n", " readout=True,\n", " description='lower threshold')\n", "slider_threshold_low.layout.width = '600px'\n", "\n", "slider_threshold_high = widgets.FloatSlider(\n", " min=0,\n", " max=np.max([np.max(data[:,spike_channel]),np.abs(np.min(data[:,spike_channel]))])+0.1,\n", " value=np.max([np.max(data[:,spike_channel]),np.abs(np.min(data[:,spike_channel]))])+0.1,\n", " step=0.001,\n", " readout_format='.3f',\n", " continuous_update=False,\n", " readout=True,\n", " description='upper threshold')\n", "slider_threshold_high.layout.width = '600px'\n", "\n", "radio_polarity = widgets.RadioButtons(\n", " options=[1, -1],\n", " value=1,\n", " description='peaks polarity',\n", " disabled=False\n", ")\n", "\n", "iei_text = widgets.Text(\n", " value='0.005',\n", " placeholder='0.005',\n", " description='min IEI (seconds)',\n", " style = {'description_width': '200px'},\n", " disabled=False\n", ")\n", "\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(xrange,yrange,thresh_low_,thresh_high_,polarity,iei):\n", " fig, ax = plt.subplots(figsize=(10,6),num=1); #specify figure number so that it does not keep creating new ones\n", " fig.tight_layout() \n", " \n", " win_0 = int(xrange[0]*fs)\n", " win_1 = int(xrange[1]*fs)\n", "\n", " xtime = np.linspace(xrange[0],xrange[1],(win_1 - win_0))\n", "\n", " ax.plot(xtime,data[win_0:win_1,spike_channel],color='black',linewidth=1)\n", " ax.set_ylim(yrange[0],yrange[1]);\n", " \n", " ax.hlines(thresh_low_*polarity, xrange[0],xrange[1],linestyle='--',color='green',zorder=3)\n", " ax.hlines(thresh_high_*polarity, xrange[0],xrange[1],linestyle='--',color='orange',zorder=3)\n", " \n", " \n", " # calculate spike times based on threshold\n", " d = float(iei)*fs #minimum time allowed between distinct events\n", " r = find_peaks(data[:,spike_channel]*polarity,height=thresh_low_,distance=d)\n", "\n", " spike_times = r[0]/fs\n", " mask_spikes = r[1]['peak_heights']b_[0]) & (spike_times(xrange[0])) & (spike_times<(xrange[1]))]\n", " ax.scatter(inwin_spikes,[np.mean(data[:,spike_channel])] * len(inwin_spikes),\n", " zorder=3,color='red',s=20)\n", "\n", " for b_ in bouts_list:\n", " ax.axvspan(b_[0], b_[1], color = 'black', alpha=0.1) \n", "\n", " ax.set_xlim(xrange[0],xrange[1])\n", " return spike_times,df_props\n", " \n", "\n", "w_spikes_ = interactive(update_plot, xrange=slider_xrange, \n", " yrange=slider_yrange,\n", " thresh_low_=slider_threshold_low,thresh_high_=slider_threshold_high,polarity=radio_polarity,iei=iei_text);\n", "\n", "display(w_spikes_)" ] }, { "cell_type": "code", "execution_count": null, "id": "d97af8ff-6402-4fab-a33f-3c4e3bd30c43", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Run this cell to finalize the list of spike times after settling on a threshold in the interactive plot.
\n", "#@markdown This will also create a histogram plot of peak heights.\n", "spike_times,df_props = w_spikes_.result\n", "\n", "n,bins = np.histogram(df_props['height'],bins = 500) # calculate the histogram\n", "bins = bins[1:]\n", "hfig,ax = plt.subplots(figsize=(5,4))\n", "ax.step(bins,n,color='black')\n", "ax.set_ylabel('count',fontsize=14)\n", "ax.set_xlabel('amplitude',fontsize=14)\n", "plt.xticks(fontsize=14)\n", "plt.yticks(fontsize=14);" ] }, { "cell_type": "markdown", "id": "daa2ad87-c7a4-468f-980a-864c25eb3b58", "metadata": {}, "source": [ "\n", "\n", "The histogram plot can give you a sense for how many distinct motor neurons might be in your recording. \n", "\n", "We can cluster events based on peak height and waveform shape using [\"Kmeans\"](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html) clustering. \n", "This will provide us with \"putative single units\" for further analysis.\n", "> If you \"over cluster\" (try to assign more categories than exist), then you can potentially dissocate closely related event waveforms. You can re-combine clusters at a later stage as needed.\n", "\n", "You will be able to visualize the mean spike waveform (and std around the mean) of events with each cluster (putative motor neurons)." ] }, { "cell_type": "code", "execution_count": null, "id": "5c4de8ce-189c-4eb2-a340-05cb6d8cfd8b", "metadata": { "cellView": "form", "id": "iE2MylaxTGpL", "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title { display-mode: \"form\" }\n", "\n", "\n", "#@markdown Choose the number of clusters you want to split the event-based data into and type that number below.
\n", "#@markdown >Note: It can sometimes help to \"over-split\" the events into more clusters \n", "#@markdown than you think will be necessary. You can try both strategies and assess the results.\n", "number_of_clusters = 3 #@param {type: \"number\"}\n", "#@markdown Then run this cell to run the Kmeans algorithm. \n", "\n", "\n", "windur = 0.001\n", "winsamp = int(windur*fs)\n", "spkarray = []\n", "for i in df_props['spikeInd'].values:\n", " spkarray.append(data[i-winsamp : i+winsamp+1,spike_channel])\n", "\n", "df = pd.DataFrame(np.asarray(spkarray).T)\n", "df_norm =(df - df.mean()) / df.std() # normalize for pca\n", "\n", "n_components=5 #df.shape[0] \n", "pca = PCA(n_components=n_components)\n", "pca.fit(df_norm)\n", "df_pca = pd.DataFrame(pca.transform(df), columns=['PC%i' % i for i in range(n_components)], index=df.index)\n", "\n", "loadings = pd.DataFrame(pca.components_.T, columns=df_pca.columns, index=df.columns)\n", "df_data = loadings.join(df_props['height'])\n", "\n", "# hfig,ax = plt.subplots(1)\n", "# ax.set_xlabel('seconds')\n", "# ax.set_ylabel('amplitude (a.u.)')\n", "# ax.set_yticklabels([])\n", "# for c in df_pca.columns:\n", "# ax.plot(df_pca[c],label = c,alpha = 0.75)\n", "# plt.legend(bbox_to_anchor=(1, 1));\n", "\n", "\n", "kmeans = KMeans(n_clusters=number_of_clusters).fit(df_data)\n", "# df_props['peaks_t'] = peaks_t\n", "df_props['cluster'] = kmeans.labels_\n", "\n", "df_data['cluster_id'] = kmeans.labels_\n", "# sns.scatterplot(x='PC0',y='PC1',hue='cluster_id',data=df_data)\n", "\n", "#@title {display-mode:'form'}\n", "\n", "#@markdown Run this cell to display the mean (and std) waveform for each cluster.\n", "\n", "windur = 0.001\n", "winsamps = int(windur * fs)\n", "x = np.linspace(-windur,windur,winsamps*2)*1000\n", "hfig,ax = plt.subplots(1,figsize=(8,6))\n", "ax.set_ylabel('Volts recorded',fontsize=14)\n", "ax.set_xlabel('milliseconds',fontsize=14)\n", "plt.xticks(fontsize=14)\n", "plt.yticks(fontsize=14)\n", "\n", "for k in np.unique(df_props['cluster']):\n", " spkt = df_props.loc[df_props['cluster']==k]['spikeT'].values #['peaks_t'].values\n", " spkt = spkt[(spkt>windur) & (spkt<(data_dur)-windur)]\n", " print(str(len(spkt)) + \" spikes in cluster number \" + str(k))\n", " spkwav = np.asarray([data[(int(t*fs)-winsamps):(int(t*fs)+winsamps),spike_channel] for t in spkt])\n", " wav_u = np.mean(spkwav,0)\n", " wav_std = np.std(spkwav,0)\n", " ax.plot(x,wav_u,linewidth = 3,label='cluster '+ str(k),color=pal[k])\n", " ax.fill_between(x, wav_u-wav_std, wav_u+wav_std, alpha = 0.25,color=pal[k])\n", "plt.legend(bbox_to_anchor=[1.25,1],fontsize=14);\n", "\n", "print('Tasks completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))" ] }, { "cell_type": "markdown", "id": "0a37aa64-ee1b-4248-8901-982f326e0cb0", "metadata": { "id": "9iyWsThmXdI_" }, "source": [ "\n", "If there are multiple spike clusters you want to merge into a single cell class, *edit and run* the cell below.\n", "\n", "> **merge_cluster_list** = a list of the clusters (identified by numbers associated with the colors specified in the legend above).\n", " - **For example**, the folowing list would merge clusters 0 and 2 together and 1, 4, and 3 together:
\n", " **merge_cluster_list = [[0,2],[1,4,3]]**\n", " - For each merge group, the first cluster number listed will be the re-asigned cluster number for that group (for example, in this case you would end up with a cluster number 0 and a cluster number 1). \n", " - Leave any clusters that don't need merging out of the list.\n", " " ] }, { "cell_type": "code", "execution_count": null, "id": "e7ae4811-90b0-4c5d-8f7b-39d48fb2590b", "metadata": { "cellView": "form", "id": "EDJgd8DAXRba", "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title { display-mode: \"form\" }\n", "\n", "#@markdown ONLY USE THIS CODE CELL IF YOU WANT TO MERGE CLUSTERS. \n", "#@markdown OTHERWISE, MOVE ON. \n", "#@markdown
Below, create your list (of sublists) of clusters to merge.\n", "#@markdown >Just leave out from the list any clusters that you want unmerged.\n", "merge_cluster_list = [[1,4,3]] #@param\n", "#@markdown Then, run this cell to merge clusters as specified.
\n", "#@markdown A new figure of waveform shapes will be generated for the new categorization.\n", "\n", "for k_group in merge_cluster_list:\n", " for k in k_group:\n", " df_props.loc[df_props['cluster']==k,'cluster'] = k_group[0]\n", "print('you now have the following clusters: ' + str(np.unique(df_props['cluster'])))\n", "\n", "print('Tasks completed at ' + str(datetime.now(timezone(-timedelta(hours=5)))))\n", "\n", "windur = 0.001\n", "winsamps = int(windur * fs)\n", "x = np.linspace(-windur,windur,winsamps*2)*1000\n", "hfig,ax = plt.subplots(1,figsize=(8,6))\n", "ax.set_ylabel('Volts recorded',fontsize=14)\n", "ax.set_xlabel('milliseconds',fontsize=14)\n", "plt.xticks(fontsize=14)\n", "plt.yticks(fontsize=14)\n", "\n", "for k in np.unique(df_props['cluster']):\n", " spkt = df_props.loc[df_props['cluster']==k]['spikeT'].values #['peaks_t'].values\n", " spkt = spkt[(spkt>windur) & (spkt<(data_dur)-windur)]\n", " print(str(len(spkt)) + \" spikes in cluster number \" + str(k))\n", " spkwav = np.asarray([data[(int(t*fs)-winsamps):(int(t*fs)+winsamps),spike_channel] for t in spkt])\n", " wav_u = np.mean(spkwav,0)\n", " wav_std = np.std(spkwav,0)\n", " ax.plot(x,wav_u,linewidth = 3,label='cluster '+ str(k),color=pal[k])\n", " ax.fill_between(x, wav_u-wav_std, wav_u+wav_std, alpha = 0.25,color=pal[k])\n", "plt.legend(bbox_to_anchor=[1.25,1],fontsize=14);" ] }, { "cell_type": "markdown", "id": "bc5a51f2-a02c-4e03-a227-6079e348bce6", "metadata": {}, "source": [ "\n", "\n", "Once you are happy with the clustering results based on the waveform shapes, check back with the raw data to make sure the spike assignments are at least close. " ] }, { "cell_type": "code", "execution_count": null, "id": "bf3a5154-edd8-49bd-8395-94ba58eabd89", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode: \"form\"}\n", "\n", "#@markdown Then, run the code cell to create an interactive plot with a slider to scroll \n", "#@markdown through the raw data and overlaid \"spike-sorted\" event time data.
\n", "#@markdown Only the signal within the previously specified bout times will be processed (shaded gray regions in the plot).\n", "\n", "\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=0,\n", " max=data_dur,\n", " value=(0,data_dur),\n", " step=0.01,\n", " readout_format='.2f',\n", " continuous_update=False,\n", " readout=True,\n", " description='xrange (s)'\n", ")\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_yrange = widgets.FloatRangeSlider(\n", " min=np.min(data[:,spike_channel])-0.1,\n", " max=np.max(data[:,spike_channel])+0.1,\n", " value=[np.min(data[:,spike_channel])-0.1,np.max(data[:,spike_channel])+0.1],\n", " step=0.01,\n", " readout_format='.2f',\n", " continuous_update=False,\n", " readout=True,\n", " description='yrange'\n", ")\n", "slider_yrange.layout.width = '600px'\n", "\n", "\n", "# a function that will modify the xaxis range\n", "def update_plot(xrange,yrange):\n", " fig, ax = plt.subplots(figsize=(10,6),num=1); #specify figure number so that it does not keep creating new ones\n", " fig.tight_layout() \n", " \n", " win_0 = int(xrange[0]*fs)\n", " win_1 = int(xrange[1]*fs)\n", "\n", " xtime = np.linspace(xrange[0],xrange[1],(win_1 - win_0))\n", "\n", " ax.plot(xtime,data[win_0:win_1,spike_channel],color='black',linewidth=1)\n", " ax.set_ylim(yrange[0],yrange[1]);\n", " ax.set_xlim(xrange[0],xrange[1]);\n", " \n", " \n", " for k in np.unique(df_props['cluster']):\n", " inwin_inds = np.asarray([(df_props['spikeT'].values>xrange[0]) & (df_props['spikeT'].values\n", "# Part II. Spike-triggered voltage\n", "\n", "[toc](#toc)\n", "\n", "You can use the event times (spike times) to extract the pre-synaptic and/or post-synaptic voltage signal following each event. This is a helpful way to determine if you have recorded any synaptic pairs (a connected pair of pre and post-synaptic cells). " ] }, { "cell_type": "markdown", "id": "b000e64f-0309-4345-80f5-463340c9bf2e", "metadata": {}, "source": [ "## Visualize the average raw signal time-locked to putative pre-synaptic neurons.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "e2fb9c5b-5f47-4bfc-b088-7d1af2eddb22", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode:\"form\"}\n", "\n", "#@markdown Specify which channel has the intracellular signal. \n", "#@markdown Then run this cell to plot the average spike-triggered \n", "#@markdown post-synaptic potential for each spike cluster you defined in Part I\n", "\n", "intracellular_channel = 1 #@param\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=-0.1,\n", " max=0.500,\n", " value=(0,0.200),\n", " step=0.01,\n", " readout_format='.2f',\n", " continuous_update=False,\n", " readout=True,\n", " description='xrange (s)'\n", ")\n", "slider_xrange.layout.width = '600px'\n", "\n", "def update_plot(xrange):\n", " # No need to edit below this line\n", " #################################\n", " windur = xrange[1]-xrange[0]\n", " winsamps = int(windur * fs)\n", "\n", " onset = int(xrange[0]*fs)\n", " offset = int(xrange[1]*fs)\n", "\n", " x = np.linspace(xrange[0],xrange[1],offset-onset)\n", " \n", " hfig,ax = plt.subplots(figsize=(10,4))\n", " ax.set_ylabel('volts recorded',fontsize=14)\n", " ax.set_xlabel('milliseconds',fontsize=14)\n", " # plt.xticks(fontsize=14)\n", " # plt.yticks(fontsize=14)\n", " for k in np.unique(df_props['cluster']):\n", " spkt = df_props.loc[df_props['cluster']==k]['spikeT'].values\n", " spkt = spkt[(spkt<((data_dur)-windur))]\n", " synwav = np.asarray([data[int(t*fs)+onset:int(t*fs)+offset,intracellular_channel] for t in spkt])\n", " wav_u = np.mean(synwav,0)\n", " wav_std = np.std(synwav,0)\n", " ax.plot(x,wav_u,linewidth = 3,color = pal[k],label='cluster '+str(k));\n", " # ax.fill_between(x, wav_u-wav_std, wav_u+wav_std, alpha = 0.25, color = pal[k])\n", " plt.legend(bbox_to_anchor=[1.5,1], fontsize=14);\n", " ax.set_xlim(xrange[0],xrange[1])\n", " \n", "w_psps_sorted_ = interactive(update_plot, xrange=slider_xrange);\n", "\n", "display(w_psps_sorted_)" ] }, { "cell_type": "markdown", "id": "449772b6-6284-4276-b1b7-37aeb1536b8c", "metadata": { "tags": [] }, "source": [ "## Visualize the raw signal time-locked to individual spike times\n", "\n", "Finally, you can plot the pre and post synaptic signal associated with each individual event time in each cluster. \n", "\n", "This visualization enables you to extract more exact quantitative measurements from the signals associated with each event. It is also helpful to look at individual events to get a sense of the variance/reliability in the pre and post-synaptic signal associated with each spike time." ] }, { "cell_type": "code", "execution_count": null, "id": "764fc80e-cfa1-497e-95ff-42b4a1939a51", "metadata": { "tags": [ "hide-input" ] }, "outputs": [], "source": [ "#@title {display-mode:\"form\"}\n", "\n", "#@markdown Run this code cell to create an interactive plot to \n", "#@markdown examine the raw signal time-locked to individual stimuli (event_times).\n", "#@markdown You can overlay multple channels by selecting more than one.\n", "#@markdown You can overlay multiple stimulus times by selecting more than one. \n", "#@markdown (To select more than one item from an option menu, press the control/command key \n", "#@markdown while mouse clicking or shift while using up/down arrows)\n", "\n", "slider_xrange = widgets.FloatRangeSlider(\n", " min=-0.01,\n", " max=0.3,\n", " value=(-0.05,0.1),\n", " step=0.001,\n", " continuous_update=False,\n", " readout=True,\n", " readout_format='.4f',\n", " description='xrange (s)'\n", ")\n", "slider_xrange.layout.width = '600px'\n", "\n", "slider_yrange_0 = widgets.FloatRangeSlider(\n", " min=-3,\n", " max=3, # normal range for crayfish superficial flexor\n", " value=(-2,2),\n", " step=0.01,\n", " continuous_update=False,\n", " readout=True,\n", " description='yrange ch 0'\n", ")\n", "slider_yrange_0.layout.width = '600px'\n", "\n", "slider_yrange_1 = widgets.FloatRangeSlider(\n", " min=-1,\n", " max=-0.2, # normal range for crayfish superficial flexor\n", " value=(-0.7,-0.4),\n", " step=0.01,\n", " continuous_update=False,\n", " readout=True,\n", " description='yrange ch 1'\n", ")\n", "slider_yrange_1.layout.width = '600px'\n", "\n", "ui_range = widgets.VBox([slider_xrange, slider_yrange_0, slider_yrange_1])\n", "\n", "select_channels = widgets.SelectMultiple(\n", " options=np.arange(np.shape(data)[1]), # start with a single trial on a single bout... it will update when runs ,\n", " value=[0],\n", " #rows=10,\n", " description='Channels',\n", " disabled=False\n", ")\n", "\n", "select_clusters = widgets.Select(\n", " options=np.unique(df_props['cluster']), # start with a single trial on a single bout... it will update when runs ; old: np.arange(len(trial_times)),\n", " value=[np.unique(df_props['cluster'])[0]],\n", " #rows=10,\n", " description='Clusters',\n", " disabled=False\n", ")\n", "\n", "select_trials = widgets.SelectMultiple(\n", " options=df_props[df_props['cluster']==np.unique(df_props['cluster'])[0]]['spikeInd'], # start with a single trial on a single bout... it will update when runs ,\n", " value=[df_props[df_props['cluster']==np.unique(df_props['cluster'])[0]]['spikeInd'].values[0]],\n", " #rows=10,\n", " description='Spikes',\n", " disabled=False\n", ")\n", "\n", "ui_trials = widgets.HBox([select_channels, select_trials, select_clusters])\n", "\n", "\n", "def update_plot(chan_list,trial_list,cluster_,xrange,yrange0,yrange1):\n", " fig, ax0 = plt.subplots(figsize=(8,4),num=1); #specify figure number so that it does not keep creating new ones\n", " \n", " ax1 = ax0.twinx()\n", " \n", " win_0 = int(xrange[0]*fs)\n", " win_1 = int(xrange[1]*fs)\n", " xtime = np.linspace(xrange[0],xrange[1],(win_1 - win_0))\n", " \n", " trial_times = df_props[df_props['cluster']==cluster_]['spikeT']\n", " trial_inds = df_props[df_props['cluster']==cluster_]['spikeInd'].values \n", " \n", " trials_init_ = np.arange(len(trial_times))\n", " select_trials.options = trials_init_\n", "\n", " trial_list = [t_try for t_try in trial_list if t_try in trials_init_]\n", " select_trials.value = trial_list\n", " \n", " channel_colors = ['purple','green','blue','orange']\n", " for chan_ in chan_list:\n", " \n", " this_chan = data[:,chan_]\n", " for trial_ in trial_list:\n", "\n", " if trial_ in trials_init_:\n", " t_ = trial_inds[trial_]\n", "\n", " if ((t_+win_0)>0) & ((t_+win_1)) \n", "Written by Dr. Krista Perks for courses taught at Wesleyan University." ] }, { "cell_type": "code", "execution_count": null, "id": "5c707588-d5f6-4142-8289-da7ba6a97dbc", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "id": "57a8a68c-055e-4af0-ba4d-67d0d67148f1", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "a350712e-e145-4475-9588-e3f19469b5ce", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "86fd2b4a-b890-465f-bf33-c2057738789e", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "b05de192-2baf-4d46-8848-6db1e3b50bff", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "id": "e6519277-fc86-44fd-a75b-f74cad0efcf1", "metadata": {}, "source": [ "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.13" }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 5 }