Skip to content

Commit a85cef7

Browse files
authored
Merge pull request #40 from lcossu/CGM-abstraction
Abstraction of CGM sensor error model
2 parents f13e057 + 8f39440 commit a85cef7

File tree

17 files changed

+815
-548
lines changed

17 files changed

+815
-548
lines changed

docs/.vuepress/config.js

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,79 @@
1-
import { defineUserConfig } from 'vuepress/cli'
2-
import { viteBundler } from '@vuepress/bundler-vite'
3-
import { hopeTheme } from "vuepress-theme-hope";
1+
import {defineUserConfig} from 'vuepress/cli'
2+
import {viteBundler} from '@vuepress/bundler-vite'
3+
import {hopeTheme} from "vuepress-theme-hope";
44

55
export default defineUserConfig({
6-
lang: 'en-US',
6+
lang: 'en-US',
77

88
title: 'ReplayBG',
99
base: '/py_replay_bg/',
1010
description: 'A digital twin based framework for the development and assessment of new algorithms for type 1 ' +
1111
'diabetes management',
1212

13-
theme: hopeTheme({
14-
logo: 'https://i.postimg.cc/gJn8Sy0X/replay-bg-logo.png',
15-
navbar: ['/', '/documentation/get_started', '/documentation/'],
16-
repo: 'https://github.com/gcappon/py_replay_bg',
17-
docsDir: 'docs/',
18-
docsBranch: 'master',
19-
markdown: {
20-
highlighter: {
21-
type: "shiki",
22-
langs: ['python', 'json', 'md', 'bash', 'diff'],
23-
themes: {
24-
dark: 'catppuccin-mocha',
25-
light: 'catppuccin-latte'
26-
}
27-
},
28-
math: {
29-
type: "mathjax",
30-
},
31-
},
32-
sidebar: [
33-
{
34-
text: 'Get Started',
35-
link: 'documentation/get_started.md'
36-
},
37-
{
38-
text: 'Data Requirements',
39-
link: 'documentation/data_requirements.md'
40-
},
41-
{
42-
text: 'The ReplayBG Object',
43-
link: 'documentation/replaybg_object.md'
44-
},
45-
{
46-
text: 'Choosing Blueprint',
47-
link: 'documentation/choosing_blueprint.md'
48-
},
49-
{
50-
text: 'Twinning Procedure',
51-
link: 'documentation/twinning_procedure.md'
52-
},
53-
{
54-
text: 'Replaying',
55-
link: 'documentation/replaying.md'
56-
},
57-
{
58-
text: 'The _results/_ Folder',
59-
link: 'documentation/results_folder.md'
60-
},
61-
{
62-
text: 'Visualizing Replay Results',
63-
link: 'documentation/visualizing_replay_results.md'
64-
},
65-
{
66-
text: 'Analyzing Replay Results',
67-
link: 'documentation/analyzing_replay_results.md'
68-
}
69-
],
70-
}),
13+
theme: hopeTheme({
14+
logo: 'https://i.postimg.cc/gJn8Sy0X/replay-bg-logo.png',
15+
navbar: ['/', '/documentation/get_started', '/documentation/'],
16+
repo: 'https://github.com/gcappon/py_replay_bg',
17+
docsDir: 'docs/',
18+
docsBranch: 'master',
19+
markdown: {
20+
highlighter: {
21+
type: "shiki",
22+
langs: ['python', 'json', 'md', 'bash', 'diff'],
23+
themes: {
24+
dark: 'catppuccin-mocha',
25+
light: 'catppuccin-latte'
26+
}
27+
},
28+
math: {
29+
type: "mathjax",
30+
},
31+
},
32+
sidebar: [
33+
{
34+
text: 'Get Started',
35+
link: 'documentation/get_started.md'
36+
},
37+
{
38+
text: 'Data Requirements',
39+
link: 'documentation/data_requirements.md'
40+
},
41+
{
42+
text: 'The ReplayBG Object',
43+
link: 'documentation/replaybg_object.md'
44+
},
45+
{
46+
text: 'Choosing Blueprint',
47+
link: 'documentation/choosing_blueprint.md'
48+
},
49+
{
50+
text: 'The CGM error model',
51+
link: 'documentation/cgm_model.md'
52+
},
53+
{
54+
text: 'Twinning Procedure',
55+
link: 'documentation/twinning_procedure.md'
56+
},
57+
{
58+
text: 'Replaying',
59+
link: 'documentation/replaying.md'
60+
},
61+
{
62+
text: 'The _results/_ Folder',
63+
link: 'documentation/results_folder.md'
64+
},
65+
{
66+
text: 'Visualizing Replay Results',
67+
link: 'documentation/visualizing_replay_results.md'
68+
},
69+
{
70+
text: 'Analyzing Replay Results',
71+
link: 'documentation/analyzing_replay_results.md'
72+
}
73+
],
74+
}),
7175

72-
bundler: viteBundler(),
76+
bundler: viteBundler(),
7377

74-
plugins: [],
78+
plugins: [],
7579
})

