Browse Source

automatic generation of a report

Paul Pfeiffer 7 months ago
parent
commit
4197d0d449
5 changed files with 320 additions and 2 deletions
  1. 6 2
      makefile
  2. 85 0
      scripts/generate_report.py
  3. 53 0
      scripts/plot_firing_rates.py
  4. 39 0
      scripts/tools/report.html
  5. 137 0
      scripts/tools/style.css

+ 6 - 2
makefile

@@ -1,4 +1,4 @@
-all: prepare_folder_structure extract_stimulations assign_protocols filter analyse_firing_rates analyse_persistent_activity plot_traces
+all: prepare_folder_structure extract_stimulations assign_protocols filter analyse_firing_rates analyse_persistent_activity plot_traces report
 
 prepare_folder_structure:
 	python scripts/check_folder_structure.py
@@ -21,8 +21,12 @@ analyse_persistent_activity:
 plot_traces:
 	python scripts/plot_traces.py
 
+plot_firing_rates:
+	python scripts/plot_firing_rates.py
+
+
 report:
-	python generate_report.py
+	python scripts/generate_report.py
 
 clean:
 	rm -r out/

+ 85 - 0
scripts/generate_report.py

@@ -0,0 +1,85 @@
+import numpy
+import pandas
+from jinja2 import Environment, FileSystemLoader
+from weasyprint import HTML
+
+from tools.helper import get_filters
+from tools.definitions import OUTPUT_FOLDER, HELPER_TABLE_FOLDER, STIMULATION_METADATA_FILTERED
+
+
+def yes(bool_array):
+    if numpy.isnan(bool_array.values[0]):
+        return numpy.NaN
+    else:
+        return numpy.sum(bool_array.values.astype(numpy.int))
+
+
+def no(bool_array):
+    no_yes = yes(bool_array)
+    if numpy.isnan(no_yes):
+        return numpy.NaN
+    else:
+        return len(bool_array) - no_yes
+
+
+# Prepare template
+env = Environment(loader=FileSystemLoader('.'))
+REPORT_HTML_TEMPLATE = "scripts/tools/report.html"
+template = env.get_template(REPORT_HTML_TEMPLATE)
+
+path_to_filtered_stimulus_file = OUTPUT_FOLDER+HELPER_TABLE_FOLDER+STIMULATION_METADATA_FILTERED
+all_protocols = pandas.read_csv(path_to_filtered_stimulus_file, index_col="stimulation_id")
+
+protocols_for_persistent_activity = all_protocols[get_filters(all_protocols) & (all_protocols["is_control"] == False)]
+protocols_for_control = all_protocols[get_filters(all_protocols) & (all_protocols["is_control"] == True)]
+protocols_filtered = all_protocols[get_filters(all_protocols)==False]
+
+overview_protocols = pandas.DataFrame(protocols_for_persistent_activity.groupby(["protocol_type", "pulse_number"]).agg({
+    "was_successful": [yes, no]}))
+
+protocols = []
+overview_plots=[]
+for name, df in protocols_for_persistent_activity.groupby(["protocol_type", "pulse_number"]):
+    protocol_dict = {
+        "type": "{}-{}-success".format(name[0], name[1]),
+        "stim_ids": df[df["was_successful"] == True].index.values,
+    }
+    protocols.append(protocol_dict)
+    protocol_dict = {
+        "type": "{}-{}-fail".format(name[0], name[1]),
+        "stim_ids": df[df["was_successful"] == False].index.values,
+    }
+    protocols.append(protocol_dict)
+    overview_plots.append("{}-{}".format(name[0], name[1]))
+
+
+for name, df in protocols_for_control.groupby(["protocol_type", "pulse_number"]):
+    protocol_dict = {
+        "type": "{}-{}-control".format(name[0], name[1]),
+        "stim_ids": df.index.values,
+    }
+    protocols.append(protocol_dict)
+
+for name, df in protocols_filtered.groupby(["protocol_type", "pulse_number"]):
+    protocol_dict = {
+        "type": "{}-{}-reject".format(name[0], name[1]),
+        "stim_ids": df.index.values,
+    }
+    protocols.append(protocol_dict)
+
+template_vars = {
+    "total_number_of_cells": len(all_protocols["cell_id"].unique()),
+    "total_number_of_protocols": len(all_protocols),
+    "number_of_analysed_protocols": len(protocols_for_persistent_activity)+len(protocols_for_control),
+    "number_cooperative": len(protocols_for_persistent_activity),
+    "number_independent": len(protocols_for_control),
+    "number_of_filtered_protocols": len(protocols_filtered),
+
+    "overview_table": overview_protocols.to_html(),
+    "protocols": protocols,
+    "overview_plots": overview_plots
+}
+
+html_out = template.render(template_vars)
+path_to_report = OUTPUT_FOLDER+"report.pdf"
+HTML(string=html_out, base_url='.').write_pdf(path_to_report, stylesheets=["scripts/tools/style.css"])

