diff --git a/.gitignore b/.gitignore index 206289357..7a235f4e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +ramutils/test/test_data/*.png out.html .cache/ ramutils/test/plotting/sample_plots/ @@ -9,7 +10,6 @@ scratch/ *.pdf *.synctex.gz *.swp -*.png *.zip *.ipynb_checkpoints/ diff --git a/demos/report_generation.py b/demos/report_generation.py index 90d67851d..c0eccb842 100644 --- a/demos/report_generation.py +++ b/demos/report_generation.py @@ -1,21 +1,16 @@ -from __future__ import print_function - -import os.path -from ramutils.parameters import FilePaths, FRParameters, PS5Parameters +from ramutils.parameters import FilePaths, FRParameters from ramutils.pipelines.report import make_report from ramutils.tasks import memory -memory.cachedir = "~" - - +memory.cachedir = "scratch/ramutils_test" paths = FilePaths( - root='/', - dest='/scratch/zduey/', - data_db='/scratch/zduey/' + root="~/mnt/rhino", + dest="scratch/ramutils_test/", + data_db="/scratch/report_database", ) params = FRParameters() -make_report("R1001P", "FR1", paths, exp_params=params, stim_params=None, - joint_report=False, rerun=False, sessions=None) +make_report("R1384J", "FR5", paths, exp_params=params, stim_params=None, + joint_report=False, rerun=False, sessions=[0], clinical=True) diff --git a/ramutils/pipelines/report.py b/ramutils/pipelines/report.py index e765cea6e..f1d970ad4 100644 --- a/ramutils/pipelines/report.py +++ b/ramutils/pipelines/report.py @@ -19,7 +19,7 @@ def make_report(subject, experiment, paths, joint_report=False, retrain=False, stim_params=None, exp_params=None, sessions=None, vispath=None, rerun=False, trigger_electrode=None, use_classifier_excluded_leads=False, - pipeline_name="report"): + pipeline_name="report", clinical=False): """ Constructs a report and saves out all the necessary data to re-construct the report This pipeline should be used for generating single session reports for both record-only and @@ -59,6 +59,9 @@ def make_report(subject, experiment, paths, joint_report=False, classifier training pipeline_name : str Name to use for status updates. + clinical: bool + If True, builds a demo clinical report instead of the internal RAM + report Returns ------- @@ -117,7 +120,8 @@ def make_report(subject, experiment, paths, joint_report=False, pre_built_results['target_selection_table'], pre_built_results['classifier_evaluation_results'], paths.dest, - hmm_results=pre_built_results['hmm_results']) + hmm_results=pre_built_results['hmm_results'], + clinical=clinical) return report.compute() final_pairs = generate_pairs_for_classifier(ec_pairs, excluded_pairs) @@ -182,7 +186,8 @@ def make_report(subject, experiment, paths, joint_report=False, report = build_static_report(subject, experiment, data.session_summaries, data.math_summaries, data.target_selection_table, data.classifier_evaluation_results, - hmm_results=output, dest=paths.dest) + hmm_results=output, dest=paths.dest, + clinical=clinical) if vispath is not None: report.visualize(filename=vispath) diff --git a/ramutils/reports/generate.py b/ramutils/reports/generate.py index 8c8b6633e..10bfebf4b 100644 --- a/ramutils/reports/generate.py +++ b/ramutils/reports/generate.py @@ -4,16 +4,15 @@ import json import os.path as osp import random -import itertools from itertools import compress from jinja2 import Environment, PackageLoader import numpy as np -from pkg_resources import resource_listdir, resource_string +from pkg_resources import resource_listdir, resource_filename, resource_string from ramutils import __version__ from ramutils.reports.summary import FRSessionSummary, MathSummary, FRStimSessionSummary -from ramutils.events import extract_experiment_from_events, extract_subject +from ramutils.events import extract_experiment_from_events from ramutils.utils import extract_experiment_series @@ -68,9 +67,12 @@ class ReportGenerator(object): """ def __init__(self, subject, experiment, session_summaries, math_summaries, - target_selection_table, classifier_summaries, hmm_results=None, dest='.'): + target_selection_table, classifier_summaries, hmm_results=None, + dest='.', clinical=False): self.subject = subject self.experiment = experiment + self.clinical = clinical + self.session_summaries = session_summaries self.math_summaries = math_summaries self.target_selection_table = target_selection_table @@ -301,7 +303,7 @@ def generate(self): raise NotImplementedError("Unsupported report type") def _render(self, experiment, **kwargs): - """Convenience method to wrap common keyword arguments passed to the + """ Convenience method to wrap common keyword arguments passed to the template renderer. Parameters @@ -311,7 +313,20 @@ def _render(self, experiment, **kwargs): Additional keyword arguments that are passed to the render method. """ - template = self._env.get_template(experiment.lower() + '.html') + if self.clinical: + from base64 import b64encode + + template = self._env.get_template('clinical_target_selection.html') + + for hemi in ["left", "right"]: + with open(resource_filename("ramutils.reports.static", + "r1384j_{}.png".format(hemi)), "rb") as infile: + encoded = b64encode(infile.read()) + img_string = "data:image/png;base64,{}".format(encoded.decode()) + kwargs["{}_hemisphere_image".format(hemi)] = img_string + else: + template = self._env.get_template(experiment.lower() + '.html') + return template.render( version=self.version, subject=self.subject, @@ -364,20 +379,26 @@ def generate_open_loop_fr_report(self): ) def generate_closed_loop_fr_report(self, experiment): - """ Generate an FR5 report + """ Generate a closed loop stimulation report Returns ------- - Rendered FR5 report as a string. + Rendered stimulation report as a string """ + stim_params = FRStimSessionSummary.stim_parameters(self.session_summaries) + multistim = False + if len(stim_params) > 1: + multistim = True + return self._render( experiment, stim=True, + multistim=multistim, + date=self.session_summaries[0].session_datetime, combined_summary=self._make_combined_summary(), classifiers=self._make_classifier_data(), - stim_params=FRStimSessionSummary.stim_parameters( - self.session_summaries), + stim_params=stim_params, recall_tests=FRStimSessionSummary.recall_test_results( self.session_summaries, experiment), feature_data=self._make_feature_plots(), diff --git a/ramutils/reports/static/plots.js b/ramutils/reports/static/plots.js index 107605528..8db482f85 100644 --- a/ramutils/reports/static/plots.js +++ b/ramutils/reports/static/plots.js @@ -9,12 +9,18 @@ var ramutils = (function (mod, Plotly) { * @param {Array} serialPos - Serial positions (x-axis) * @param {Object} overallProbs - Overall probabilities per serial position * @param {Object} firstProbs - Probability of first recall per serial position + * @param {Object} plot_first -- If true, plot probability of first recall */ - plotSerialpos: function (serialPos, overallProbs, firstProbs) { + plotSerialpos: function (serialPos, overallProbs, firstProbs, plot_first = true) { const mode = "lines+markers"; let data = []; + max_prob = 0 for (let name in overallProbs) { + current_max = Math.max(...overallProbs[name]) + if (current_max > max_prob) { + max_prob = current_max + }; data.push({ x: serialPos, y: overallProbs[name], @@ -23,14 +29,20 @@ var ramutils = (function (mod, Plotly) { }); } - for (let name in firstProbs) { - data.push({ - x: serialPos, - y: firstProbs[name], - mode: mode, - name: name - }); - } + if (plot_first) { + for (let name in firstProbs) { + current_max = Math.max(...firstProbs[name]) + if (current_max > max_prob) { + max_prob = current_max + }; + data.push({ + x: serialPos, + y: firstProbs[name], + mode: mode, + name: name + }); + } + }; const layout = { title: "Probability of Recall as a Function of Serial Position", @@ -40,7 +52,7 @@ var ramutils = (function (mod, Plotly) { }, yaxis: { title: 'Probability', - range: [0, 1] + range: [0, max_prob + 0.01] } }; @@ -103,9 +115,10 @@ var ramutils = (function (mod, Plotly) { * @param {Object} nonStimRecalls * @param {Object} stimRecalls * @param {Object} stimEvents + * @param {bool} plot_stim_events */ - plotRecallSummary: function (nonStimRecalls, stimRecalls, stimEvents) { - const data = [ + plotRecallSummary: function (nonStimRecalls, stimRecalls, stimEvents, plot_stim_events = true) { + let data = [ { x: nonStimRecalls.listno, y: nonStimRecalls.recalled, @@ -122,13 +135,15 @@ var ramutils = (function (mod, Plotly) { marker: {size: 12}, name: 'Stim recalls' }, - { + ] + if (plot_stim_events == true) { + data.push({ x: stimEvents.listno, y: stimEvents.count, type: 'bar', name: 'Stim events' - } - ]; + }) + }; const layout = { title: 'Number of Items Stimulated and Number of Items Recalled', diff --git a/ramutils/reports/static/r1384j_left.png b/ramutils/reports/static/r1384j_left.png new file mode 100644 index 000000000..9ece5c459 Binary files /dev/null and b/ramutils/reports/static/r1384j_left.png differ diff --git a/ramutils/reports/static/r1384j_right.png b/ramutils/reports/static/r1384j_right.png new file mode 100644 index 000000000..b0aa96494 Binary files /dev/null and b/ramutils/reports/static/r1384j_right.png differ diff --git a/ramutils/reports/summary.py b/ramutils/reports/summary.py index 02bb13d7d..cec5d613e 100644 --- a/ramutils/reports/summary.py +++ b/ramutils/reports/summary.py @@ -1029,9 +1029,16 @@ def recall_test_results(summaries, experiment): df = df[df.list > -1] results = [] + n_correct_recalls = df.recalled.sum() + n_items = len(df) for name, group in df.groupby(['stimAnodeTag', 'stimCathodeTag', 'amplitude', 'stim_duration', 'pulse_freq']): + if name[0].find(",") != -1: + target_name = "Multi-Site" + else: + target_name = "-".join([name[0], name[1]]) + single_target_results = {"target": target_name} parameters = "/".join([str(n) for n in name]) # Stim lists vs. non-stim lists @@ -1047,13 +1054,16 @@ def recall_test_results(summaries, experiment): n_correct_stim_list_recalls, n_correct_nonstim_list_recalls], [n_stim_list_words, n_nonstim_list_words]) - results.append({"parameters": parameters, - "comparison": "Stim Lists vs. Non-stim Lists", - "stim": (n_correct_stim_list_recalls, - n_stim_list_words), - "non-stim": (n_correct_nonstim_list_recalls, n_nonstim_list_words), - "t-stat": tstat_list, - "p-value": pval_list}) + single_target_results['list'] = { + "parameters": parameters, + "comparison": "Stim Lists vs. Non-stim Lists", + "stim": (n_correct_stim_list_recalls, + n_stim_list_words), + "non-stim": (n_correct_nonstim_list_recalls, n_nonstim_list_words), + "delta_recall": 100 * ((n_correct_stim_list_recalls/n_stim_list_words) - + (n_correct_nonstim_list_recalls/n_nonstim_list_words)) / (n_correct_recalls / n_items), + "t-stat": tstat_list, + "p-value": pval_list} # stim items vs. non-stim low biomarker items n_correct_stim_item_recalls = group[group.is_stim_item == True].recalled.sum( @@ -1071,13 +1081,15 @@ def recall_test_results(summaries, experiment): [n_correct_stim_item_recalls, n_correct_nonstim_item_recalls], [n_stim_items, n_nonstim_items]) - results.append({ + single_target_results['item'] = { "parameters": parameters, "comparison": "Stim Items vs. Low Biomarker Non-stim Items", "stim": (n_correct_stim_item_recalls, n_stim_items), "non-stim": (n_correct_nonstim_item_recalls, n_nonstim_items), + "delta_recall": 100 * ((n_correct_stim_item_recalls/n_stim_items) - + (n_correct_nonstim_item_recalls/n_nonstim_items)) / (n_correct_recalls / n_items), "t-stat": tstat_list, - "p-value": pval_list}) + "p-value": pval_list} # post stim items vs. non-stim low biomarker items n_correct_post_stim_item_recalls = group[group.is_post_stim_item == True].recalled.sum( @@ -1089,14 +1101,17 @@ def recall_test_results(summaries, experiment): [n_correct_post_stim_item_recalls, n_correct_nonstim_item_recalls], [n_post_stim_items, n_nonstim_items]) - results.append({ + single_target_results['post_stim_item'] = { "parameters": parameters, "comparison": "Post-stim Items vs. Low Biomarker Non-stim Items", "stim": (n_correct_post_stim_item_recalls, n_post_stim_items), "non-stim": (n_correct_nonstim_item_recalls, n_nonstim_items), + "delta_recall": 100 * ((n_correct_post_stim_item_recalls/n_post_stim_items) - + (n_correct_nonstim_item_recalls/n_nonstim_items)) / (n_correct_recalls / n_items), "t-stat": tstat_list, - "p-value": pval_list}) - + "p-value": pval_list + } + results.append(single_target_results) return results @staticmethod diff --git a/ramutils/reports/templates/clinical_base.html b/ramutils/reports/templates/clinical_base.html new file mode 100644 index 000000000..efd3bbbad --- /dev/null +++ b/ramutils/reports/templates/clinical_base.html @@ -0,0 +1,33 @@ +{% set sections = [] %} + + +
+ +Courtesy of Nia Theraputics: {{ datetime.now().strftime('%Y-%m-%d %H:%M') }}
+| Target | +Location | +TMI | +SME | +Delta Recall | +Delta Classifier | +Rank | +
| LA10-LA11 | +Left Middle Temporal | +2.87 | +4.45 | +27% | +.06 | +1 | +
| LF4-LF5 | +Let Superior Frontal | +2.64 | +3.12 | +30% | +.08 | +2 | +
| RFI4-RFI5 | +Right Superior Frontal | +3.01 | +2.75 | +12% | +0.04 | +3 | +
| RMI1-RMI2 | +Right Caudal Middle Frontal | +1.75 | +1.04 | +3% | +0.01 | +4 | +