docs/documentation/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ description: Docs index
1616

1717
### [Choosing Blueprint](./choosing_blueprint.md)
1818

19+
### [The CGM error model](./cgm_model.md)
20+
1921
### [Twinning Procedure](./twinning_procedure.md)
2022

2123
### [Replaying](./replaying.md)

docs/documentation/cgm_model.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
sidebar: heading
3+
---
4+
5+
# The CGM error model
6+
7+
The CGM error model is a key component of the ReplayBG framework, and it is designed to simulate the errors associated with
8+
these devices.
9+
10+
## Model abstract class
11+
12+
The CGM error model is implemented as an abstract class `CGM`, which defines the interface for
13+
all CGM error models. This class can be extended by specific CGM error model implementations.
14+
15+
The constructor of the abstract class defines the following attributes:
16+
17+
- `ts`: The sample time of the cgm sensor in minutes (default: 5).
18+
- `max_lifetime`: The maximum lifetime of the CGM sensor in minutes (default: 1440).
19+
20+
The abstract class defines the following methods:
21+
22+
- `connect_new_cgm(connected_at: int=0)`: Connects a new CGM device, setting the time at which it is connected. Used
23+
internally to reset state across multiple replays.
24+
- `add_offset(to_add: float)`: Adjusts the model's internal offset. Rarely needs overriding.
25+
- `measure(ig: float, past_ig: list[float], t: float)`: Function that provides a CGM measure from the past interstitial
26+
glucose values. It takes as input the interstitial glucose values `ig`, all the past interstitial glucose values
27+
`past_ig`, and the relative time `t` in days since the startup of the sensor.
28+
29+
## Default CGM error model
30+
31+
The default CGM error model available in ReplayBG is the Vettoretti19CGM model as published in Vettoretti et al., Sensors,
32+
2019, which is a factory-calibrated device with a lifespan of 10 days and a sample time of 5 minutes.
33+
34+
Model equations are:
35+
36+
$
37+
\begin{cases}
38+
IG_S(t) = (a_0 + a_1 \cdot t + a_2 \cdot t^2) \cdot IG(t) + b_0 \\
39+
CGM(t) = IG_S(t) + v(t)
40+
\end{cases}
41+
$
42+
43+
with $a_0$, $a_1$, $a_2$, and $b_0$ (min$^{-1}$) model coefficients; and $v(t) \sim N(0, \epsilon_v)$ random white
44+
noise. Particularly, in order to mimic real CGM systems, $CGM(t)$ is saturated between 40 and 400 mg/dL.
45+
46+
## How to choose the CGM error model to use during replays
47+
48+
The replay method of the `ReplayBG` class accepts a `sensor_cgm` parameter, which can be set to the class of the CGM to
49+
be used.
50+
```python
51+
import MyCustomCGM
52+
results = rbg.replay(..., sensor_cgm=MyCustomCGM)
53+
```
54+
If no `sensor_cgm` is provided, the default CGM error model (Vettoretti19CGM) is used.
55+
56+
### Example of use
57+
58+
This example shows how to use a custom CGM error model in a ReplayBG simulation. The example uses a fake CGM model
59+
that doubles the interstitial glucose values for demonstration purposes. The sensor has a maximum lifetime of 10
60+
minutes.
61+
62+
The full example is available in the `example/code/` folder in the `fake_CGM.py` and `replay_map_fakecgm.py` files.
63+
64+
```
65+
class FakeCGM(CGM):
66+
def __init__(self):
67+
super().__init__()
68+
self.max_lifetime = 10
69+
70+
def measure(self, ig: float, past_ig: list[float], t):
71+
return ig * 2
72+
73+
def connect_new_cgm(self, connected_at=0):
74+
super().connect_new_cgm()
75+
print(f"New CGM sensor connected at {connected_at}")
76+
77+
def main():
78+
...
79+
# Replay the twin with the same input data used for twinning
80+
replay_results = rbg.replay(..., sensor_cgm=FakeCGM)
81+
...
82+
```

