├── data
├── datasets
│ └── .gitkeep
├── records
│ └── .gitkeep
└── metadata.csv
├── examples
├── dl
│ ├── log
│ │ └── .gitkeep
│ ├── config.py
│ ├── create_dataset.py
│ ├── model.py
│ ├── predict.ipynb
│ └── train_model.py
└── ml
│ ├── config.py
│ └── create_dataset.py
├── requirements.txt
├── iridia_af
├── hyperparameters.py
└── record.py
├── README.md
└── LICENSE
/data/datasets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/data/records/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/dl/log/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/ml/config.py:
--------------------------------------------------------------------------------
1 | WINDOW_SIZE = 300
2 | TRAINING_STEP = 100
3 | PREDICTION_STEP = 10
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | h5py
2 | numpy
3 | pandas
4 | matplotlib
5 | scipy
6 | xgboost
7 | scikit-learn
8 | torch
9 | tqdm
--------------------------------------------------------------------------------
/examples/dl/config.py:
--------------------------------------------------------------------------------
1 | WINDOW_SIZE = 8192
2 | TRAINING_STEP = 4096
3 | TESTING_STEP = 8192
4 | RANDOM_SEED = 42
5 |
6 | EPOCH = 100
7 | PATIENCE = 5
8 | BATCH_SIZE = 32
9 | LEARNING_RATE = 0.0001
10 |
11 | NUM_PROC_WORKERS_DATA = 4
12 |
13 | def get_dict():
14 | return {
15 | "WINDOW_SIZE": WINDOW_SIZE,
16 | "TRAINING_STEP": TRAINING_STEP,
17 | "TESTING_STEP": TESTING_STEP,
18 | "RANDOM_SEED": RANDOM_SEED,
19 | "EPOCH": EPOCH,
20 | "PATIENCE": PATIENCE,
21 | "BATCH_SIZE": BATCH_SIZE,
22 | "LEARNING_RATE": LEARNING_RATE,
23 | "NUM_PROC_WORKERS": NUM_PROC_WORKERS_DATA
24 | }
--------------------------------------------------------------------------------
/iridia_af/hyperparameters.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 | from pathlib import Path
3 |
4 | ROOT_PATH = Path(__file__).resolve().parent.parent.resolve()
5 | DATA_PATH = Path(ROOT_PATH, "data")
6 | DATASET_PATH = Path(DATA_PATH, "datasets")
7 |
8 | LOG_DL_PATH = Path(ROOT_PATH, "examples", "dl", "log")
9 |
10 | RECORDS_PATH = Path(DATA_PATH, "records")
11 | assert RECORDS_PATH.exists(), f"RECORDS_PATH does not exist: {RECORDS_PATH}"
12 | METADATA_PATH = Path(DATA_PATH, "metadata.csv")
13 | assert METADATA_PATH.exists(), f"METADATA_PATH does not exist: {METADATA_PATH}"
14 |
15 | NUM_PROC = multiprocessing.cpu_count() - 2
16 |
17 | SAMPLE_RATE = 200
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IRIDIA-AF
2 |
3 | A large paroxysmal atrial fibrillation long-term electrocardiogram monitoring database
4 | ## Authors
5 |
6 | - Cédric GILON (1)
7 | - Jean-Marie GRÉGOIRE (1,2)
8 | - Marianne MATHIEU (1)
9 | - Stéphane CARLIER (2)
10 | - Hugues BERSINI (1)
11 |
12 | 1. IRIDIA, Université libre de Bruxelles, Belgium
13 | 2. Cardiology departement, Université de Mons, Belgium
14 |
15 | ## Abstract
16 |
17 | Atrial fibrillation (AF) is the most common sustained heart arrhythmia in adults.
18 | Holter monitoring, a long-term 2-lead electrocardiogram (ECG), is a key tool available to cardiologists for AF diagnosis.
19 | Machine learning (ML) and deep learning (DL) models have shown great capacity to automatically detect AF in ECG and their use as medical decision support tool is growing.
20 | Training these models rely on a few open and annotated databases.
21 | We present a new Holter monitoring database from patients with paroxysmal AF with 167 records from 152 patients, acquired from an outpatient cardiology clinic from 2006 to 2017 in Belgium.
22 | AF episodes were manually annotated and reviewed by an expert cardiologist and a specialist cardiac nurse.
23 | Records last from 19 hours up to 95 hours, divided into 24-hour files.
24 | In total, it represents 24 million seconds of annotated Holter monitoring, sampled at 200 Hz.
25 | This dataset aims at expanding the available options for researchers and offers a valuable resource for advancing ML and DL use in the field of cardiac arrhythmia diagnosis.
26 |
27 | ## Data
28 |
29 | The data is available on Zenodo: https://zenodo.org/records/8405941.
30 |
--------------------------------------------------------------------------------
/examples/dl/create_dataset.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 | import h5py
4 | import numpy as np
5 | import pandas as pd
6 | import torch
7 | from torch.utils.data import Dataset
8 | from tqdm import tqdm
9 |
10 | import config as cfg
11 | import iridia_af.hyperparameters as hp
12 | from iridia_af.record import create_record
13 |
14 |
15 | def create_dataset_csv():
16 | metadata_df = pd.read_csv(hp.METADATA_PATH)
17 | list_windows = []
18 | for record_id in tqdm(metadata_df["record_id"].unique()):
19 | record = create_record(record_id, metadata_df, hp.RECORDS_PATH)
20 | record.load_ecg()
21 | for day_index in range(record.metadata.record_n_files):
22 | len_day = record.ecg[day_index].shape[0]
23 | for i in range(0, len_day - cfg.WINDOW_SIZE, cfg.TRAINING_STEP):
24 | label = 1 if np.sum(record.ecg_labels[day_index][i:i + cfg.WINDOW_SIZE]) > 0 else 0
25 | detection_window = {
26 | "patient_id": record.metadata.patient_id,
27 | "file": record.ecg_files[day_index],
28 | "start_index": i,
29 | "end_index": i + cfg.WINDOW_SIZE,
30 | "label": label
31 | }
32 | list_windows.append(detection_window)
33 |
34 | new_df = pd.DataFrame(list_windows)
35 | new_df_path = Path(hp.DATASET_PATH, f"dataset_detection_ecg_{cfg.WINDOW_SIZE}.csv")
36 | new_df.to_csv(Path(new_df_path, index=False))
37 | print(f"Saved dataset to {Path(hp.DATASET_PATH, f'dataset_detection_ecg_{cfg.WINDOW_SIZE}.csv')}")
38 |
39 |
40 | class DetectionDataset(Dataset):
41 | def __init__(self, df):
42 | self.df = df
43 |
44 | def __len__(self):
45 | return len(self.df)
46 |
47 | def __getitem__(self, idx):
48 | dw = self.df.iloc[idx]
49 | with h5py.File(dw.file, "r") as f:
50 | key = list(f.keys())[0]
51 | ecg_data = f[key][dw.start_index:dw.end_index, 0]
52 | ecg_data = torch.tensor(ecg_data, dtype=torch.float32)
53 | ecg_data = ecg_data.unsqueeze(0)
54 | label = torch.tensor(dw.label, dtype=torch.float32)
55 | label = label.unsqueeze(0)
56 | return ecg_data, label
57 |
58 |
59 | if __name__ == "__main__":
60 | create_dataset_csv()
61 |
--------------------------------------------------------------------------------
/examples/ml/create_dataset.py:
--------------------------------------------------------------------------------
1 | import multiprocessing
2 | from itertools import repeat
3 | from pathlib import Path
4 |
5 | import hrvanalysis as hrv
6 | import numpy as np
7 | import pandas as pd
8 |
9 | import iridia_af.hyperparameters as hp
10 | import config as cfg
11 | from iridia_af.record import Record
12 |
13 |
14 | def main():
15 | metadata_df = pd.read_csv(hp.METADATA_PATH)
16 | list_record_path = metadata_df["record_id"].values
17 | with multiprocessing.Pool(hp.NUM_PROC) as pool:
18 | all_windows = pool.starmap(get_record_windows, zip(list_record_path, repeat(metadata_df)))
19 |
20 | new_all_windows = []
21 | for windows in all_windows:
22 | new_all_windows.extend(windows)
23 |
24 | df_features = pd.DataFrame(new_all_windows)
25 | new_df_path = Path(hp.DATASET_PATH, f"dataset_hrv_{cfg.WINDOW_SIZE}_{cfg.TRAINING_STEP}.csv")
26 | df_features.to_csv(new_df_path, index=False)
27 | print(f"Saved dataset to {new_df_path}")
28 |
29 |
30 | def get_record_windows(record_id, metadata_df):
31 | metadata_record = metadata_df[metadata_df["record_id"] == record_id]
32 | metadata_record = metadata_record.values[0]
33 | record_path = Path(hp.RECORDS_PATH, record_id)
34 | record = Record(record_path, metadata_record)
35 | record.load_rr_record()
36 | record_windows = []
37 | for day_index in range(record.num_days):
38 | for i in range(0, len(record.rr[day_index]) - cfg.WINDOW_SIZE, cfg.TRAINING_STEP):
39 | rr_window = record.rr[day_index][i:i + cfg.WINDOW_SIZE]
40 | label_window = record.rr_labels[day_index][i:i + cfg.WINDOW_SIZE]
41 | if np.sum(label_window) == 0:
42 | label = 0
43 | else:
44 | label = 1
45 | features = get_hrv_metrics(rr_window)
46 | features["patient"] = record.metadata.patient_id
47 | features["record"] = record.metadata.record_id
48 | features["label"] = label
49 | record_windows.append(features)
50 | print(f"Finished record {record_id}")
51 | return record_windows
52 |
53 |
54 | def get_hrv_metrics(rr_window: np.ndarray):
55 | time_domain_features = hrv.get_time_domain_features(rr_window)
56 | frequency_domain_features = hrv.get_frequency_domain_features(rr_window)
57 | geometrical_features = hrv.get_geometrical_features(rr_window)
58 | poincare_features = hrv.get_poincare_plot_features(rr_window)
59 |
60 | all_features = {**time_domain_features, **frequency_domain_features, **geometrical_features, **poincare_features}
61 | # remove tinn
62 | del all_features["tinn"]
63 | return all_features
64 |
65 |
66 | if __name__ == '__main__':
67 | main()
68 |
--------------------------------------------------------------------------------
/examples/dl/model.py:
--------------------------------------------------------------------------------
1 | import dataclasses
2 | import torch
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 |
6 |
7 | @dataclasses.dataclass
8 | class CNNModelConfig:
9 | input_size: int
10 |
11 |
12 | class CNNModel(nn.Module):
13 | def __init__(self, config: CNNModelConfig):
14 | super().__init__()
15 | self.config = config
16 |
17 | # input 2 leads with 4096 samples (= 20 seconds)
18 | self.block1 = ResidualBlock(in_channels=1, out_channels=16, kernel_size=7, padding=3)
19 | self.block2 = ResidualBlock(in_channels=16, out_channels=16, kernel_size=7, padding=3)
20 | self.block3 = ResidualBlock(in_channels=16, out_channels=16, kernel_size=7, padding=3)
21 |
22 | self.dropout = nn.Dropout(p=0.2)
23 |
24 | self.block4 = ResidualBlock(in_channels=16, out_channels=32, kernel_size=5, padding=2)
25 | self.block5 = ResidualBlock(in_channels=32, out_channels=32, kernel_size=5, padding=2)
26 | self.block6 = ResidualBlock(in_channels=32, out_channels=32, kernel_size=5, padding=2)
27 |
28 | self.block7 = ResidualBlock(in_channels=32, out_channels=64, kernel_size=3, padding=1)
29 | self.block8 = ResidualBlock(in_channels=64, out_channels=64, kernel_size=3, padding=1)
30 | self.block9 = ResidualBlock(in_channels=64, out_channels=64, kernel_size=3, padding=1)
31 |
32 | # invert dimension for convolution on leads (2)
33 | self.conv1 = nn.Conv1d(in_channels=8, out_channels=128, kernel_size=8, stride=2, padding=1)
34 | self.bn1 = nn.BatchNorm1d(128)
35 |
36 | size = int(config.input_size / 8)
37 |
38 | self.fc1 = nn.Linear(size, 32)
39 | self.fc2 = nn.Linear(32, 1)
40 |
41 | def forward(self, x):
42 | # block 1
43 | x = self.block1(x)
44 | x = self.block2(x)
45 | x = self.block3(x)
46 |
47 | x = self.dropout(x)
48 |
49 | x = self.block4(x)
50 | x = self.block5(x)
51 | x = self.block6(x)
52 |
53 | x = self.dropout(x)
54 |
55 | x = self.block7(x)
56 | x = self.block8(x)
57 | x = self.block9(x)
58 |
59 | x = self.dropout(x)
60 |
61 | x = x.view(x.shape[0], -1)
62 | x = self.fc1(x)
63 | x = F.relu(x)
64 | x = self.fc2(x)
65 | x = torch.sigmoid(x)
66 |
67 | return x
68 |
69 | def configure_optimizers(self):
70 | optimizer = torch.optim.AdamW(self.parameters(), lr=1e-3)
71 | return optimizer
72 |
73 |
74 | class ResidualBlock(nn.Module):
75 | def __init__(self, in_channels, out_channels, kernel_size, padding):
76 | super().__init__()
77 | self.conv1 = nn.Conv1d(in_channels=in_channels, out_channels=out_channels,
78 | kernel_size=kernel_size, stride=1, padding=padding)
79 | self.bn1 = nn.BatchNorm1d(out_channels)
80 |
81 | self.conv2 = nn.Conv1d(in_channels=out_channels, out_channels=out_channels,
82 | kernel_size=kernel_size, stride=2, padding=padding)
83 | self.bn2 = nn.BatchNorm1d(out_channels)
84 |
85 | self.conv_shortcut = nn.Conv1d(in_channels=in_channels, out_channels=out_channels,
86 | kernel_size=1, stride=1, padding=0)
87 | self.maxpool = nn.MaxPool1d(kernel_size=2)
88 |
89 | def forward(self, x_in):
90 | x = self.conv1(x_in)
91 | x = F.relu(x)
92 | x = self.bn1(x)
93 |
94 | x = self.conv2(x)
95 | x = F.relu(x)
96 | x = self.bn2(x)
97 |
98 | shortcut = self.conv_shortcut(x_in)
99 | shortcut = self.maxpool(shortcut)
100 |
101 | x += shortcut
102 |
103 | return x
104 |
--------------------------------------------------------------------------------
/examples/dl/predict.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "outputs": [],
7 | "source": [
8 | "%load_ext autoreload\n",
9 | "%autoreload 2"
10 | ],
11 | "metadata": {
12 | "collapsed": false
13 | }
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "outputs": [],
19 | "source": [
20 | "from pathlib import Path\n",
21 | "\n",
22 | "import matplotlib.pyplot as plt\n",
23 | "\n",
24 | "plt.rcParams['figure.dpi'] = 300\n",
25 | "import numpy as np\n",
26 | "import pandas as pd\n",
27 | "import torch\n",
28 | "from tqdm.notebook import tqdm\n",
29 | "\n",
30 | "import iridia_af.hyperparameters as hp\n",
31 | "import config as cfg\n",
32 | "from model import CNNModel, CNNModelConfig\n",
33 | "from iridia_af.record import create_record"
34 | ],
35 | "metadata": {
36 | "collapsed": false
37 | }
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "outputs": [],
43 | "source": [
44 | "# folder_name = \"20230701-161104\"\n",
45 | "folder_name = \"20230720-164047\"\n",
46 | "folder_path = Path(hp.LOG_DL_PATH, folder_name)\n",
47 | "assert folder_path.exists()\n",
48 | "\n",
49 | "model_path = Path(folder_path, \"model.pt\")\n",
50 | "assert model_path.exists()\n",
51 | "#\n",
52 | "config = CNNModelConfig(cfg.WINDOW_SIZE)\n",
53 | "model = CNNModel(config)\n",
54 | "model.load_state_dict(torch.load(model_path))\n",
55 | "model.float()\n",
56 | "model.eval()\n",
57 | "\n",
58 | "# folder path\n",
59 | "folder_path.absolute()"
60 | ],
61 | "metadata": {
62 | "collapsed": false
63 | }
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "outputs": [],
69 | "source": [
70 | "metadata_df = pd.read_csv(hp.METADATA_PATH)\n",
71 | "record = create_record(\"record_104\", metadata_df, hp.RECORDS_PATH)\n",
72 | "record.load_ecg(clean_front=True)"
73 | ],
74 | "metadata": {
75 | "collapsed": false
76 | }
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": null,
81 | "outputs": [],
82 | "source": [
83 | "all_y_pred = [[] for _ in range(len(record.ecg[0]))]\n",
84 | "for i in tqdm(range(0, len(record.ecg[0]) - cfg.WINDOW_SIZE, cfg.TESTING_STEP)):\n",
85 | " x = torch.tensor(record.ecg[0][i:i + cfg.WINDOW_SIZE, 0].copy()).float()\n",
86 | " x = x.unsqueeze(0).unsqueeze(0)\n",
87 | " y_pred = model(x)\n",
88 | " for j in range(i, i + cfg.WINDOW_SIZE):\n",
89 | " all_y_pred[j].append(y_pred.detach().numpy()[0][0])"
90 | ],
91 | "metadata": {
92 | "collapsed": false
93 | }
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "outputs": [],
99 | "source": [
100 | "y_pred = []\n",
101 | "for i in tqdm(all_y_pred):\n",
102 | " if len(i) == 0:\n",
103 | " y_pred.append(0)\n",
104 | " else:\n",
105 | " y_pred.append(np.mean(i))"
106 | ],
107 | "metadata": {
108 | "collapsed": false
109 | }
110 | },
111 | {
112 | "cell_type": "code",
113 | "execution_count": null,
114 | "outputs": [],
115 | "source": [
116 | "fig, ax = plt.subplots(3, 1, figsize=(20, 10), sharex=True)\n",
117 | "\n",
118 | "ax_index = 0\n",
119 | "ax[ax_index].plot(record.ecg[0][:, 0])\n",
120 | "ax[ax_index].set_ylabel(\"ECG Lead I (mV)\")\n",
121 | "ax_index += 1\n",
122 | "\n",
123 | "ax[ax_index].plot(record.ecg_labels[0])\n",
124 | "ax[ax_index].set_ylim(-0.1, 1.1)\n",
125 | "ax[ax_index].set_yticks([0, 1])\n",
126 | "ax[ax_index].set_yticklabels([\"NSR\", \"AF\"])\n",
127 | "ax[ax_index].set_ylabel(\"Annotation\")\n",
128 | "ax_index += 1\n",
129 | "\n",
130 | "ax[ax_index].plot(y_pred)\n",
131 | "ax[ax_index].set_ylim(-0.1, 1.1)\n",
132 | "ax[ax_index].set_yticks([0, 1])\n",
133 | "ax[ax_index].set_yticklabels([\"NSR\", \"AF\"])\n",
134 | "ax[ax_index].set_ylabel(\"Prediction\")\n",
135 | "ax_index += 1"
136 | ],
137 | "metadata": {
138 | "collapsed": false
139 | }
140 | }
141 | ],
142 | "metadata": {
143 | "kernelspec": {
144 | "display_name": "Python 3",
145 | "language": "python",
146 | "name": "python3"
147 | },
148 | "language_info": {
149 | "codemirror_mode": {
150 | "name": "ipython",
151 | "version": 2
152 | },
153 | "file_extension": ".py",
154 | "mimetype": "text/x-python",
155 | "name": "python",
156 | "nbconvert_exporter": "python",
157 | "pygments_lexer": "ipython2",
158 | "version": "2.7.6"
159 | }
160 | },
161 | "nbformat": 4,
162 | "nbformat_minor": 0
163 | }
164 |
--------------------------------------------------------------------------------
/examples/dl/train_model.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from pathlib import Path
3 |
4 | import numpy as np
5 | import pandas as pd
6 | import torch
7 | import torch.nn as nn
8 | from sklearn.metrics import roc_auc_score, confusion_matrix
9 | from sklearn.model_selection import train_test_split
10 | from tqdm import tqdm
11 |
12 | import config as cfg
13 | import iridia_af.hyperparameters as hp
14 | from create_dataset import DetectionDataset
15 | from model import CNNModel, CNNModelConfig
16 |
17 | torch.manual_seed(cfg.RANDOM_SEED)
18 | torch.backends.cuda.matmul.allow_tf32 = True # allow tf32 on matmul
19 | torch.backends.cudnn.allow_tf32 = True # allow tf32 on cudnn
20 |
21 |
22 | def train_model():
23 | print("Loading data")
24 | train_dataset, val_dataset, test_dataset, list_patients = create_train_val_test_split()
25 |
26 | device = get_device()
27 | print(f"Using device: {device}")
28 |
29 | print(cfg.get_dict())
30 |
31 | config = CNNModelConfig(input_size=cfg.WINDOW_SIZE)
32 | model = CNNModel(config)
33 |
34 | # summary(model, (1, hp.WINDOW_SIZE))
35 |
36 | model = model.to(device)
37 | optimizer = configure_optimizers(model)
38 | criterion = nn.BCELoss()
39 |
40 | min_val_loss = 1000
41 | min_val_loss_epoch = 0
42 | best_model = None
43 |
44 | for i in range(cfg.EPOCH):
45 | model.train()
46 | train_losses = []
47 | list_y_true = []
48 | list_y_pred = []
49 | for batch_idx, (x, y) in enumerate(tqdm(train_dataset)):
50 | x = x.to(device)
51 | y = y.to(device)
52 | optimizer.zero_grad()
53 | y_pred = model(x)
54 | loss = criterion(y_pred, y)
55 |
56 | train_losses.append(loss.item())
57 | list_y_true.extend(y.tolist())
58 | list_y_pred.extend(y_pred.tolist())
59 |
60 | loss.backward()
61 | optimizer.step()
62 |
63 | total = len(list_y_true)
64 | list_y_pred_round = np.round(list_y_pred)
65 | correct = sum([1 if y_true == y_pred else 0 for y_true, y_pred in zip(list_y_true, list_y_pred_round)])
66 |
67 | train_accuracy = correct / total
68 |
69 | train_loss = np.mean(train_losses)
70 | val_loss = estimate_loss(model, device, val_dataset, criterion)
71 | metrics = estimate_metrics(model, val_dataset, device)
72 | print(f"step {i + 1}: train loss {train_loss}, val loss {val_loss}")
73 | print(f"step {i + 1}: train accuracy {train_accuracy}, val accuracy {metrics['accuracy']}")
74 | print(f"step {i + 1}: train roc_auc {metrics['roc_auc']}, val roc_auc {metrics['roc_auc']}")
75 |
76 | # early stopping
77 | if val_loss < min_val_loss:
78 | min_val_loss = val_loss
79 | min_val_loss_epoch = 0
80 | best_model = model.state_dict()
81 | else:
82 | min_val_loss_epoch += 1
83 |
84 | if min_val_loss_epoch >= cfg.PATIENCE:
85 | print(f"Early stopping at epoch {i + 1}")
86 | model.load_state_dict(best_model)
87 | break
88 |
89 | test_loss = estimate_loss(model, device, test_dataset, criterion)
90 | metrics = estimate_metrics(model, test_dataset, device)
91 | print(f"test loss {test_loss}")
92 | print(f"test roc_auc {metrics['roc_auc']}")
93 | print(f"test accuracy {metrics['accuracy']}")
94 | print(f"test sensitivity {metrics['sensitivity']}")
95 | print(f"test specificity {metrics['specificity']}")
96 | print(f"test f1_score {metrics['f1_score']}")
97 |
98 | # save model
99 | timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
100 | folder = Path(hp.LOG_DL_PATH, f"{timestamp}")
101 | print(f"Saving model to {folder.absolute()}")
102 | folder.mkdir(parents=True, exist_ok=True)
103 | torch.save(model.state_dict(), Path(folder, "model.pt"))
104 | pd.DataFrame(list_patients).to_csv(Path(folder, "list_patients.csv"))
105 | # save hyperparameters
106 | df_hp = pd.DataFrame(cfg.get_dict(), index=[0])
107 | df_hp.to_csv(Path(folder, "hyperparameters.csv"))
108 | # save metrics
109 | df_metrics = pd.DataFrame(metrics, index=[0])
110 | df_metrics.to_csv(Path(folder, "metrics.csv"))
111 |
112 |
113 | @torch.no_grad()
114 | def estimate_loss(model, device, dataset, criterion):
115 | model.eval()
116 | losses = []
117 | for batch_idx, (x, y) in enumerate(dataset):
118 | x = x.to(device)
119 | y = y.to(device)
120 | y_pred = model(x)
121 | loss = criterion(y, y_pred)
122 | losses.append(loss.item())
123 |
124 | # if batch_idx > 10:
125 | # break
126 |
127 | return np.mean(losses)
128 |
129 |
130 | @torch.no_grad()
131 | def estimate_metrics(model, dataset, device, threshold=0.5):
132 | model.eval()
133 | list_y_true = []
134 | list_y_pred = []
135 | for batch_idx, (x, y) in enumerate(dataset):
136 | x = x.to(device)
137 | y = y.to(device)
138 | list_y_true.extend(y.tolist())
139 | y_pred = model(x)
140 | list_y_pred.extend(y_pred.tolist())
141 |
142 | roc_auc = roc_auc_score(list_y_true, list_y_pred)
143 |
144 | list_y_pred_round = np.where(np.array(list_y_pred) > threshold, 1, 0)
145 | cm = confusion_matrix(list_y_true, list_y_pred_round)
146 | accuracy = (cm[0, 0] + cm[1, 1]) / (cm[0, 0] + cm[1, 1] + cm[1, 0] + cm[0, 1])
147 | sensitivity = cm[1, 1] / (cm[1, 1] + cm[1, 0])
148 | specificity = cm[0, 0] / (cm[0, 0] + cm[0, 1])
149 | f1_score = 2 * (sensitivity * specificity) / (sensitivity + specificity)
150 |
151 | return {"roc_auc": roc_auc,
152 | "accuracy": accuracy,
153 | "sensitivity": sensitivity,
154 | "specificity": specificity,
155 | "f1_score": f1_score}
156 |
157 |
158 | def get_device():
159 | if torch.cuda.is_available():
160 | return torch.device("cuda")
161 | elif torch.backends.mps.is_available() and torch.backends.mps.is_built():
162 | return torch.device("mps")
163 | else:
164 | return torch.device("cpu")
165 |
166 |
167 | def configure_optimizers(model):
168 | optimizer = torch.optim.AdamW(model.parameters(), lr=cfg.LEARNING_RATE)
169 | return optimizer
170 |
171 |
172 | def create_train_val_test_split():
173 | dataset_path = Path(hp.DATASET_PATH, f"dataset_detection_ecg_{cfg.WINDOW_SIZE}.csv")
174 | df = pd.read_csv(dataset_path)
175 | # crop to the first 20000 rows
176 | df = df[:20000]
177 | patients = df["patient_id"].unique()
178 |
179 | train_val_patients, test_patients = train_test_split(patients, test_size=0.2, random_state=cfg.RANDOM_SEED)
180 | train_patients, val_patients = train_test_split(train_val_patients, test_size=0.2, random_state=cfg.RANDOM_SEED)
181 |
182 | train_df = df[df["patient_id"].isin(train_patients)]
183 | train_dataset = DetectionDataset(train_df)
184 | train_dataset_loader = torch.utils.data.DataLoader(train_dataset,
185 | batch_size=cfg.BATCH_SIZE,
186 | shuffle=True,
187 | num_workers=cfg.NUM_PROC_WORKERS_DATA,
188 | pin_memory=True)
189 |
190 | val_df = df[df["patient_id"].isin(val_patients)]
191 | val_dataset = DetectionDataset(val_df)
192 | val_dataset_loader = torch.utils.data.DataLoader(val_dataset,
193 | batch_size=cfg.BATCH_SIZE,
194 | shuffle=False,
195 | num_workers=cfg.NUM_PROC_WORKERS_DATA,
196 | pin_memory=True)
197 |
198 | test_df = df[df["patient_id"].isin(test_patients)]
199 | test_dataset = DetectionDataset(test_df)
200 | test_dataset_loader = torch.utils.data.DataLoader(test_dataset,
201 | batch_size=cfg.BATCH_SIZE,
202 | shuffle=False,
203 | num_workers=cfg.NUM_PROC_WORKERS_DATA,
204 | pin_memory=True)
205 |
206 | return train_dataset_loader, val_dataset_loader, test_dataset_loader, [train_patients, val_patients, test_patients]
207 |
208 |
209 | if __name__ == "__main__":
210 | train_model()
211 |
--------------------------------------------------------------------------------
/iridia_af/record.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from pathlib import Path
3 |
4 | import h5py
5 | import hrvanalysis as hrv
6 | import numpy as np
7 | import pandas as pd
8 | from matplotlib import pyplot as plt
9 |
10 |
11 | def create_record(record_id, metadata_df, record_path):
12 | metadata_record = (metadata_df[metadata_df["record_id"] == record_id])
13 | assert len(metadata_record) == 1
14 | metadata_record = metadata_record.values[0]
15 | record_path = Path(record_path, record_id)
16 | record = Record(record_path, metadata_record)
17 | return record
18 |
19 |
20 | class Record:
21 | def __init__(self, record_folder, metadata_record):
22 | # self.metadata = self.__get_metadata(record_id)
23 | self.metadata = RecordMetadata(*metadata_record)
24 |
25 | self.record_folder = record_folder
26 | self.num_days = len(list(self.record_folder.glob("*ecg_*.h5")))
27 |
28 | self.rr_files = sorted(self.record_folder.glob("*rr_*.h5"))
29 | assert len(self.rr_files) == self.num_days
30 | self.rr = None
31 | self.rr_labels_df = None
32 | self.rr_labels = None
33 |
34 | self.ecg_files = sorted(self.record_folder.glob("*ecg_*.h5"))
35 | assert len(self.ecg_files) == self.num_days
36 | self.ecg = None
37 | self.ecg_labels_df = None
38 | self.ecg_labels = None
39 |
40 | def load_rr_record(self):
41 | self.rr = [self.__read_rr_file(rr_file) for rr_file in self.rr_files]
42 | self.__create_rr_labels()
43 |
44 | def __read_rr_file(self, rr_file: Path, clean_rr=True) -> np.ndarray:
45 | with h5py.File(rr_file, "r") as f:
46 | rr = f["rr"][:]
47 | if clean_rr:
48 | rr = self.__clean_rr(rr)
49 | return rr
50 |
51 | def __clean_rr(self, rr_list, remove_invalid=True, low_rr=200, high_rr=4000, interpolation_method="linear",
52 | remove_ecto=True) -> np.ndarray:
53 |
54 | if remove_invalid:
55 | rr_list = [rr if high_rr >= rr >= low_rr else np.nan for rr in rr_list]
56 | rr_list = pd.Series(rr_list).interpolate(method=interpolation_method).tolist()
57 | if remove_ecto:
58 | rr_list = hrv.remove_ectopic_beats(rr_list,
59 | method='custom',
60 | custom_removing_rule=0.3,
61 | verbose=False)
62 | rr_list = pd.Series(rr_list) \
63 | .interpolate(method=interpolation_method) \
64 | .interpolate(limit_direction='both').tolist()
65 | return np.array(rr_list)
66 |
67 | def __read_rr_label(self) -> pd.DataFrame:
68 | rr_labels = sorted(self.record_folder.glob("*rr_labels.csv"))
69 | df_rr_labels = pd.read_csv(rr_labels[0])
70 | return df_rr_labels
71 |
72 | def __create_rr_labels(self):
73 | self.rr_labels_df = self.__read_rr_label()
74 | len_rr = [len(rr) for rr in self.rr]
75 |
76 | start_day = self.rr_labels_df["start_file_index"].unique()
77 | end_day = self.rr_labels_df["end_file_index"].unique()
78 | days = np.unique(np.concatenate([start_day, end_day]))
79 | assert len(days) <= len(len_rr)
80 |
81 | labels = [np.zeros(len_day_rr) for len_day_rr in len_rr]
82 |
83 | for i, row in self.rr_labels_df.iterrows():
84 | rr_event = RREvent(**row)
85 | if rr_event.start_file_index == rr_event.end_file_index:
86 | labels[rr_event.start_file_index][rr_event.start_rr_index:rr_event.end_rr_index] = 1
87 | else:
88 | labels[rr_event.start_file_index][rr_event.start_rr_index:] = 1
89 | labels[rr_event.end_file_index][:rr_event.end_rr_index] = 1
90 | if rr_event.end_file_index - rr_event.end_file_index > 1:
91 | for day in range(rr_event.start_file_index + 1, rr_event.end_file_index):
92 | labels[day][:] = 1
93 | self.rr_labels = labels
94 |
95 | def plot_rr(self, has_day_ticks=True, has_abnormal_color=False):
96 | all_rr = np.concatenate(self.rr)
97 | all_rr_labels = np.concatenate(self.rr_labels)
98 |
99 | fig, ax = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
100 |
101 | ax[0].plot(all_rr)
102 | ax[0].set_ylabel("RR (ms)")
103 |
104 | ax[1].plot(all_rr_labels)
105 | ax[1].set_ylim(-0.1, 1.1)
106 | ax[1].set_yticks([0, 1])
107 | ax[1].set_yticklabels(["NSR", "AF"])
108 | ax[1].set_xlabel("RR index", labelpad=10)
109 | ax[1].set_ylabel("Label")
110 |
111 | ax[0].set_title(f"Record {self.metadata.record_id}")
112 |
113 | if has_day_ticks:
114 | # add vertical lines at the end of each day
115 | len_rr = [len(rr) for rr in self.rr]
116 | index_len_rr = np.cumsum(len_rr)
117 | index_len_rr = np.insert(index_len_rr, 0, 0)
118 | for i in index_len_rr:
119 | ax[0].axvline(i, color="k", linestyle="--", alpha=0.5)
120 | ax[1].axvline(i, color="k", linestyle="--", alpha=0.5)
121 |
122 | if has_abnormal_color:
123 | # color the background of the abnormal regions
124 | abnormal_regions_start = np.where(np.diff(all_rr_labels) == 1)[0]
125 | abnormal_regions_end = np.where(np.diff(all_rr_labels) == -1)[0]
126 | if len(abnormal_regions_start) > len(abnormal_regions_end):
127 | abnormal_regions_end = np.append(abnormal_regions_end, len(all_rr_labels))
128 | elif len(abnormal_regions_start) < len(abnormal_regions_end):
129 | abnormal_regions_start = np.insert(abnormal_regions_start, 0, 0)
130 | for start, end in zip(abnormal_regions_start, abnormal_regions_end):
131 | ax[0].axvspan(start, end, alpha=0.3, color="red")
132 | ax[1].axvspan(start, end, alpha=0.3, color="red")
133 |
134 | plt.show()
135 |
136 | def load_ecg(self, clean_front=False):
137 | ecg_files = sorted(self.record_folder.glob("*_ecg_*.h5"))
138 | self.ecg = [self.__read_ecg_file(ecg_file, clean_front) for ecg_file in ecg_files]
139 | self.__create_ecg_labels(clean_front)
140 |
141 | def __read_ecg_file(self, ecg_file: Path, clean_front=False) -> np.ndarray:
142 | with h5py.File(ecg_file, "r") as f:
143 | key = list(f.keys())[0]
144 | ecg = f[key][:]
145 | if clean_front:
146 | ecg = ecg[6000:]
147 | return ecg
148 |
149 | def __read_ecg_labels(self) -> pd.DataFrame:
150 | ecg_labels = sorted(self.record_folder.glob("*ecg_labels.csv"))
151 | df_ecg_labels = pd.read_csv(ecg_labels[0])
152 | return df_ecg_labels
153 |
154 | def __create_ecg_labels(self, clean_front=False):
155 | self.ecg_labels_df = self.__read_ecg_labels()
156 | len_ecg = [len(ecg) for ecg in self.ecg]
157 |
158 | start_day = self.ecg_labels_df["start_file_index"].unique()
159 | end_day = self.ecg_labels_df["end_file_index"].unique()
160 | days = np.unique(np.concatenate([start_day, end_day]))
161 | assert len(days) <= len(len_ecg)
162 |
163 | labels = [np.zeros(len_day_ecg) for len_day_ecg in len_ecg]
164 |
165 | for i, row in self.ecg_labels_df.iterrows():
166 | if row.start_file_index == row.end_file_index:
167 | labels[row.start_file_index][row.start_qrs_index:row.end_qrs_index] = 1
168 | else:
169 | labels[row.start_file_index][row.start_qrs_index:] = 1
170 | labels[row.end_file_index][:row.end_qrs_index] = 1
171 | if row.end_file_index - row.end_file_index > 1:
172 | for day in range(row.start_file_index + 1, row.end_file_index):
173 | labels[day][:] = 1
174 | if clean_front:
175 | labels = [label[6000:] for label in labels]
176 | self.ecg_labels = labels
177 |
178 | def plot_ecg(self, has_day_ticks=True):
179 | all_ecg = np.concatenate(self.ecg)
180 | all_ecg_labels = np.concatenate(self.ecg_labels)
181 |
182 | # set font size
183 | plt.rcParams.update({"font.size": 18})
184 |
185 | fig, ax = plt.subplots(3, 1, figsize=(10, 8), sharex=True)
186 |
187 | ax[0].plot(all_ecg[:, 0])
188 | ax[0].set_ylabel("ECG I (mV)")
189 |
190 | ax[1].plot(all_ecg[:, 1])
191 | ax[1].set_ylabel("ECG II (mV)")
192 |
193 | ax[2].plot(all_ecg_labels)
194 | ax[2].set_ylim(-0.1, 1.1)
195 | ax[2].set_yticks([0, 1])
196 | ax[2].set_yticklabels(["NSR", "AF"])
197 | ax[2].set_xlabel("ECG index", labelpad=10)
198 | ax[2].set_ylabel("Label")
199 |
200 | if has_day_ticks:
201 | # add vertical lines at the end of each day
202 | len_ecg = [len(ecg) for ecg in self.ecg]
203 | index_len_ecg = np.cumsum(len_ecg)
204 | index_len_ecg = np.insert(index_len_ecg, 0, 0)
205 | for i in index_len_ecg:
206 | ax[0].axvline(i, color="k", linestyle="--", alpha=0.5)
207 | ax[1].axvline(i, color="k", linestyle="--", alpha=0.5)
208 | ax[2].axvline(i, color="k", linestyle="--", alpha=0.5)
209 |
210 | plt.show()
211 |
212 | def number_of_episodes(self):
213 | rr_labels = sorted(self.record_folder.glob("*rr_labels.csv"))
214 | df_rr_labels = pd.read_csv(rr_labels[0])
215 | num_episodes_rr = len(df_rr_labels)
216 |
217 | ecg_labels = sorted(self.record_folder.glob("*ecg_labels.csv"))
218 | df_ecg_labels = pd.read_csv(ecg_labels[0])
219 | num_episodes_ecg = len(df_ecg_labels)
220 |
221 | assert num_episodes_rr == num_episodes_ecg
222 | return num_episodes_rr
223 |
224 |
225 | @dataclass
226 | class RecordMetadata:
227 | patient_id: str
228 | patient_sex: str
229 | patient_age: int
230 | record_id: str
231 | record_date: str
232 | record_start_time: str
233 | record_end_time: str
234 | record_timedelta: str
235 | record_n_files: int
236 | record_n_seconds: int
237 | record_n_samples: int
238 |
239 |
240 | @dataclass
241 | class ECGEvent:
242 | # start event
243 | start_datetime: str
244 | start_file_index: int
245 | start_qrs_index: int
246 | # end event
247 | end_datetime: str
248 | end_file_index: int
249 | end_qrs_index: int
250 | # duration
251 | af_duration: int
252 | nsr_duration: int
253 |
254 |
255 | @dataclass
256 | class RREvent:
257 | # start event
258 | start_file_index: int
259 | start_rr_index: int
260 | # end event
261 | end_file_index: int
262 | end_rr_index: int
263 |
--------------------------------------------------------------------------------
/data/metadata.csv:
--------------------------------------------------------------------------------
1 | patient_id,patient_sex,patient_age,record_id,record_date,record_start_time,record_end_time,record_timedelta,record_files,record_seconds,record_samples
2 | patient_000,female,86,record_000,2012-10-02,2012-10-02T10:50:11,2012-10-03T10:50:02,86391,1,86391,17278200
3 | patient_001,female,72,record_001,2011-08-19,2011-08-19T11:19:55,2011-08-21T11:19:54,172799,2,172798,34559600
4 | patient_002,male,73,record_002,2012-01-16,2012-01-16T11:29:38,2012-01-17T09:34:22,79484,1,79484,15896800
5 | patient_003,female,71,record_003,2017-04-14,2017-04-14T10:18:10,2017-04-15T08:23:04,79494,1,79488,15897790
6 | patient_004,female,71,record_004,2008-08-06,2008-08-06T13:01:10,2008-08-07T11:05:49,79479,1,79479,15895800
7 | patient_005,female,74,record_005,2016-12-29,2016-12-29T11:32:24,2016-12-30T11:32:23,86399,1,86399,17279800
8 | patient_006,female,79,record_006,2010-04-24,2010-04-24T13:51:45,2010-04-28T12:54:09,342144,4,342112,68422592
9 | patient_007,female,74,record_007,2010-12-15,2010-12-15T10:07:32,2010-12-18T19:05:51,291499,4,291474,58294883
10 | patient_008,female,58,record_008,2012-08-27,2012-08-27T11:31:10,2012-08-29T11:31:09,172799,2,172798,34559600
11 | patient_009,male,41,record_009,2012-03-19,2012-03-19T11:02:35,2012-03-23T11:00:09,345454,4,326368,65273661
12 | patient_010,female,80,record_010,2013-11-01,2013-11-01T11:21:07,2013-11-02T11:21:00,86393,1,86393,17278600
13 | patient_011,male,76,record_011,2008-11-07,2008-11-07T09:20:53,2008-11-09T09:20:52,172799,2,172798,34559600
14 | patient_012,male,68,record_012,2016-06-28,2016-06-28T10:01:51,2016-06-29T10:01:44,86393,1,86393,17278600
15 | patient_013,male,59,record_013,2013-11-16,2013-11-16T14:23:53,2013-11-19T14:23:52,259199,3,259197,51839400
16 | patient_014,male,65,record_014,2013-11-18,2013-11-18T12:02:28,2013-11-19T12:02:18,86390,1,86390,17278000
17 | patient_015,female,87,record_015,2012-04-30,2012-04-30T14:43:20,2012-05-01T14:43:19,86399,1,86399,17279800
18 | patient_016,male,86,record_016,2010-03-07,2010-03-07T12:09:01,2010-03-08T12:08:51,86390,1,86390,17278000
19 | patient_017,female,68,record_017,2009-05-30,2009-05-30T11:15:48,2009-05-31T11:15:39,86391,1,86391,17278200
20 | patient_018,female,66,record_018,2017-07-08,2017-07-08T09:06:01,2017-07-09T09:06:00,86399,1,86399,17279800
21 | patient_019,female,99,record_019,2009-07-17,2009-07-17T09:59:47,2009-07-18T09:05:36,83149,1,83142,16628530
22 | patient_020,male,81,record_020,2007-10-28,2007-10-28T12:18:29,2007-11-01T07:28:31,328202,4,327996,65599266
23 | patient_021,female,82,record_021,2012-05-29,2012-05-29T10:01:55,2012-05-30T10:01:54,86399,1,86399,17279800
24 | patient_022,male,78,record_022,2014-05-07,2014-05-07T14:10:51,2014-05-08T10:46:15,74124,1,74121,14824250
25 | patient_023,male,63,record_023,2008-07-02,2008-07-02T15:24:34,2008-07-04T15:24:33,172799,2,172798,34559600
26 | patient_024,male,72,record_024,2011-06-22,2011-06-22T10:16:33,2011-06-24T10:16:32,172799,2,172798,34559600
27 | patient_025,female,64,record_025,2012-06-04,2012-06-04T10:00:41,2012-06-08T10:00:40,345599,4,345596,69119200
28 | patient_026,male,47,record_026,2012-02-20,2012-02-20T14:04:26,2012-02-23T14:04:25,259199,3,259197,51839400
29 | patient_027,male,73,record_027,2009-06-02,2009-06-02T09:43:48,2009-06-03T09:43:45,86397,1,86397,17279400
30 | patient_028,female,79,record_028,2013-06-30,2013-06-30T15:01:50,2013-07-04T09:57:35,327345,4,288005,57601192
31 | patient_029,male,68,record_029,2013-06-23,2013-06-23T14:29:07,2013-06-24T14:28:59,86392,1,86392,17278400
32 | patient_030,male,59,record_030,2015-12-23,2015-12-23T09:44:35,2015-12-24T09:44:34,86399,1,86399,17279800
33 | patient_031,male,77,record_031,2011-04-25,2011-04-25T11:59:55,2011-04-27T11:59:54,172799,2,172798,34559600
34 | patient_032,female,60,record_032,2009-10-28,2009-10-28T13:16:24,2009-10-30T11:19:30,165786,2,165606,33121269
35 | patient_033,female,83,record_033,2011-11-28,2011-11-28T09:51:34,2011-11-29T09:51:33,86399,1,86399,17279800
36 | patient_034,male,67,record_034,2011-12-06,2011-12-06T08:25:16,2011-12-09T08:25:15,259199,3,259197,51839400
37 | patient_035,female,59,record_035,2010-03-26,2010-03-26T16:55:11,2010-03-30T08:51:30,316579,4,316566,63313231
38 | patient_035,female,61,record_036,2011-11-11,2011-11-11T10:19:44,2011-11-14T10:31:52,259928,4,259850,51970146
39 | patient_035,female,66,record_037,2016-07-28,2016-07-28T11:31:21,2016-07-29T11:31:20,86399,1,86399,17279800
40 | patient_035,female,66,record_038,2017-02-17,2017-02-17T11:41:30,2017-02-18T11:41:29,86399,1,86399,17279800
41 | patient_036,male,90,record_039,2013-04-12,2013-04-12T11:12:06,2013-04-13T11:11:58,86392,1,86392,17278400
42 | patient_037,female,93,record_040,2014-08-21,2014-08-21T08:58:07,2014-08-22T08:57:59,86392,1,86392,17278400
43 | patient_038,female,89,record_041,2014-01-18,2014-01-18T12:01:35,2014-01-19T12:01:34,86399,1,83662,16732523
44 | patient_039,female,65,record_042,2011-03-18,2011-03-18T10:04:57,2011-03-20T10:04:56,172799,2,172798,34559600
45 | patient_040,female,89,record_043,2009-12-07,2009-12-07T12:39:04,2009-12-08T12:38:56,86392,1,86392,17278400
46 | patient_041,male,83,record_044,2013-08-28,2013-08-28T09:55:06,2013-08-29T09:55:03,86397,1,86397,17279400
47 | patient_042,female,66,record_045,2013-04-21,2013-04-21T10:41:33,2013-04-22T10:41:32,86399,1,86257,17251582
48 | patient_043,male,75,record_046,2017-04-01,2017-04-01T11:12:42,2017-04-04T11:12:41,259199,3,259197,51839400
49 | patient_044,female,66,record_047,2014-12-06,2014-12-06T10:15:34,2014-12-08T10:15:33,172799,2,172798,34559600
50 | patient_045,male,76,record_048,2013-03-19,2013-03-19T10:09:02,2013-03-20T10:08:56,86394,1,86394,17278800
51 | patient_045,male,80,record_049,2017-03-12,2017-03-12T12:07:17,2017-03-13T12:07:10,86393,1,86393,17278600
52 | patient_046,female,66,record_050,2016-02-05,2016-02-05T11:54:00,2016-02-06T09:58:32,79472,1,79472,15894400
53 | patient_047,male,71,record_051,2013-10-18,2013-10-18T10:24:54,2013-10-21T10:24:53,259199,3,259197,51839400
54 | patient_048,female,70,record_052,2008-06-02,2008-06-02T09:50:40,2008-06-04T09:50:39,172799,2,172798,34559600
55 | patient_049,female,80,record_053,2013-05-17,2013-05-17T07:24:03,2013-05-18T07:23:56,86393,1,86393,17278600
56 | patient_050,female,75,record_054,2013-11-30,2013-11-30T14:07:18,2013-12-01T14:07:17,86399,1,86394,17278800
57 | patient_051,male,68,record_055,2008-03-23,2008-03-23T15:35:17,2008-03-24T11:29:12,71635,1,71408,14281713
58 | patient_052,male,87,record_056,2010-09-30,2010-09-30T10:20:53,2010-10-01T08:24:46,79433,1,73356,14671273
59 | patient_053,female,65,record_057,2013-03-18,2013-03-18T12:35:31,2013-03-19T12:26:27,85856,1,85856,17171200
60 | patient_054,male,79,record_058,2010-04-08,2010-04-08T10:12:25,2010-04-09T10:12:15,86390,1,86390,17278000
61 | patient_055,male,95,record_059,2017-10-06,2017-10-06T08:22:30,2017-10-07T08:22:21,86391,1,86391,17278200
62 | patient_056,female,76,record_060,2015-08-31,2015-08-31T15:34:04,2015-09-03T15:34:03,259199,3,259197,51839400
63 | patient_056,female,77,record_061,2017-04-20,2017-04-20T13:38:22,2017-04-21T13:38:21,86399,1,86399,17279800
64 | patient_057,male,60,record_062,2012-11-06,2012-11-06T15:56:42,2012-11-08T15:56:41,172799,2,172798,34559600
65 | patient_058,male,78,record_063,2013-03-10,2013-03-10T11:05:16,2013-03-14T11:05:04,345588,4,345596,69119200
66 | patient_059,female,55,record_064,2013-01-20,2013-01-20T12:22:19,2013-01-22T12:08:05,171946,2,169937,33987570
67 | patient_060,male,55,record_065,2006-03-15,2006-03-15T00:07:46,2006-03-17T00:07:45,172799,2,172798,34559600
68 | patient_061,male,89,record_066,2014-03-25,2014-03-25T09:39:41,2014-03-26T09:39:37,86396,1,82744,16548800
69 | patient_062,female,75,record_067,2008-10-17,2008-10-17T08:58:46,2008-10-18T08:58:42,86396,1,86396,17279200
70 | patient_062,female,76,record_068,2009-03-03,2009-03-03T12:26:15,2009-03-07T12:09:57,344622,4,344346,68869292
71 | patient_063,male,81,record_069,2017-12-26,2017-12-26T13:57:09,2017-12-29T13:57:08,259199,3,259197,51839400
72 | patient_064,male,60,record_070,2009-03-07,2009-03-07T16:01:48,2009-03-08T16:01:38,86390,1,86390,17278000
73 | patient_064,male,66,record_071,2014-09-09,2014-09-09T11:59:54,2014-09-12T11:59:53,259199,3,259197,51839400
74 | patient_065,female,61,record_072,2015-09-25,2015-09-25T15:41:07,2015-09-29T15:41:06,345599,4,342112,68422592
75 | patient_066,male,77,record_073,2010-10-26,2010-10-26T10:02:06,2010-10-27T10:02:05,86399,1,83579,16715991
76 | patient_067,male,72,record_074,2011-12-11,2011-12-11T11:08:09,2011-12-13T11:08:08,172799,2,172798,34559600
77 | patient_068,female,94,record_075,2015-12-19,2015-12-19T14:52:18,2015-12-20T11:44:58,75160,1,73619,14723862
78 | patient_069,male,84,record_076,2009-06-07,2009-06-07T10:58:14,2009-06-08T10:58:05,86391,1,86391,17278200
79 | patient_070,male,69,record_077,2008-01-13,2008-01-13T08:48:02,2008-01-14T08:47:56,86394,1,86394,17278800
80 | patient_071,male,87,record_078,2014-06-25,2014-06-25T15:19:29,2014-06-26T15:19:28,86399,1,76635,15327055
81 | patient_072,female,83,record_079,2008-01-01,2008-01-01T13:00:42,2008-01-02T13:00:41,86399,1,79745,15949118
82 | patient_073,female,83,record_080,2011-10-30,2011-10-30T11:46:22,2011-10-31T09:54:16,79674,1,77044,15408949
83 | patient_074,male,78,record_081,2017-02-05,2017-02-05T08:59:06,2017-02-06T08:59:05,86399,1,83224,16644979
84 | patient_075,male,68,record_082,2013-12-07,2013-12-07T15:07:09,2013-12-08T14:49:58,85369,1,85192,17038407
85 | patient_076,male,69,record_083,2015-04-06,2015-04-06T15:00:19,2015-04-08T14:56:55,172596,2,172539,34507817
86 | patient_077,female,93,record_084,2008-03-22,2008-03-22T15:12:35,2008-03-24T15:12:34,172799,2,100456,20091273
87 | patient_078,male,61,record_085,2012-08-27,2012-08-27T10:08:17,2012-08-30T10:08:16,259199,3,258803,51760788
88 | patient_079,female,70,record_086,2009-07-25,2009-07-25T00:09:11,2009-07-28T05:34:12,278701,4,278659,55731937
89 | patient_080,male,67,record_087,2014-03-13,2014-03-13T11:02:34,2014-03-17T11:02:33,345599,4,345596,69119200
90 | patient_080,male,69,record_088,2016-01-28,2016-01-28T10:19:08,2016-01-31T10:19:07,259199,3,259197,51839400
91 | patient_081,female,91,record_089,2015-01-22,2015-01-22T11:06:01,2015-01-23T11:06:00,86399,1,86399,17279800
92 | patient_082,male,85,record_090,2010-04-26,2010-04-26T09:36:59,2010-04-27T09:36:49,86390,1,86390,17278000
93 | patient_082,male,87,record_091,2011-07-25,2011-07-25T16:31:11,2011-07-29T15:28:08,341817,4,341779,68355956
94 | patient_083,male,63,record_092,2013-06-29,2013-06-29T14:38:42,2013-07-03T14:38:27,345585,4,345596,69119200
95 | patient_084,male,61,record_093,2016-07-24,2016-07-24T15:05:47,2016-07-25T15:05:36,86389,1,86389,17277800
96 | patient_085,male,66,record_094,2016-01-10,2016-01-10T14:21:14,2016-01-11T14:21:13,86399,1,86399,17279800
97 | patient_086,male,60,record_095,2011-11-12,2011-11-12T10:34:39,2011-11-13T20:39:53,122714,2,122469,24493807
98 | patient_087,male,68,record_096,2010-01-24,2010-01-24T09:50:25,2010-01-25T09:50:24,86399,1,86399,17279800
99 | patient_088,female,79,record_097,2012-01-03,2012-01-03T10:02:09,2012-01-04T10:02:08,86399,1,86399,17279800
100 | patient_089,female,87,record_098,2013-09-28,2013-09-28T16:14:12,2013-09-29T16:14:11,86399,1,86399,17279800
101 | patient_090,female,81,record_099,2012-04-26,2012-04-26T11:11:41,2012-04-28T11:11:40,172799,2,172798,34559600
102 | patient_091,female,69,record_100,2016-09-17,2016-09-17T10:24:25,2016-09-18T10:24:24,86399,1,86399,17279800
103 | patient_092,female,63,record_101,2018-01-04,2018-01-04T03:48:12,2018-01-07T03:48:11,259199,3,259197,51839400
104 | patient_093,female,59,record_102,2015-02-04,2015-02-04T10:44:08,2015-02-05T10:43:57,86389,1,86389,17277800
105 | patient_094,female,78,record_103,2013-11-11,2013-11-11T09:19:34,2013-11-12T09:19:33,86399,1,86399,17279800
106 | patient_095,female,76,record_104,2011-12-24,2011-12-24T11:03:39,2011-12-25T11:03:38,86399,1,86399,17279800
107 | patient_096,female,86,record_105,2012-12-11,2012-12-11T11:38:19,2012-12-12T11:38:13,86394,1,86394,17278800
108 | patient_097,male,67,record_106,2011-12-21,2011-12-21T08:36:27,2011-12-22T08:36:22,86395,1,86395,17279000
109 | patient_098,male,91,record_107,2010-08-13,2010-08-13T08:57:55,2010-08-14T08:57:54,86399,1,85228,17045635
110 | patient_099,female,90,record_108,2016-01-01,2016-01-01T16:41:01,2016-01-02T16:41:00,86399,1,86399,17279800
111 | patient_100,male,70,record_109,2012-05-18,2012-05-18T11:03:03,2012-05-22T11:03:02,345599,4,345596,69119200
112 | patient_101,male,67,record_110,2009-09-13,2009-09-13T10:57:17,2009-09-16T10:57:16,259199,3,259197,51839400
113 | patient_101,male,67,record_111,2009-12-17,2009-12-17T13:37:28,2009-12-20T10:48:46,249078,3,240931,48186260
114 | patient_102,female,90,record_112,2013-01-11,2013-01-11T09:01:29,2013-01-12T09:01:28,86399,1,86399,17279800
115 | patient_103,female,76,record_113,2010-03-16,2010-03-16T13:02:43,2010-03-17T13:02:40,86397,1,86397,17279400
116 | patient_104,male,76,record_114,2012-04-03,2012-04-03T10:03:04,2012-04-04T10:03:03,86399,1,86399,17279800
117 | patient_105,male,78,record_115,2010-01-17,2010-01-17T10:45:15,2010-01-18T10:45:09,86394,1,86394,17278800
118 | patient_106,male,56,record_116,2008-09-28,2008-09-28T14:03:05,2008-10-02T14:02:54,345589,4,345596,69119200
119 | patient_107,female,63,record_117,2012-02-27,2012-02-27T14:34:04,2012-02-28T14:34:03,86399,1,86399,17279800
120 | patient_107,female,64,record_118,2013-02-20,2013-02-20T11:39:39,2013-02-21T11:39:38,86399,1,86399,17279800
121 | patient_108,female,98,record_119,2011-05-27,2011-05-27T09:02:24,2011-05-28T09:02:17,86393,1,86393,17278600
122 | patient_109,male,54,record_120,2010-11-28,2010-11-28T11:13:37,2010-11-30T08:26:55,162798,2,162802,32560566
123 | patient_109,male,55,record_121,2012-04-03,2012-04-03T11:42:11,2012-04-07T11:41:59,345588,4,345596,69119200
124 | patient_110,male,58,record_122,2009-11-19,2009-11-19T15:36:31,2009-11-20T15:36:23,86392,1,86392,17278400
125 | patient_111,male,62,record_123,2009-06-19,2009-06-19T11:20:03,2009-06-20T09:24:31,79468,1,79468,15893600
126 | patient_112,male,66,record_124,2014-05-18,2014-05-18T09:55:44,2014-05-22T09:55:43,345599,4,337048,67409740
127 | patient_113,male,58,record_125,2011-10-02,2011-10-02T11:39:57,2011-10-05T11:39:56,259199,3,259197,51839400
128 | patient_113,male,61,record_126,2014-08-03,2014-08-03T14:42:06,2014-08-04T14:42:00,86394,1,86394,17278800
129 | patient_114,male,78,record_127,2009-04-02,2009-04-02T10:34:22,2009-04-03T08:39:03,79481,1,79481,15896200
130 | patient_114,male,79,record_128,2009-09-23,2009-09-23T14:01:58,2009-09-24T11:54:30,78752,1,78206,15641266
131 | patient_115,female,73,record_129,2011-09-09,2011-09-09T09:50:55,2011-09-10T09:50:54,86399,1,86399,17279800
132 | patient_116,male,89,record_130,2013-09-25,2013-09-25T14:32:27,2013-09-26T14:32:18,86391,1,86391,17278200
133 | patient_117,female,79,record_131,2015-06-09,2015-06-09T11:31:29,2015-06-10T11:31:21,86392,1,86392,17278400
134 | patient_118,female,78,record_132,2008-05-20,2008-05-20T15:10:47,2008-05-21T11:15:33,72286,1,72286,14457200
135 | patient_119,male,60,record_133,2009-09-14,2009-09-14T16:53:12,2009-09-17T16:53:11,259199,3,259197,51839400
136 | patient_120,male,79,record_134,2013-03-15,2013-03-15T12:42:59,2013-03-16T12:42:54,86395,1,86395,17279000
137 | patient_121,male,54,record_135,2011-11-23,2011-11-23T09:46:26,2011-11-24T09:46:20,86394,1,86394,17278800
138 | patient_122,female,62,record_136,2014-02-07,2014-02-07T15:33:05,2014-02-10T12:06:15,246790,3,246800,49360082
139 | patient_123,female,75,record_137,2008-11-04,2008-11-04T09:14:18,2008-11-05T09:09:55,86137,1,83238,16647712
140 | patient_124,male,69,record_138,2007-09-04,2007-09-04T10:27:46,2007-09-05T10:27:37,86391,1,86391,17278200
141 | patient_125,female,66,record_139,2016-08-14,2016-08-14T11:32:29,2016-08-15T09:37:26,79497,1,79497,15899400
142 | patient_126,male,71,record_140,2011-04-22,2011-04-22T10:33:38,2011-04-23T08:38:15,79477,1,79477,15895400
143 | patient_127,male,82,record_141,2011-10-06,2011-10-06T11:04:12,2011-10-07T11:04:11,86399,1,86399,17279800
144 | patient_128,male,63,record_142,2011-01-26,2011-01-26T09:32:15,2011-01-29T09:32:14,259199,2,124730,24946157
145 | patient_129,male,86,record_143,2013-08-11,2013-08-11T03:33:30,2013-08-13T03:33:29,172799,2,172798,34559600
146 | patient_130,male,78,record_144,2010-04-28,2010-04-28T09:15:03,2010-04-29T09:14:53,86390,1,86390,17278000
147 | patient_131,female,83,record_145,2013-01-10,2013-01-10T16:46:55,2013-01-11T16:46:54,86399,1,86399,17279800
148 | patient_132,female,82,record_146,2012-12-27,2012-12-27T10:43:15,2012-12-28T10:43:14,86399,1,86399,17279800
149 | patient_133,male,86,record_147,2011-05-10,2011-05-10T11:01:51,2011-05-11T11:01:50,86399,1,86399,17279800
150 | patient_134,female,56,record_148,2016-01-01,2016-01-01T10:15:38,2016-01-03T10:15:37,172799,2,172798,34559600
151 | patient_135,male,59,record_149,2015-11-06,2015-11-06T11:14:15,2015-11-07T11:14:14,86399,1,86399,17279800
152 | patient_136,female,51,record_150,2012-11-02,2012-11-02T11:40:03,2012-11-05T13:10:52,264649,4,264590,52918181
153 | patient_137,male,78,record_151,2011-08-30,2011-08-30T12:51:51,2011-08-31T12:39:30,85659,1,85659,17131800
154 | patient_138,female,87,record_152,2014-08-24,2014-08-24T10:02:35,2014-08-25T10:02:34,86399,1,79849,15969917
155 | patient_139,male,82,record_153,2012-09-29,2012-09-29T15:18:56,2012-09-30T15:09:43,85847,1,85847,17169400
156 | patient_140,male,68,record_154,2012-08-16,2012-08-16T10:25:08,2012-08-17T10:25:07,86399,1,77714,15542925
157 | patient_141,female,66,record_155,2010-03-02,2010-03-02T11:27:43,2010-03-04T11:25:34,172671,2,172485,34497040
158 | patient_142,female,75,record_156,2011-12-02,2011-12-02T10:35:27,2011-12-03T10:35:26,86399,1,84042,16808453
159 | patient_143,male,72,record_157,2010-08-28,2010-08-28T17:38:51,2010-08-30T17:38:50,172799,2,172798,34559600
160 | patient_144,female,59,record_158,2002-02-19,2002-02-19T18:01:36,2002-02-20T17:48:08,85592,1,83797,16759546
161 | patient_144,female,71,record_159,2013-09-16,2013-09-16T15:17:18,2013-09-17T15:17:17,86399,1,86399,17279800
162 | patient_145,female,87,record_160,2012-07-26,2012-07-26T10:05:56,2012-07-28T09:46:40,171644,2,162972,32594541
163 | patient_146,male,81,record_161,2010-08-19,2010-08-19T13:42:11,2010-08-20T13:42:06,86395,1,86395,17279000
164 | patient_147,male,80,record_162,2016-01-06,2016-01-06T09:32:44,2016-01-07T09:32:43,86399,1,86399,17279800
165 | patient_148,female,82,record_163,2009-04-21,2009-04-21T11:55:03,2009-04-25T11:55:02,345599,4,345596,69119200
166 | patient_149,female,75,record_164,2010-11-03,2010-11-03T14:11:05,2010-11-04T14:10:56,86391,1,86391,17278200
167 | patient_150,male,82,record_165,2010-09-11,2010-09-11T11:20:54,2010-09-13T11:20:53,172799,2,172798,34559600
168 | patient_151,male,43,record_166,2009-10-19,2009-10-19T13:27:13,2009-10-21T08:08:12,153659,2,153390,30678001
169 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------