+ 53 - 0
scripts/plot_firing_rates.py

@@ -0,0 +1,53 @@
+import matplotlib.pyplot as plt
+import numpy
+import pandas as pd
+from matplotlib.lines import Line2D
+
+from tools.helper import after_pulse_mean_string, pulse_mean_string
+from tools.definitions import OUTPUT_FOLDER, HELPER_TABLE_FOLDER, PLOTS_FOLDER, STIMULATION_METADATA_FILTERED
+
+
+path_to_plot_folder = OUTPUT_FOLDER+PLOTS_FOLDER
+path_to_filtered_stimulus_file = OUTPUT_FOLDER+HELPER_TABLE_FOLDER+STIMULATION_METADATA_FILTERED
+
+stimulations = pd.read_csv(path_to_filtered_stimulus_file, index_col="stimulation_id")
+
+type_number_combinations = stimulations[["protocol_type", "pulse_number"]].drop_duplicates().values
+
+print "# Plotting firing rates"
+
+for type_number in type_number_combinations:
+    protocol_type = type_number[0]
+    number = type_number[1]
+
+    # Determine the protocol
+    protocol_id = "{}-{:d}".format(protocol_type, number)
+    path_to_firing_rates_file = OUTPUT_FOLDER + HELPER_TABLE_FOLDER + "{}.csv".format(protocol_id)
+    isis = pd.read_csv(path_to_firing_rates_file, index_col="stimulation_id")
+
+    fig = plt.figure(figsize=(9, 9))
+    ax = fig.add_subplot(111)
+    markers = iter(Line2D.filled_markers)
+
+    if protocol_type == "UP":
+        firing_info_columns = ["isi_before"] + \
+                              [after_pulse_mean_string.format(pulse_idx) for pulse_idx in range(1, number + 1)]
+    elif protocol_type == "DOWN":
+        firing_info_columns = [pulse_mean_string.format(pulse_idx) for pulse_idx in range(1, number + 1)] + [
+            "isi_after"]
+
+    else:
+        RuntimeWarning("Unknown protocol {}".format(protocol_type))
+
+    for stim_id, isi in isis.iterrows():
+        legend = stim_id
+        # noinspection PyTypeChecker
+        firing_rates = numpy.nan_to_num(1.0 / isi[firing_info_columns].values.astype(numpy.float))
+        color = 'k' if stimulations.loc[stim_id]["was_successful"] else 'r'
+        ax.plot(firing_rates, ls='-', marker=markers.next(), markersize=10, color=color, label=stim_id)
+    ax.set_xticks(ticks=numpy.arange(0, number + 1))
+    ax.legend()
+
+    ax.set_xlabel("Pulse")
+    ax.set_ylabel("Firing [Hz]")
+    fig.savefig("{}/{}.png".format(path_to_plot_folder, protocol_id))

+ 39 - 0
scripts/tools/report.html

@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title> Recordings of graded persístent activity mediated by ion channel cooperativity</title>
+</head>
+<body>
+<h2>Overview</h2>
+<div>
+    <p>In total, {{total_number_of_cells}} cells were recorded with {{total_number_of_protocols}} runs of protocols
+        to test for persistent activity. From all recordings, we analysed {{number_of_analysed_protocols}} protocols
+        ({{number_cooperative}} with cooperative channels, {{ number_independent}} as controls). The other
+        {{number_of_filtered_protocols}} were not suitable for analysis for different reasons (run <code> make filter</code> to get a detailed overview).
+    </p>
+</div>
+
+<h3>Table of analysed protocols</h3>
+{{ overview_table }}
+
+<h3>Firing rates in analysed protocols</h3>
+{% for img in overview_plots %}
+<div>
+    <h5>{{ img }}</h5>
+    <img src="out/plots/{{img}}.png" alt="Overview">
+</div>
+{% endfor %}
+
+<h2>Traces</h2>
+{% for protocol in protocols %}
+<h3>{{ protocol.type }}</h3>
+{% for img in protocol.stim_ids %}
+<div>
+    <h5>{{ protocol.type }} - {{img}}</h5>
+    <img src="out/plots/{{img}}.png" alt="Trace">
+</div>
+{% endfor %}
+{% endfor %}
+</body>
+</html>