docs/documentation/choosing_blueprint.md

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,9 @@ $
139139
where $G_{th}$ is the hypoglycemic threshold (set to 60 mg/dl) and $r_1$ (dimensionless) and $r_2$ (dimensionless)
140140
are two model parameters, without direct physiological interpretation, set to 1.44 and 0.81, respectively.
141141

142-
#### CGM sensor error
143-
The subsystem describes the CGM sensor error model of a factory-calibrated CGM sensor with 10-day lifetime.
144-
Model equations are:
145-
146-
$
147-
\begin{cases}
148-
IG_S(t) = (a_0 + a_1 \cdot t + a_2 \cdot t^2) \cdot IG(t) + b_0 \\
149-
CGM(t) = IG_S(t) + v(t)
150-
\end{cases}
151-
$
152-
153-
with $a_0$, $a_1$, $a_2$, and $b_0$ (min$^{-1}$) model coefficients; and $v(t) \sim N(0, \epsilon_v)$ random white
154-
noise. Particularly, in order to mimic real CGM systems, $CGM(t)$ is saturated between 40 and 400 mg/dL.
142+
#### CGM sensor error model
143+
The subsystem describes the CGM sensor error model. For more details on the CGM error model, please refer to the
144+
[CGM Error Model](./cgm_model.md) page.
155145

156146
#### Overall model
157147

