From 75679887bea7fa21c092aa953c6a8d9146e8b2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikkel=20Elle=20Lepper=C3=B8d?= Date: Tue, 8 Oct 2019 16:00:25 +0200 Subject: [PATCH] auto commit actions/stimulus-response --- actions/stimulus-response/attributes.yaml | 7 + .../data/10-calculate-stimulus-response.html | 13456 ++++++++++++++++ .../data/10-calculate-stimulus-response.ipynb | 280 + .../data/figures/summary_histogram.png | Bin 0 -> 10579 bytes actions/stimulus-response/data/results.csv | 1282 ++ .../stimulus-response/modules/parameters.yaml | 2 + 6 files changed, 15027 insertions(+) create mode 100644 actions/stimulus-response/attributes.yaml create mode 100644 actions/stimulus-response/data/10-calculate-stimulus-response.html create mode 100644 actions/stimulus-response/data/10-calculate-stimulus-response.ipynb create mode 100644 actions/stimulus-response/data/figures/summary_histogram.png create mode 100644 actions/stimulus-response/data/results.csv create mode 100644 actions/stimulus-response/modules/parameters.yaml diff --git a/actions/stimulus-response/attributes.yaml b/actions/stimulus-response/attributes.yaml new file mode 100644 index 000000000..331d5436c --- /dev/null +++ b/actions/stimulus-response/attributes.yaml @@ -0,0 +1,7 @@ +registered: '2019-10-03T08:37:34' +data: + figures: figures + statistics: statistics + notebook: 10-calculate-stimulus-response.ipynb + html: 10-calculate-stimulus-response.html + results: results.csv diff --git a/actions/stimulus-response/data/10-calculate-stimulus-response.html b/actions/stimulus-response/data/10-calculate-stimulus-response.html new file mode 100644 index 000000000..c49ba8843 --- /dev/null +++ b/actions/stimulus-response/data/10-calculate-stimulus-response.html @@ -0,0 +1,13456 @@ + + + + +10-calculate-stimulus-response + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
In [1]:
+
+
+
%load_ext autoreload
+%autoreload 2
+
+ +
+
+
+ +
+
+
+
In [2]:
+
+
+
import matplotlib.pyplot as plt
+%matplotlib inline
+import spatial_maps as sp
+import septum_mec.analysis.data_processing as dp
+import septum_mec.analysis.registration
+import expipe
+import os
+import pathlib
+import numpy as np
+import exdir
+import pandas as pd
+import optogenetics as og
+import quantities as pq
+import shutil
+from distutils.dir_util import copy_tree
+
+from septum_mec.analysis.stimulus_response import stimulus_response_latency, compute_response
+
+from tqdm import tqdm_notebook as tqdm
+from tqdm._tqdm_notebook import tqdm_notebook
+tqdm_notebook.pandas()
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
08:31:41 [I] klustakwik KlustaKwik2 version 0.2.6
+
+
+
+ +
+
+ +
+
+
+
In [3]:
+
+
+
std_gaussian_kde = 0.04
+window_size = 0.03
+
+ +
+
+
+ +
+
+
+
In [4]:
+
+
+
data_loader = dp.Data()
+actions = data_loader.actions
+project = data_loader.project
+
+ +
+
+
+ +
+
+
+
In [5]:
+
+
+
output = pathlib.Path('output/stimulus-response')
+(output / 'figures').mkdir(parents=True, exist_ok=True)
+
+ +
+
+
+ +
+
+
+
In [6]:
+
+
+
identify_neurons = actions['identify-neurons']
+units = pd.read_csv(identify_neurons.data_path('units'))
+
+ +
+
+
+ +
+
+
+
In [7]:
+
+
+
def process(row):
+    
+    action_id = row['action']
+    channel_id = int(row['channel_group'])
+    unit_id = int(row['unit_name'])    
+    
+    spike_times = data_loader.spike_train(action_id, channel_id, unit_id)
+    
+    spike_times = np.array(spike_times)
+    
+    stim_times = data_loader.stim_times(action_id)
+    
+    nan_series = pd.Series({
+            't_e_peak': np.nan,
+            'p_e_peak': np.nan,
+            't_i_peak': np.nan,
+            'p_i_peak': np.nan
+        })
+    
+    if stim_times is None:
+        return nan_series
+    
+    stim_times = np.array(stim_times)
+    
+    times, spikes, kernel, p_e, p_i = stimulus_response_latency(
+        spike_times, stim_times, window_size, std_gaussian_kde)
+    
+    # if no spikes detected after stimulus nan is returned
+    if all(np.isnan([p_e, p_i])):
+        return nan_series
+        
+    t_e_peak, p_e_peak, t_i_peak, p_i_peak = compute_response(
+        spike_times, stim_times, times, kernel, p_e, p_i)
+
+    return pd.Series({
+        't_e_peak': t_e_peak,
+        'p_e_peak': p_e_peak,
+        't_i_peak': t_i_peak,
+        'p_i_peak': p_i_peak
+    })
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
results = units.merge(
+    units.progress_apply(process, axis=1), 
+    left_index=True, right_index=True)
+
+ +
+
+
+ +
+
+ + +
+ +
+ + + + + + + +
+
+ + +
+ +
+ +
+ +
+ + +
+
/home/mikkel/apps/expipe-project/septum-mec/septum_mec/analysis/stimulus_response.py:33: RuntimeWarning: invalid value encountered in less
+  if any(times[idxs_i] < te_peak):
+
+
+
+ +
+
+ +
+
+
+
In [ ]:
+
+
+
results.loc[:, ['t_e_peak', 't_i_peak', 'p_e_peak', 'p_i_peak']].hist()
+plt.gcf().savefig(output / 'figures' / 'summary_histogram.png')
+
+ +
+
+
+ +
+
+
+
+