+ 137 - 0
scripts/tools/style.css

@@ -0,0 +1,137 @@
+/* --------------------------------------------------------------
+
+   typography.css
+   * Sets up some sensible default typography.
+   http://www.blueprintcss.org/blueprint/src/typography.css
+-------------------------------------------------------------- */
+
+/* Default font settings.
+   The font-size percentage is of 16px. (0.75 * 16px = 12px) */
+html { font-size:100.01%; }
+body {
+  font-size: 75%;
+  color: #222;
+  background: #fff;
+  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+}
+
+
+/* Headings
+-------------------------------------------------------------- */
+
+h1,h2,h3,h4,h5,h6 { font-weight: normal; color: #111; }
+
+h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
+h2 { font-size: 2em; margin-bottom: 0.75em; }
+h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
+h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
+h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
+h6 { font-size: 1em; font-weight: bold; }
+
+h1 img, h2 img, h3 img,
+h4 img, h5 img, h6 img {
+  margin: 0;
+}
+
+
+/* Text elements
+-------------------------------------------------------------- */
+
+p           { margin: 0 0 1.5em; }
+/*
+	These can be used to pull an image at the start of a paragraph, so
+	that the text flows around it (usage: <p><img class="left">Text</p>)
+ */
+.left  			{ float: left !important; }
+p .left			{ margin: 1.5em 1.5em 1.5em 0; padding: 0; }
+.right 			{ float: right !important; }
+p .right 		{ margin: 1.5em 0 1.5em 1.5em; padding: 0; }
+
+a:focus,
+a:hover     { color: #09f; }
+a           { color: #06c; text-decoration: underline; }
+
+blockquote  { margin: 1.5em; color: #666; font-style: italic; }
+strong,dfn	{ font-weight: bold; }
+em,dfn      { font-style: italic; }
+sup, sub    { line-height: 0; }
+
+abbr,
+acronym     { border-bottom: 1px dotted #666; }
+address     { margin: 0 0 1.5em; font-style: italic; }
+del         { color:#666; }
+
+pre         { margin: 1.5em 0; white-space: pre; }
+pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
+
+
+/* Lists
+-------------------------------------------------------------- */
+
+li ul,
+li ol       { margin: 0; }
+ul, ol      { margin: 0 1.5em 1.5em 0; padding-left: 1.5em; }
+
+ul          { list-style-type: disc; }
+ol          { list-style-type: decimal; }
+
+dl          { margin: 0 0 1.5em 0; }
+dl dt       { font-weight: bold; }
+dd          { margin-left: 1.5em;}
+
+
+/* Tables
+-------------------------------------------------------------- */
+
+/*
+	Because of the need for padding on TH and TD, the vertical rhythm
+	on table cells has to be 27px, instead of the standard 18px or 36px
+	of other elements.
+ */
+table       { margin-bottom: 1.4em; width:100%; }
+th          { font-weight: bold; }
+thead th    { background: #c3d9ff; }
+th,td,caption { padding: 4px 10px 4px 5px; }
+/*
+	You can zebra-stripe your tables in outdated browsers by adding
+	the class "even" to every other table row.
+ */
+tbody tr:nth-child(even) td,
+tbody tr.even td  {
+	background: #e5ecf9;
+}
+tfoot       { font-style: italic; }
+caption     { background: #eee; }
+
+
+/*Images
+-------------------------------------------------------------- */
+img         { width:400px; height:auto;}
+
+/* Misc classes
+-------------------------------------------------------------- */
+
+.small      { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
+.large      { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
+.hide       { display: none; }
+
+.quiet      { color: #666; }
+.loud       { color: #000; }
+.highlight  { background:#ff0; }
+.added      { background:#060; color: #fff; }
+.removed    { background:#900; color: #fff; }
+
+.first      { margin-left:0; padding-left:0; }
+.last       { margin-right:0; padding-right:0; }
+.top        { margin-top:0; padding-top:0; }
+.bottom     { margin-bottom:0; padding-bottom:0; }
+
+
+/* Page setup
+-------------------------------------------------------------- */
+@page {
+    @bottom-center{
+        content: counter(page) " / " counter(pages);
+        font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+    }
+}