@@ -306,18 +296,8 @@ where $G_{th}$ is the hypoglycemic threshold (set to 60 mg/dl) and $r_1$ (dimens
306296
are two model parameters, without direct physiological interpretation, set to 1.44 and 0.81, respectively.
307297

308298
#### CGM sensor error
309-
The subsystem describes the CGM sensor error model of a factory-calibrated CGM sensor with 10-day lifetime.
310-
Model equations are:
311-
312-
$
313-
\begin{cases}
314-
IG_S(t) = (a_0 + a_1 \cdot t + a_2 \cdot t^2) \cdot IG(t) + b_0 \\
315-
CGM(t) = IG_S(t) + v(t)
316-
\end{cases}
317-
$
318-
319-
with $a_0$, $a_1$, $a_2$, and $b_0$ (min$^{-1}$) model coefficients; and $v(t) \sim N(0, \epsilon_v)$ random white
320-
noise. Particularly, in order to mimic real CGM systems, $CGM(t)$ is saturated between 40 and 400 mg/dL.
299+
The subsystem describes the CGM sensor error model. For more details on the CGM error model, please refer to the
300+
[CGM Error Model](./cgm_model.md) page.
321301

322302
#### Overall model
323303

docs/documentation/replaying.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ rbg.replay(data: pd.DataFrame,
4848
save_suffix: str = '',
4949
save_workspace: bool = False,
5050
n_replay: int = 1000,
51-
sensors: list | None = None
51+
sensors: list | None = None,
52+
sensor_cgm: CGM = Vettoretti19CGM,
5253
) -> Dict:
5354
```
5455
### Input parameters
@@ -117,6 +118,7 @@ in the `results/workspaces` folder or not.
117118
replay simulations. Ignored if twinning_method is 'map'.
118119
- `sensors`: , optional, default: `None`: A `list[Sensors]` to be used in each of the replay simulations. Its length
119120
must coincide with the selected `n_replay`. Used when working with intervals. If `None` new sensors will be used.
121+
- `sensor_cgm`, optional, default: `Vettoretti19CGM`: The class of the CGM error model to be used during the replay simulation.
120122

121123
::: tip REMEMBER
122124
The total length of the simulation, `simulation_length`, is defined in minutes and determined by ReplayBG automatically

py_replay_bg.egg-info/SOURCES.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ py_replay_bg.egg-info/SOURCES.txt
99
py_replay_bg.egg-info/dependency_links.txt
1010
py_replay_bg.egg-info/requires.txt
1111
py_replay_bg.egg-info/top_level.txt
12-
py_replay_bg/analyzer/__init__.py
1312
py_replay_bg/data/__init__.py
1413
py_replay_bg/dss/__init__.py
1514
py_replay_bg/dss/default_dss_handlers.py
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import numpy as np
2+
from py_replay_bg.sensors import CGM
3+
4+
5+
class FakeCGM(CGM):
6+
def __init__(self):
7+
super().__init__()
8+
self.max_lifetime = 10 # This sensor only lasts 10 minutes
9+
10+
def measure(self, ig: float, past_ig: list[float], t):
11+
# Fake sensor that just doubles the ig value
12+
# return ig * 2
13+
14+
# Fake sensor that averages the last 10 ig values (10 minutes) plus some noise
15+
return np.nanmean(past_ig[-10:]) + np.random.uniform(-10,
16+
10)
17+
18+
def connect_new_cgm(self, connected_at=0):
19+
super().connect_new_cgm()
20+
print(f"New CGM sensor connected at {connected_at}") # Just to show that this method is called

py_replay_bg/example/code/replay_map.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import numpy as np
33

4+
from py_replay_bg.sensors.Vettoretti19CGM import Vettoretti19CGM
45
from utils import load_test_data, load_patient_info
56

67
from py_replay_bg.py_replay_bg import ReplayBG
@@ -13,7 +14,7 @@
1314

1415
# Set other parameters for twinning
1516
blueprint = 'multi-meal'
16-
save_folder = os.path.join(os.path.abspath(''),'..','..','..')
17+
save_folder = os.path.join(os.path.abspath(''), '..', '..', '..')
1718

1819
# load patient_info
1920
patient_info = load_patient_info()
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import os
2+
import numpy as np
3+
4+
from py_replay_bg.example.code.fake_CGM import FakeCGM
5+
from py_replay_bg.sensors.Vettoretti19CGM import Vettoretti19CGM
6+
from utils import load_test_data, load_patient_info
7+
8+
from py_replay_bg.py_replay_bg import ReplayBG
9+
from py_replay_bg.visualizer import Visualizer
10+
from py_replay_bg.analyzer import Analyzer
11+
12+
# Set verbosity
13+
verbose = True
14+
plot_mode = False
15+
16+
# Set other parameters for twinning
17+
blueprint = 'multi-meal'
18+
save_folder = os.path.join(os.path.abspath(''), '..', '..', '..')
19+
20+
# load patient_info
21+
patient_info = load_patient_info()
22+
p = np.where(patient_info['patient'] == 1)[0][0]
23+
# Set bw and u2ss
24+
bw = float(patient_info.bw.values[p])
25+
26+
# Instantiate ReplayBG
27+
rbg = ReplayBG(blueprint=blueprint, save_folder=save_folder,
28+
yts=5, exercise=False,
29+
seed=1,
30+
verbose=verbose, plot_mode=plot_mode)
31+
32+
# Load data and set save_name
33+
data = load_test_data(day=1)
34+
save_name = 'data_day_' + str(1)
35+
36+
print("Replaying " + save_name)
37+
38+
# Replay the twin with the same input data used for twinning
39+
replay_results = rbg.replay(data=data, bw=bw, save_name=save_name,
40+
twinning_method='map',
41+
save_workspace=True,
42+
save_suffix='_replay_map', sensor_cgm=FakeCGM)
43+
44+
# Visualize and analyze results
45+
Visualizer.plot_replay_results(replay_results, data=data)
46+
analysis = Analyzer.analyze_replay_results(replay_results, data=data)
47+
print('Mean glucose: %.2f mg/dl' % analysis['median']['glucose']['variability']['mean_glucose'])
48+
print('TIR: %.2f %%' % analysis['median']['glucose']['time_in_ranges']['time_in_target'])
49+
print('N Days: %.2f days' % analysis['median']['glucose']['data_quality']['number_days_of_observation'])

0 commit comments

Comments
 (0)