Use Case 1: Defibrillator data from out-of-hospital cardiac arrest case¶
This notebook illustrates the usage of the vitabel module to visualize, annotate and process time-series data from the medical field. Please find the detailed, searchable documentation here:
In this case we analyze data recorded by a defibrillator during a real-world out-of-hospital cardiac arrest case.
This notebook in particular demonstrates the capabilities of vitabel to display and annote data.
If you have never worked with Jupyter Notebooks before, you may find this guide helpful: Beginners Guide to Jupyter Notebooks
from vitabel import Vitals, Label
import bz2
from pathlib import Path
import tempfile
import shutil
from datetime import datetime
import pandas as pd
1. Loading Data¶
An empty Vitabel object called case is initiated and all channels from a defibrillator recording (in this case a ZOLL X-Series case, stored in a compressed file) are loaded and added to the case.
case = Vitals()
compressed_defi_file = Path("data/ZOLL_test_case.json.bz2")
with tempfile.TemporaryDirectory() as tmpdir:
defi_file = Path(tmpdir) / compressed_defi_file.stem
with bz2.open(compressed_defi_file, "rb") as source:
with open(defi_file, "wb") as dest:
shutil.copyfileobj(source, dest)
case.add_defibrillator_recording(defi_file)
Every Vitabel object as well as every channel and label holds a metadata dictionary. In the following we add the usage of the case in this notebook to the metadata.
usage_metadata = {
"vitabel publication": {
"user": "ENTER YOUR NAME HERE",
"purpose": "highlighting capabilities to display and annotate defibrillator data",
"remarks": [
"a useful tool for researchers",
"handling data of devices by other manufacturers should be integrated better in the future"
],
}
}
case.metadata.update({"usage": usage_metadata})
print("Current case metadata:")
case.metadata
We get an overview over all loaded channels by calling case.info()
case.info()
2. Processing Data¶
We use integrated algorithms to extract etCO₂ values and detect ventilations from the capnography signal. Additionally, we analyze the accelerometer data from the CPR feedback device and the ECG signal from the defibrillation pads to estimate the probability of spontaneous circulation.
case.compute_etco2_and_ventilations(source='capnography')
case.predict_circulation(cpr_acceleration_source='cpr_acceleration', ecg_pads_source='ecg_pads')
3. Adding new Labels¶
We aim to manually annotate occurrences of Return of Spontaneous Circulation (ROSC). To do so, we create an empty label and add it to the case. With metadata we can store miscellaneous data in the label inside a dictionary ({}). Furthermore, we can define the plotstyle by passing parameters in another dictionary.
The argument plot_type determines how label data is depicted in the plot. As the type is also dynamically adapted to the available data,
we can explore the different types by adding labels with different data in the later generated plot.
More details on labels can be found in our documentation.
ROSC_label = Label(
name = "ROSC",
time_index = [],
data = [],
text_data = [],
metadata = {
"source": "manual annotation",
"label initialization time" : str(datetime.now())
},
plot_type = "combined",
plotstyle = {"marker": "$\u2665$", "color": "red", "ms": 10, "linestyle": ""},
)
case.add_global_label(ROSC_label)
We see the newly added label (ROSC) by checking out all present labels in the case.
case.get_label_names()
4. Plotting Data interactively¶
We provide a setup for the interactive plot.
The channels to be plottted are defined as lists in a list ([[],[]]). Each of the inner lists define a new subplot.
The labels to be plotted on top are defined by another lists in a list.
Subplots below the already defined ones are defined by channel_overviews. These subplots serve a special purpose by displaying the entire recording interval and highlighting the depicted subsegement in the upper subplots.
Above the plot a menu is given to Annotate, Align Timelines, and deffines Settings of the plot.
Try to annotate the ROSCs by clicking with the right mouse button where you assume a ROSC. Also try to toggle the checkboxes in various combinations in oder to add different data to the label.
plot = case.plot_interactive(
channels = [
["cpr_acceleration"],
["capnography"],
["ecg_pads"],
[]
],
labels = [
["ROSC", "cc_periods"],
["etco2_from_capnography", "ROSC"],
["ROSC"],
["ROSC", "rosc_probability"],
],
channel_overviews = [["cpr_acceleration"]],
time_unit = "s",
subplots_kwargs = {"figsize": (16.5, 8)},
)
plot
The data that has been added to the ROSC label can be retrieved by calling the get_data method. The result is returned in a DataSlice object.
ROSC_label.get_data()
By accessing time_index, data, or text_data we can explicitly obtain them from the output of get_data.
dt = ROSC_label.get_data()
print(
"Time Index: ", dt.time_index,
"\nNumeric Values: ", dt.data,
"\nTextual Values: ", dt.text_data,
"\n"
)
5. Storing Data¶
The entire case can be serialized and stored as a JSON file.
case.save_data("case_1.json")
Raw data of individual channels or labels can also be exported to a CSV file.
case.get_label("ROSC").to_csv()
pd.read_csv("ROSC.csv", index_col = 0)
Alternatively, the label can also be serialized as a dictionary.
case.get_label("ROSC").to_dict()