{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Feature Detection\n",
    "\n",
    "This Notebook is used to test the detection ability of the NeuroKit2 library to detect features in the ECG dataset. Those features than are important to train the model to detect the different diagnostic groups. The features are generated using the NeuroKit2 library."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "import wfdb.processing\n",
    "import sys\n",
    "import json\n",
    "import scipy\n",
    "import numpy as np\n",
    "import neurokit2 as nk\n",
    "import math\n",
    "\n",
    "sys.path.append('../scripts')\n",
    "import data_helper\n",
    "\n",
    "# possible liberays to use:\n",
    "# https://neuropsychology.github.io/NeuroKit/examples/ecg_delineate/ecg_delineate.html\n",
    "# https://biosppy.readthedocs.io/en/stable/\n",
    "# https://python-heart-rate-analysis-toolkit.readthedocs.io/en/latest/\n",
    "# https://pywavelets.readthedocs.io/en/latest/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Reading GSVT\n",
      "Reading AFIB\n",
      "Reading SR\n",
      "Reading SB\n",
      "Number of patients per category:\n",
      "GSVT: 1027\n",
      "AFIB: 9787\n",
      "SR: 10426\n",
      "SB: 15826\n"
     ]
    }
   ],
   "source": [
    "data_org = data_helper.load_data(only_demographic=False)\n",
    "\n",
    "print(\"Number of patients per category:\")\n",
    "for cat_name in data_org.keys():\n",
    "    print(f\"{cat_name}: {len(data_org[cat_name])}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Noise Reduction\n",
    "Load the parameters needed to filter the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('../filter_params.json', 'r') as f:\n",
    "    filter_params = json.load(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "example_data = data_org['SB'][0]\n",
    "sig_channel= 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_data = data_helper.filter_data(example_data, filter_params)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 107856 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#data = filtered_data.p_signal[:,sig_channel]\n",
    "ecg_signal = data_org['SB'][0].p_signal[:,sig_channel]\n",
    "ecg_signal = filtered_data.p_signal[:,sig_channel]\n",
    "\n",
    "sampling_rate = 500 # Sampling rate of your ECG data\n",
    "\n",
    "\n",
    "ecg_cleaned = nk.ecg_clean(ecg_signal, sampling_rate=sampling_rate)\n",
    "\n",
    "# Plot the original and denoised signal\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(example_data.p_signal[:,0], label='Original signal')\n",
    "plt.plot(ecg_cleaned, label='bib filtered signal')\n",
    "#plt.plot(filtered_data.p_signal[:,0], label='self filtered signal')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Amplitude')\n",
    "plt.title('Signal with and without All Denoising')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Detect Peaks"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[477, 1050, 1617, 2216, 2750, 3386, 3890, 4482]\n",
      "[439, 1019, 1590, 2186, 2727, 3356, 3876, 4458]\n",
      "[512, 1075, 1647, 2248, 2757, 3421, 3915, 4496]\n"
     ]
    }
   ],
   "source": [
    "# Extract R-peaks\n",
    "_, rpeaks = nk.ecg_peaks(ecg_cleaned, sampling_rate=sampling_rate)\n",
    "\n",
    "# Extract all ECG features\n",
    "ecg_signals, info = nk.ecg_delineate(ecg_cleaned, rpeaks, sampling_rate=sampling_rate, method=\"dwt\")\n",
    "\n",
    "# Normalize data by setting qrs peaks to index position 0 (“X-axis normalization”)\n",
    "p_peaks = info['ECG_P_Peaks']\n",
    "p_onsets = info['ECG_P_Onsets']\n",
    "p_offsets = info['ECG_P_Offsets']\n",
    "print(p_peaks)\n",
    "print(p_onsets)\n",
    "print(p_offsets)\n",
    "\n",
    "p_peaks_y = [ecg_cleaned[i] for i in p_peaks] \n",
    "p_onsets_y = [ecg_cleaned[i] for i in p_onsets]\n",
    "p_offsets_y = [ecg_cleaned[i] for i in p_offsets]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 141052 characters]",
      "text/plain": [
       "<Figure size 640x480 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Process it\n",
    "signals, info = nk.ecg_process(ecg_cleaned, sampling_rate=500)\n",
    "\n",
    "# Visualise the processing\n",
    "nk.ecg_plot(signals, info)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[693, 1262, 1835, 2435, 3005, 3603, 4165, 4758]\n",
      "[636, 1234, 1786, 2388, 2947, 3546, 4122, 4698]\n",
      "[737, 1307, 1881, 2476, 3049, 3651, 4214, 4799]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 89904 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "t_peaks = info['ECG_T_Peaks']\n",
    "t_onsets = info['ECG_T_Onsets']\n",
    "t_offsets = info['ECG_T_Offsets']\n",
    "\n",
    "print(t_peaks)\n",
    "print(t_onsets)\n",
    "print(t_offsets)\n",
    "\n",
    "t_peaks_y = [ecg_cleaned[i] for i in t_peaks] \n",
    "t_onsets_y = [ecg_cleaned[i] for i in t_onsets]\n",
    "t_offsets_y = [ecg_cleaned[i] for i in t_offsets]\n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(ecg_cleaned)\n",
    "plt.scatter(t_peaks, t_peaks_y, color='red', label='t_peaks')\n",
    "plt.scatter(t_onsets, t_onsets_y, color='green', label='t_onsets')\n",
    "plt.scatter(t_offsets, t_offsets_y, color='blue', label='t_offsets')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Amplitude')\n",
    "plt.title('Filtered XQRS Signal with Peaks')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[477, 1050, 1617, 2217, 2788, 3386, 3890, 4543]\n",
      "[438, 1018, 1589, 2184, 2763, 3355, 3876, 4511]\n",
      "[514, 1076, 1648, 2248, 2820, 3421, 3914, 4574]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 89608 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "p_peaks = info['ECG_P_Peaks']\n",
    "p_onsets = info['ECG_P_Onsets']\n",
    "p_offsets = info['ECG_P_Offsets']\n",
    "print(p_peaks)\n",
    "print(p_onsets)\n",
    "print(p_offsets)\n",
    "\n",
    "p_peaks_y = [ecg_cleaned[i] for i in p_peaks] \n",
    "p_onsets_y = [ecg_cleaned[i] for i in p_onsets]\n",
    "p_offsets_y = [ecg_cleaned[i] for i in p_offsets]\n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(ecg_cleaned)\n",
    "plt.scatter(p_peaks, p_peaks_y, color='red', label='p_peaks')\n",
    "plt.scatter(p_onsets, p_onsets_y, color='green', label='p_onsets')\n",
    "plt.scatter(p_offsets, p_offsets_y, color='blue', label='p_offsets')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Amplitude')\n",
    "plt.title('Filtered XQRS Signal with Peaks')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[523, 1086, 1658, 2256, 2830, 3430, 3990]\n",
      "[563, 1132, 1701, 2300, 2873, 3471, 4034]\n",
      "[482, 1055, 1622, 2221, 2794, 3391, 3957]\n",
      "[580, 1151, 1720, 2317, 2893, 3491, 4055]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 92588 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 'ECG_Q_Peaks', 'ECG_R_Onsets', 'ECG_R_Offsets', 'ECG_S_Peaks'\n",
    "\n",
    "\n",
    "q_peaks = info['ECG_Q_Peaks'][:-1]\n",
    "s_peaks = info['ECG_S_Peaks'][:-1]\n",
    "r_onsets = info['ECG_R_Onsets'][:-1]\n",
    "r_offsets = info['ECG_R_Offsets'][:-1]\n",
    "\n",
    "print(q_peaks)\n",
    "print(s_peaks)\n",
    "print(r_onsets)\n",
    "print(r_offsets)\n",
    "\n",
    "q_peaks_y = [ecg_cleaned[i] for i in q_peaks] \n",
    "s_peaks_y = [ecg_cleaned[i] for i in s_peaks]\n",
    "r_onsets_y = [ecg_cleaned[i] for i in r_onsets]\n",
    "r_offsets_y = [ecg_cleaned[i] for i in r_offsets]\n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(ecg_cleaned)\n",
    "plt.scatter(q_peaks, q_peaks_y, color='red', label='q_peaks')\n",
    "plt.scatter(s_peaks, s_peaks_y, color='green', label='s_peaks')\n",
    "plt.scatter(r_onsets, r_onsets_y, color='blue', label='r_onsets')\n",
    "plt.scatter(r_offsets, r_offsets_y, color='purple', label='r_offsets')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Amplitude')\n",
    "plt.title('Filtered XQRS Signal with Peaks')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 69644 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Delineate the ECG signal and visualizing all peaks of ECG complexes\n",
    "_, waves_peak = nk.ecg_delineate(ecg_signal, \n",
    "                                 rpeaks, \n",
    "                                 sampling_rate=500, \n",
    "                                 method=\"peak\", \n",
    "                                 show=True, \n",
    "                                 show_type='peaks')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 51128 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Delineate the ECG signal and visualizing all P-peaks boundaries\n",
    "signal_peak, waves_peak = nk.ecg_delineate(ecg_signal, \n",
    "                                           rpeaks, \n",
    "                                           sampling_rate=500,\n",
    "                                           method=\"peak\", \n",
    "                                           show=True, \n",
    "                                           show_type='bounds_P')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 51100 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Delineate the ECG signal and visualizing all T-peaks boundaries\n",
    "signal_peaj, waves_peak = nk.ecg_delineate(ecg_signal, \n",
    "                                           rpeaks, \n",
    "                                           sampling_rate=500, \n",
    "                                           method=\"peak\", \n",
    "                                           show=True, \n",
    "                                           show_type='bounds_T')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 100520 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Delineate the ECG signal\n",
    "signal_cwt, waves_cwt = nk.ecg_delineate(ecg_signal, \n",
    "                                         rpeaks, \n",
    "                                         sampling_rate=500, \n",
    "                                         method=\"cwt\", \n",
    "                                         show=True, \n",
    "                                         show_type='all')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 69464 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize P-peaks and T-peaks\n",
    "signal_cwt, waves_cwt = nk.ecg_delineate(ecg_signal, \n",
    "                                         rpeaks, \n",
    "                                         sampling_rate=500, \n",
    "                                         method=\"cwt\", \n",
    "                                         show=True, \n",
    "                                         show_type='peaks')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 56296 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize T-waves boundaries\n",
    "signal_cwt, waves_cwt = nk.ecg_delineate(ecg_signal, \n",
    "                                         rpeaks, \n",
    "                                         sampling_rate=500, \n",
    "                                         method=\"cwt\", \n",
    "                                         show=True, \n",
    "                                         show_type='bounds_T')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 56884 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize P-waves boundaries\n",
    "signal_cwt, waves_cwt = nk.ecg_delineate(ecg_signal, \n",
    "                                         rpeaks, \n",
    "                                         sampling_rate=500, \n",
    "                                         method=\"cwt\", \n",
    "                                         show=True, \n",
    "                                         show_type='bounds_P')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 55664 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualize R-waves boundaries\n",
    "signal_cwt, waves_cwt = nk.ecg_delineate(ecg_signal, \n",
    "                                         rpeaks, \n",
    "                                         sampling_rate=500, \n",
    "                                         method=\"cwt\", \n",
    "                                         show=True, \n",
    "                                         show_type='bounds_R')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Learning initial signal parameters...\n",
      "Failed to find 8 beats during learning.\n",
      "Initializing using default parameters\n",
      "Running QRS detection...\n",
      "QRS detection complete.\n",
      "[ 544 1114 1683 2281 2855 3452 4016 4606]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 78176 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Normalize data by setting qrs peaks to index position 0 (“X-axis normalization”)\n",
    "indicies = wfdb.processing.xqrs_detect(filtered_data.p_signal[:,sig_channel],example_data.fs,0,5000)\n",
    "print(indicies)\n",
    "\n",
    "peak_points_y = [filtered_data.p_signal[:,sig_channel][i] for i in indicies] \n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(filtered_data.p_signal[:,sig_channel])\n",
    "plt.scatter(indicies, peak_points_y, color='red', label='Peaks')\n",
    "plt.xlabel('Time')\n",
    "plt.ylabel('Amplitude')\n",
    "plt.title('Filtered XQRS Signal with Peaks')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Calculate R and T Axis for one datapoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calculate_axis(data, leads, sampling_rate=500, aVF=5, I=0):\n",
    "    # warnining if leads not include 0 and 5 or one lead is over 5\n",
    "    if not (I in leads and aVF in leads) or any([lead > aVF for lead in leads]) or len(leads) > 6:\n",
    "        print(\"Warning: Leads should include 0 and 5, and should not include a lead over 5\")\n",
    "        return None, None\n",
    "    # Calculate the net QRS in each lead\n",
    "    net_qrs = {}\n",
    "    net_t = {}\n",
    "    for lead in leads:\n",
    "        # get data and peaks\n",
    "        ecg_signal = data.p_signal[:,lead]\n",
    "        signals, info = nk.ecg_process(ecg_signal, sampling_rate=500)\n",
    "        # r axis\n",
    "        # get amplitude of peaks\n",
    "        q_peaks = [ecg_signal[int(i)] for i in info['ECG_Q_Peaks'] if not math.isnan(i)]\n",
    "        s_peaks = [ecg_signal[int(i)] for i in info['ECG_S_Peaks'] if not math.isnan(i)]\n",
    "        _, rpeaks_idx = nk.ecg_peaks(ecg_signal, sampling_rate=sampling_rate)\n",
    "        rpeaks_idx = rpeaks_idx['ECG_R_Peaks']\n",
    "        r_peaks = [ecg_signal[int(i)] for i in rpeaks_idx if not math.isnan(i)]\n",
    "        # calculate avg peal amplitude\n",
    "        q_peaks_avg = np.mean(q_peaks) if q_peaks else 0\n",
    "        s_peaks_avg = np.mean(s_peaks) if s_peaks else 0\n",
    "        r_peaks_avg = np.mean(r_peaks) if r_peaks else 0\n",
    "        # Calculate net QRS in lead\n",
    "        net_qrs[lead] = r_peaks_avg - (q_peaks_avg + s_peaks_avg)\n",
    "\n",
    "        # T axis\n",
    "        t_peaks = [ecg_signal[int(i)] for i in info['ECG_T_Peaks'] if not math.isnan(i)]\n",
    "        net_t[lead] = np.mean(t_peaks) if t_peaks else 0\n",
    "\n",
    "    print(net_qrs.get(aVF, 0), net_qrs.get(I, 0))\n",
    "    # Calculate the R axis (Convert to degrees)\n",
    "    r_axis = np.arctan2(net_qrs.get(aVF, 0), net_qrs.get(I, 0)) * (180 / np.pi)\n",
    "\n",
    "    # Calculate the T axis (Convert to degrees)\n",
    "    t_axis = np.arctan2(net_t.get(aVF, 0), net_t.get(I, 0)) * (180 / np.pi)\n",
    "    \n",
    "    return r_axis, t_axis\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8047692058411333 0.5260930603345693\n",
      "2 lead R Axis: 56.83 degrees\n",
      "2 lead T Axis: 30.78 degrees\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# print(data_org['SB'][0].__dict__) 'sig_name': ['I', 'II', 'III', 'aVR', 'aVL', 'aVF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']\n",
    "\n",
    "i_sig = 0\n",
    "avf = 5\n",
    "sampling_rate = 500\n",
    "\n",
    "example_data = data_org['SB'][0]\n",
    "filtered_data = data_helper.filter_data(example_data, filter_params)\n",
    "\n",
    "\n",
    "r_ax, t_ax = calculate_axis(filtered_data, [0, 5], sampling_rate=500)\n",
    "print(f\"2 lead R Axis: {r_ax:.2f} degrees\")\n",
    "print(f\"2 lead T Axis: {t_ax:.2f} degrees\")\n",
    "print(\"\")\n"
   ]
  }
 ],
 "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.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