Save to expipe

+
+
+
+
+
+
In [ ]:
+
+
+
action = project.require_action("stimulus-response")
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
action.modules['parameters'] = {
+    'window_size': window_size,
+    'std_gaussian_kde': std_gaussian_kde
+}
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
action.data['results'] = 'results.csv'
+results.to_csv(action.data_path('results'), index=False)
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
stuff = {
+    "figures": "figures",
+#     "statistics": "statistics"
+}
+
+for key, value in stuff.items():
+    action.data[key] = value
+    data_path = action.data_path(key)
+    data_path.parent.mkdir(exist_ok=True, parents=True)
+    source = output / value
+    if source.is_file():
+        shutil.copy(source, data_path)
+    else:
+        copy_tree(str(source), str(data_path))
+
+ +
+
+
+ +
+
+
+
In [ ]:
+
+
+
septum_mec.analysis.registration.store_notebook(action, "10-calculate-stimulus-response.ipynb")
+
+ +
+
+
+ +
+
+
+ + + + + + diff --git a/actions/stimulus-response/data/10-calculate-stimulus-response.ipynb b/actions/stimulus-response/data/10-calculate-stimulus-response.ipynb new file mode 100644 index 000000000..db9fae70a --- /dev/null +++ b/actions/stimulus-response/data/10-calculate-stimulus-response.ipynb @@ -0,0 +1,280 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "08:31:41 [I] klustakwik KlustaKwik2 version 0.2.6\n" + ] + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "import spatial_maps as sp\n", + "import septum_mec.analysis.data_processing as dp\n", + "import septum_mec.analysis.registration\n", + "import expipe\n", + "import os\n", + "import pathlib\n", + "import numpy as np\n", + "import exdir\n", + "import pandas as pd\n", + "import optogenetics as og\n", + "import quantities as pq\n", + "import shutil\n", + "from distutils.dir_util import copy_tree\n", + "\n", + "from septum_mec.analysis.stimulus_response import stimulus_response_latency, compute_response\n", + "\n", + "from tqdm import tqdm_notebook as tqdm\n", + "from tqdm._tqdm_notebook import tqdm_notebook\n", + "tqdm_notebook.pandas()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "std_gaussian_kde = 0.04\n", + "window_size = 0.03" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "data_loader = dp.Data()\n", + "actions = data_loader.actions\n", + "project = data_loader.project" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "output = pathlib.Path('output/stimulus-response')\n", + "(output / 'figures').mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "identify_neurons = actions['identify-neurons']\n", + "units = pd.read_csv(identify_neurons.data_path('units'))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "def process(row):\n", + " \n", + " action_id = row['action']\n", + " channel_id = int(row['channel_group'])\n", + " unit_id = int(row['unit_name']) \n", + " \n", + " spike_times = data_loader.spike_train(action_id, channel_id, unit_id)\n", + " \n", + " spike_times = np.array(spike_times)\n", + " \n", + " stim_times = data_loader.stim_times(action_id)\n", + " \n", + " nan_series = pd.Series({\n", + " 't_e_peak': np.nan,\n", + " 'p_e_peak': np.nan,\n", + " 't_i_peak': np.nan,\n", + " 'p_i_peak': np.nan\n", + " })\n", + " \n", + " if stim_times is None:\n", + " return nan_series\n", + " \n", + " stim_times = np.array(stim_times)\n", + " \n", + " times, spikes, kernel, p_e, p_i = stimulus_response_latency(\n", + " spike_times, stim_times, window_size, std_gaussian_kde)\n", + " \n", + " # if no spikes detected after stimulus nan is returned\n", + " if all(np.isnan([p_e, p_i])):\n", + " return nan_series\n", + " \n", + " t_e_peak, p_e_peak, t_i_peak, p_i_peak = compute_response(\n", + " spike_times, stim_times, times, kernel, p_e, p_i)\n", + "\n", + " return pd.Series({\n", + " 't_e_peak': t_e_peak,\n", + " 'p_e_peak': p_e_peak,\n", + " 't_i_peak': t_i_peak,\n", + " 'p_i_peak': p_i_peak\n", + " })\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d3f0400c75c745c789907bd763bf2833", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(IntProgress(value=0, max=1281), HTML(value='')))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mikkel/apps/expipe-project/septum-mec/septum_mec/analysis/stimulus_response.py:33: RuntimeWarning: invalid value encountered in less\n", + " if any(times[idxs_i] < te_peak):\n" + ] + } + ], + "source": [ + "results = units.merge(\n", + " units.progress_apply(process, axis=1), \n", + " left_index=True, right_index=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "results.loc[:, ['t_e_peak', 't_i_peak', 'p_e_peak', 'p_i_peak']].hist()\n", + "plt.gcf().savefig(output / 'figures' / 'summary_histogram.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Save to expipe" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "action = project.require_action(\"stimulus-response\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "action.modules['parameters'] = {\n", + " 'window_size': window_size,\n", + " 'std_gaussian_kde': std_gaussian_kde\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "action.data['results'] = 'results.csv'\n", + "results.to_csv(action.data_path('results'), index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "stuff = {\n", + " \"figures\": \"figures\",\n", + "# \"statistics\": \"statistics\"\n", + "}\n", + "\n", + "for key, value in stuff.items():\n", + " action.data[key] = value\n", + " data_path = action.data_path(key)\n", + " data_path.parent.mkdir(exist_ok=True, parents=True)\n", + " source = output / value\n", + " if source.is_file():\n", + " shutil.copy(source, data_path)\n", + " else:\n", + " copy_tree(str(source), str(data_path))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "septum_mec.analysis.registration.store_notebook(action, \"10-calculate-stimulus-response.ipynb\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.6.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/actions/stimulus-response/data/figures/summary_histogram.png b/actions/stimulus-response/data/figures/summary_histogram.png new file mode 100644 index 0000000000000000000000000000000000000000..9c15bbf8be04c1b76de9f44ae1ece46d8faf3812 GIT binary patch literal 10579 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