{ "cells": [ { "cell_type": "markdown", "id": "8217d056-c75e-446a-930d-24c5364d2041", "metadata": {}, "source": [ "# Use Case 2: Data from multiple sources in an animal experiment\n", "\n", "
\n", "\n", "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: \n", "[![Documentation Status](https://readthedocs.org/projects/vitabel/badge/?version=latest)](https://vitabel.readthedocs.io/en/latest/?badge=latest)\n", "\n", "In this case we analyze data collected in an animal laboratory experiment of cardiopulmonary resuscitation. This notebook in particular demonstrates the capabilities of `vitabel` to **align** time-series data recorded by **multiple devices** with unsynchronized clocks.\n", "\n", "
\n", "\n", "If you have never worked with _Jupyter Notebooks_ before, you may find this guide helpful: **[Beginners Guide to Jupyter Notebooks](https://mybinder.org/v2/gh/jupyter/notebook/HEAD?urlpath=%2Fdoc%2Ftree%2Fdocs%2Fsource%2Fexamples%2FNotebook%2FRunning+Code.ipynb)**" ] }, { "cell_type": "code", "execution_count": null, "id": "8865d157-3791-4855-a97f-54350306fd38", "metadata": {}, "outputs": [], "source": [ "from vitabel import Vitals, IntervalLabel\n", "\n", "from pathlib import Path\n", "import pandas as pd" ] }, { "cell_type": "markdown", "id": "53b87624-1de6-4b01-a602-11836d567ac6", "metadata": {}, "source": [ "## 1. Loading Data" ] }, { "cell_type": "markdown", "id": "d33399f4-a038-46fe-add0-d65e80608df5", "metadata": {}, "source": [ "
\n", "\n", "We begin by specifying the file paths for the multiple data sources. In this use case, three different types of \n", "files are read: data from the mechanical CPR device is stored in an XML export from a proprietary format. Invasive blood pressure was recorded from\n", "a patient monitor via [VitalRecorder](https://doi.org/10.1038/s41598-018-20062-4).\n", "Airflow, airway pressure were recorded each by a separate single board computer and stored in CSV files.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "53b61298-6e47-468d-ad38-8598aa57cdd4", "metadata": {}, "outputs": [], "source": [ "lucas_file = Path(\"data/Lucas_file_Lucas.xml\") # exported via CodeStat\n", "vital_recorder_file = Path(\"data/vital_file.vit\") # recorded via VitalRecorder (https://vitaldb.net/vital-recorder/)\n", "\n", "flow_file = Path(\"data/flow.csv.bz2\")\n", "airway_pressure_file = Path(\"data/p1.csv.bz2\")\n", "capnography_file = Path(\"data/capno.csv.bz2\")" ] }, { "cell_type": "markdown", "id": "5907eee4-a22e-4d09-a927-2cda1bfb190b", "metadata": {}, "source": [ "
\n", "\n", "A new instance of the `Vitals` class is initialized and all data is loaded from the files.\n", "As ventilatory parameters were recorded in UNIX time, `time_start` and `time_unit` must be set accordingly.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "680a8cdb-9363-43e7-85fe-c887eaa4923c", "metadata": {}, "outputs": [], "source": [ "case = Vitals()\n", "case.add_defibrillator_recording(lucas_file)\n", "case.add_vital_db_recording(\n", " vital_recorder_file, \n", " metadata={\"source\": \"GE Healthcare monitor\"}, # providing metadata to the channels\n", ") \n", "\n", "case.add_data_from_csv(\n", " flow_file,\n", " time_start=pd.Timestamp(1970, 1, 1, 0, 0, 0),\n", " time_unit=\"ms\",\n", " metadata={\"source\": \"volucapno\"},\n", " index_col=\"timestamp\",\n", ")\n", "case.add_data_from_csv(\n", " capnography_file,\n", " time_start=pd.Timestamp(1970, 1, 1, 0, 0, 0),\n", " time_unit=\"ms\",\n", " metadata={\"source\": \"volucapno\"},\n", " index_col=\"Timestamp\",\n", ")\n", "case.add_data_from_csv(\n", " airway_pressure_file,\n", " time_start=pd.Timestamp(1970, 1, 1, 0, 0, 0),\n", " time_unit=\"ms\",\n", " metadata={\"source\": \"volucapno\"},\n", " index_col=0,\n", " names=[\"airway_pressure\", \"temperature_1\"],\n", ")" ] }, { "cell_type": "markdown", "id": "f7429c52-b609-4c1f-9f7f-afff41a30ee1", "metadata": {}, "source": [ "## 2. Processing Data" ] }, { "cell_type": "markdown", "id": "2cf848fd-13a9-42bc-a6d0-ec81f4e82bc4", "metadata": {}, "source": [ "
\n", "\n", "Several channels are assigned to variables and **renamed** to our discretion.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "f5b80780-8d7e-40b1-8b71-6737bccc1342", "metadata": {}, "outputs": [], "source": [ "capno_channel = case.get_channel(\"CO2 Concentration\")\n", "capno_channel.rename(\"capnography\")\n", "\n", "case.get_channel(\"PLETH\").rename(\"ppg\") # just another way to achieve the same" ] }, { "cell_type": "markdown", "id": "40477b92-4d77-459b-b74b-bdf2afe0e3d0", "metadata": {}, "source": [ "
\n", "\n", "We get an overview over all channels by calling the `get_channel_infos` routine\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "e0f72738-92ab-46b8-ae5f-1b99e7de719f", "metadata": {}, "outputs": [], "source": [ "case.get_channel_infos()" ] }, { "cell_type": "markdown", "id": "8674d03f-16a7-4313-81be-cd2333902ea6", "metadata": {}, "source": [ "
\n", "\n", "By calling `get_channel_infos` with **keyword arguments (`kwargs`)** we can filter the list of channels, for example by the _source_ set in their `metadata`. \n", "The argument `kwargs` can be used the same way with other functions to retrieve channels and labels or details on them.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "c6a91794-5483-4741-81a2-3c4be4343391", "metadata": {}, "outputs": [], "source": [ "case.get_channel_infos(metadata={\"source\": \"volucapno\"})" ] }, { "cell_type": "markdown", "id": "6dad72c8-3d29-4d4b-8652-e9cb3b247b5d", "metadata": {}, "source": [ "
\n", "\n", "In the following we are calling specific channels and assign them to variables, such that we can work on them later on.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "b838fd35-385c-4186-afe2-56ead3e8d62f", "metadata": {}, "outputs": [], "source": [ "ibp_channel = case.get_channel(name=\"IBP1\")\n", "cc_channel = case.get_channel(name=\"cc\")\n", "flow_channel = case.get_channel(\"airflow\")\n", "pressure_channel = case.get_channel(\"airway_pressure\")" ] }, { "cell_type": "markdown", "id": "a21374d0-f4e0-401e-9adb-754252a2ecfc", "metadata": {}, "source": [ "
\n", "\n", "We generated a new `IntervalLabel` to later annotate noisy segements of the invasive blood pressure recordings, for example due to sampling of blood gas samples.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "4c55d682-c55e-4e1e-bd49-3b4ca61796dd", "metadata": {}, "outputs": [], "source": [ "aline_noise = IntervalLabel(\"IBP noise\", annotation_preset_type=\"timestamp\")\n", "aline_noise.attach_to(ibp_channel)" ] }, { "cell_type": "markdown", "id": "c5e9e5e7-4c54-4f12-a305-9db38d4f2d5e", "metadata": {}, "source": [ "
\n", "\n", "Calling the `print_data_summary` method gives us information about channels and labels and their **attachment**.\n", "Shifting the time index of a channel will also shift all labels attached to it accordingly.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "017972da-6f17-4387-b282-da3341fb100f", "metadata": {}, "outputs": [], "source": [ "case.print_data_summary()" ] }, { "cell_type": "markdown", "id": "be3c238c-5f28-4a88-a7db-1a85eda17d61", "metadata": {}, "source": [ "## 3. Plotting Data interactively\n" ] }, { "cell_type": "markdown", "id": "597a22d5-aa9c-4182-805c-57caf17084ef", "metadata": {}, "source": [ "
\n", "\n", "The plotstyle of the channels is adapted by the `set_channel_plotstyle` method. All keyword arguments of matplotlib's [axes.plot](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.plot.html) can be set in the plotstyle dictionary.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "861e6074-5eb9-434d-8465-55817baa2dc8", "metadata": {}, "outputs": [], "source": [ "case.set_channel_plotstyle(capno_channel, color=\"goldenrod\", lw=1, alpha=0.8)\n", "case.set_channel_plotstyle(\"airflow\", color=\"blue\", lw=1, alpha=0.8) # another way to call the channel by its name\n", "case.get_channel(\"airway_pressure\").plotstyle = {\"color\": \"green\", \"lw\": 1, \"alpha\": 0.8} # the plotstyle can also be set by direct assignment\n", "case.set_channel_plotstyle(ibp_channel, color=\"red\", lw=1, alpha=0.8)\n", "case.set_channel_plotstyle(cc_channel, color=\"purple\", marker=\"o\", alpha=0.8, linestyle=\"\", label=\"chest compressions\") # be aware that we define the _label_ of the plot legend here\n", "case.set_label_plotstyle(aline_noise, color=\"dimgray\", lw=3, alpha=0.8)" ] }, { "cell_type": "markdown", "id": "b006ed0c-31cf-44ef-9a85-1d8cfbb60343", "metadata": {}, "source": [ "
\n", "\n", "An interactive plot is initialized. The menu an explanation to **align** the channels is given in the tab `Align Timelines`. We can first align the chest compressions to the IBP and than select both and align them with the first artefact of a chest compression in the airway pressure. Afterward, we can **label** noisy segments in the blood-pressure signal the the menu offered in the `Annotate`-Tab. Be aware that we have added an `IntervaLabel` which has to be defined by a start and an end, thus clicking twice.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "8e202974-35f4-481c-aa54-2241b42a8670", "metadata": {}, "outputs": [], "source": [ "plot = case.plot_interactive(\n", " channels=[\n", " [flow_channel, \"cc\"], # flow_channel is added directly, while the cc channel is added by its name\n", " [4], # the capnography channel is added via its channel index\n", " [pressure_channel],\n", " [ibp_channel],\n", " ],\n", " labels=[[], [], [], [\"IBP noise\"]],\n", " channel_overviews=[[ibp_channel]],\n", " time_unit=\"s\",\n", " subplots_kwargs={\"figsize\": (16.5, 8)},\n", ")\n", "\n", "plot" ] }, { "cell_type": "markdown", "id": "2fd99fa9-f56c-4966-a83d-03222a258c75", "metadata": {}, "source": [ "## 4. Storing Data\n" ] }, { "cell_type": "markdown", "id": "8bc7ac13", "metadata": {}, "source": [ "
\n", "\n", "As we have seen already, the `Vitals.info` method prints a compact overview of all channels and labels contained in a given collection.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "2b543e66-9d23-4d8e-ab70-09f7ff3233ea", "metadata": {}, "outputs": [], "source": [ "case.info()" ] }, { "cell_type": "markdown", "id": "1ac66cfe", "metadata": {}, "source": [ "
\n", "\n", "As soon as we are done with our adjustments and annotations, the `Vitals` object can be serialized and saved to a JSON file.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "995aba87-e130-4eba-a932-84ced280891e", "metadata": {}, "outputs": [], "source": [ "case.save_data(\"case_2.json\")" ] }, { "cell_type": "markdown", "id": "f7478c76", "metadata": {}, "source": [ "
\n", "\n", "Our `Vitals` objects that have been serialized and stored can be loaded again using the `Vitals.load_data` method.\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "id": "42540ddb", "metadata": {}, "outputs": [], "source": [ "new_case = Vitals()\n", "new_case.load_data(\"case_2.json\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.13 (spa-ga)", "language": "python", "name": "spa-ga" }, "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.13.3" } }, "nbformat": 4, "nbformat_minor": 5 }