iints-sdk-python35 0.0.18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iints/__init__.py +183 -0
- iints/analysis/__init__.py +12 -0
- iints/analysis/algorithm_xray.py +387 -0
- iints/analysis/baseline.py +92 -0
- iints/analysis/clinical_benchmark.py +198 -0
- iints/analysis/clinical_metrics.py +551 -0
- iints/analysis/clinical_tir_analyzer.py +136 -0
- iints/analysis/diabetes_metrics.py +43 -0
- iints/analysis/edge_efficiency.py +33 -0
- iints/analysis/edge_performance_monitor.py +315 -0
- iints/analysis/explainability.py +94 -0
- iints/analysis/explainable_ai.py +232 -0
- iints/analysis/hardware_benchmark.py +221 -0
- iints/analysis/metrics.py +117 -0
- iints/analysis/population_report.py +188 -0
- iints/analysis/reporting.py +345 -0
- iints/analysis/safety_index.py +311 -0
- iints/analysis/sensor_filtering.py +54 -0
- iints/analysis/validator.py +273 -0
- iints/api/__init__.py +0 -0
- iints/api/base_algorithm.py +307 -0
- iints/api/registry.py +103 -0
- iints/api/template_algorithm.py +195 -0
- iints/assets/iints_logo.png +0 -0
- iints/cli/__init__.py +0 -0
- iints/cli/cli.py +2598 -0
- iints/core/__init__.py +1 -0
- iints/core/algorithms/__init__.py +0 -0
- iints/core/algorithms/battle_runner.py +138 -0
- iints/core/algorithms/correction_bolus.py +95 -0
- iints/core/algorithms/discovery.py +92 -0
- iints/core/algorithms/fixed_basal_bolus.py +58 -0
- iints/core/algorithms/hybrid_algorithm.py +92 -0
- iints/core/algorithms/lstm_algorithm.py +138 -0
- iints/core/algorithms/mock_algorithms.py +162 -0
- iints/core/algorithms/pid_controller.py +88 -0
- iints/core/algorithms/standard_pump_algo.py +64 -0
- iints/core/device.py +0 -0
- iints/core/device_manager.py +64 -0
- iints/core/devices/__init__.py +3 -0
- iints/core/devices/models.py +160 -0
- iints/core/patient/__init__.py +9 -0
- iints/core/patient/bergman_model.py +341 -0
- iints/core/patient/models.py +285 -0
- iints/core/patient/patient_factory.py +117 -0
- iints/core/patient/profile.py +41 -0
- iints/core/safety/__init__.py +12 -0
- iints/core/safety/config.py +37 -0
- iints/core/safety/input_validator.py +95 -0
- iints/core/safety/supervisor.py +39 -0
- iints/core/simulation/__init__.py +0 -0
- iints/core/simulation/scenario_parser.py +61 -0
- iints/core/simulator.py +874 -0
- iints/core/supervisor.py +367 -0
- iints/data/__init__.py +53 -0
- iints/data/adapter.py +142 -0
- iints/data/column_mapper.py +398 -0
- iints/data/datasets.json +132 -0
- iints/data/demo/__init__.py +1 -0
- iints/data/demo/demo_cgm.csv +289 -0
- iints/data/importer.py +275 -0
- iints/data/ingestor.py +162 -0
- iints/data/nightscout.py +128 -0
- iints/data/quality_checker.py +550 -0
- iints/data/registry.py +166 -0
- iints/data/tidepool.py +38 -0
- iints/data/universal_parser.py +813 -0
- iints/data/virtual_patients/clinic_safe_baseline.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_midnight.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_pizza.yaml +9 -0
- iints/data/virtual_patients/clinic_safe_stress_meal.yaml +9 -0
- iints/data/virtual_patients/default_patient.yaml +11 -0
- iints/data/virtual_patients/patient_559_config.yaml +11 -0
- iints/emulation/__init__.py +80 -0
- iints/emulation/legacy_base.py +414 -0
- iints/emulation/medtronic_780g.py +337 -0
- iints/emulation/omnipod_5.py +367 -0
- iints/emulation/tandem_controliq.py +393 -0
- iints/highlevel.py +451 -0
- iints/learning/__init__.py +3 -0
- iints/learning/autonomous_optimizer.py +194 -0
- iints/learning/learning_system.py +122 -0
- iints/metrics.py +34 -0
- iints/population/__init__.py +11 -0
- iints/population/generator.py +131 -0
- iints/population/runner.py +327 -0
- iints/presets/__init__.py +28 -0
- iints/presets/presets.json +114 -0
- iints/research/__init__.py +30 -0
- iints/research/config.py +68 -0
- iints/research/dataset.py +319 -0
- iints/research/losses.py +73 -0
- iints/research/predictor.py +329 -0
- iints/scenarios/__init__.py +3 -0
- iints/scenarios/generator.py +92 -0
- iints/templates/__init__.py +0 -0
- iints/templates/default_algorithm.py +91 -0
- iints/templates/scenarios/__init__.py +0 -0
- iints/templates/scenarios/chaos_insulin_stacking.json +29 -0
- iints/templates/scenarios/chaos_runaway_ai.json +25 -0
- iints/templates/scenarios/example_scenario.json +35 -0
- iints/templates/scenarios/exercise_stress.json +30 -0
- iints/utils/__init__.py +3 -0
- iints/utils/plotting.py +50 -0
- iints/utils/run_io.py +152 -0
- iints/validation/__init__.py +133 -0
- iints/validation/schemas.py +94 -0
- iints/visualization/__init__.py +34 -0
- iints/visualization/cockpit.py +691 -0
- iints/visualization/uncertainty_cloud.py +612 -0
- iints_sdk_python35-0.0.18.dist-info/METADATA +225 -0
- iints_sdk_python35-0.0.18.dist-info/RECORD +118 -0
- iints_sdk_python35-0.0.18.dist-info/WHEEL +5 -0
- iints_sdk_python35-0.0.18.dist-info/entry_points.txt +10 -0
- iints_sdk_python35-0.0.18.dist-info/licenses/LICENSE +28 -0
- iints_sdk_python35-0.0.18.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Uncertainty Cloud Visualizer - IINTS-AF
|
|
4
|
+
Creates visualization of AI confidence as shadow around glucose predictions.
|
|
5
|
+
|
|
6
|
+
The Uncertainty Cloud provides:
|
|
7
|
+
- Visual representation of AI confidence
|
|
8
|
+
- Confidence intervals around glucose predictions
|
|
9
|
+
- Color-coded uncertainty levels
|
|
10
|
+
- "Why" annotations for key decisions
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pandas as pd
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
import matplotlib.patches as mpatches
|
|
17
|
+
from matplotlib.collections import PatchCollection
|
|
18
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class UncertaintyData:
|
|
25
|
+
"""Data structure for uncertainty visualization"""
|
|
26
|
+
timestamps: np.ndarray
|
|
27
|
+
glucose_values: np.ndarray
|
|
28
|
+
predictions: np.ndarray
|
|
29
|
+
lower_bounds: np.ndarray
|
|
30
|
+
upper_bounds: np.ndarray
|
|
31
|
+
confidence_scores: np.ndarray # 0.0 to 1.0
|
|
32
|
+
decision_points: Optional[List[Dict]] = None
|
|
33
|
+
|
|
34
|
+
def __post_init__(self):
|
|
35
|
+
if self.decision_points is None:
|
|
36
|
+
self.decision_points = []
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class VisualizationConfig:
|
|
41
|
+
"""Configuration for uncertainty cloud visualization"""
|
|
42
|
+
# Figure settings
|
|
43
|
+
figure_size: Tuple[int, int] = (14, 8)
|
|
44
|
+
dpi: int = 150
|
|
45
|
+
style: str = 'default'
|
|
46
|
+
|
|
47
|
+
# Glucose target zones
|
|
48
|
+
target_low: float = 70.0
|
|
49
|
+
target_high: float = 180.0
|
|
50
|
+
tight_low: float = 70.0
|
|
51
|
+
tight_high: float = 140.0
|
|
52
|
+
|
|
53
|
+
# Colors
|
|
54
|
+
glucose_color: str = '#2196F3'
|
|
55
|
+
prediction_color: str = '#4CAF50'
|
|
56
|
+
uncertainty_color: str = '#81C784' # Light green
|
|
57
|
+
cloud_alpha: float = 0.3
|
|
58
|
+
|
|
59
|
+
# Thresholds for coloring
|
|
60
|
+
critical_low: float = 54.0
|
|
61
|
+
critical_high: float = 250.0
|
|
62
|
+
|
|
63
|
+
# Annotation settings
|
|
64
|
+
show_annotations: bool = True
|
|
65
|
+
annotation_fontsize: int = 8
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class UncertaintyCloud:
|
|
69
|
+
"""
|
|
70
|
+
Creates uncertainty cloud visualizations for glucose predictions.
|
|
71
|
+
|
|
72
|
+
The cloud represents AI confidence through:
|
|
73
|
+
1. Semi-transparent shaded regions showing prediction bounds
|
|
74
|
+
2. Color intensity based on confidence score
|
|
75
|
+
3. Annotation markers for key decision points
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
cloud = UncertaintyCloud()
|
|
79
|
+
|
|
80
|
+
# Create data
|
|
81
|
+
data = UncertaintyData(
|
|
82
|
+
timestamps=np.arange(0, 480, 5),
|
|
83
|
+
glucose_values=glucose,
|
|
84
|
+
predictions=predictions,
|
|
85
|
+
lower_bounds=lower,
|
|
86
|
+
upper_bounds=upper,
|
|
87
|
+
confidence_scores=confidence
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Generate plot
|
|
91
|
+
fig = cloud.plot(data)
|
|
92
|
+
plt.savefig("uncertainty_cloud.png")
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, config: Optional[VisualizationConfig] = None):
|
|
96
|
+
"""
|
|
97
|
+
Initialize uncertainty cloud visualizer.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
config: Visualization configuration (uses default if None)
|
|
101
|
+
"""
|
|
102
|
+
self.config = config or VisualizationConfig()
|
|
103
|
+
self.style_applied = False
|
|
104
|
+
|
|
105
|
+
def _apply_style(self):
|
|
106
|
+
"""Apply matplotlib style settings"""
|
|
107
|
+
if not self.style_applied:
|
|
108
|
+
plt.style.use(self.config.style)
|
|
109
|
+
self.style_applied = True
|
|
110
|
+
|
|
111
|
+
def _create_target_zones(self, ax: plt.Axes, time_range: Tuple[float, float]):
|
|
112
|
+
"""Create shaded regions for target glucose zones"""
|
|
113
|
+
# Very tight range (70-140) - green
|
|
114
|
+
ax.axhspan(
|
|
115
|
+
self.config.tight_low, self.config.tight_high,
|
|
116
|
+
xmin=0, xmax=1,
|
|
117
|
+
alpha=0.15, color='green',
|
|
118
|
+
label='Tight Range (70-140)'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Standard target range (70-180) - yellow
|
|
122
|
+
ax.axhspan(
|
|
123
|
+
self.config.target_low, self.config.target_high,
|
|
124
|
+
xmin=0, xmax=1,
|
|
125
|
+
alpha=0.1, color='orange',
|
|
126
|
+
label='Target Range (70-180)'
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Critical zones
|
|
130
|
+
ax.axhspan(
|
|
131
|
+
0, self.config.critical_low,
|
|
132
|
+
xmin=0, xmax=1,
|
|
133
|
+
alpha=0.3, color='red',
|
|
134
|
+
label='Critical Low (<54)'
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
ax.axhspan(
|
|
138
|
+
self.config.critical_high, 400,
|
|
139
|
+
xmin=0, xmax=1,
|
|
140
|
+
alpha=0.3, color='red',
|
|
141
|
+
label='Critical High (>250)'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def _plot_uncertainty_cloud(self,
|
|
145
|
+
ax: plt.Axes,
|
|
146
|
+
data: UncertaintyData) -> plt.Axes:
|
|
147
|
+
"""Plot the uncertainty cloud as filled region"""
|
|
148
|
+
# Calculate cloud properties based on confidence
|
|
149
|
+
# Higher confidence = narrower cloud, darker color
|
|
150
|
+
|
|
151
|
+
alpha_values = self.config.cloud_alpha * (1 - data.confidence_scores * 0.5)
|
|
152
|
+
|
|
153
|
+
# Plot multiple layers for visual depth
|
|
154
|
+
for i in range(len(data.timestamps) - 1):
|
|
155
|
+
# Main cloud (prediction bounds)
|
|
156
|
+
polygon = plt.Polygon(
|
|
157
|
+
[
|
|
158
|
+
(data.timestamps[i], data.lower_bounds[i]),
|
|
159
|
+
(data.timestamps[i+1], data.lower_bounds[i+1]),
|
|
160
|
+
(data.timestamps[i+1], data.upper_bounds[i+1]),
|
|
161
|
+
(data.timestamps[i], data.upper_bounds[i])
|
|
162
|
+
],
|
|
163
|
+
alpha=alpha_values[i] if i < len(alpha_values) else self.config.cloud_alpha,
|
|
164
|
+
facecolor=self.config.uncertainty_color,
|
|
165
|
+
edgecolor='none'
|
|
166
|
+
)
|
|
167
|
+
ax.add_patch(polygon)
|
|
168
|
+
|
|
169
|
+
# Center line (prediction)
|
|
170
|
+
ax.plot(
|
|
171
|
+
data.timestamps, data.predictions,
|
|
172
|
+
color=self.config.prediction_color,
|
|
173
|
+
linewidth=2,
|
|
174
|
+
linestyle='--',
|
|
175
|
+
label='AI Prediction',
|
|
176
|
+
zorder=3
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return ax
|
|
180
|
+
|
|
181
|
+
def _plot_glucose_line(self,
|
|
182
|
+
ax: plt.Axes,
|
|
183
|
+
data: UncertaintyData,
|
|
184
|
+
time_range: Tuple[float, float]) -> plt.Axes:
|
|
185
|
+
"""Plot actual glucose values"""
|
|
186
|
+
# Color glucose line based on values
|
|
187
|
+
colors = []
|
|
188
|
+
for g in data.glucose_values:
|
|
189
|
+
if g < self.config.critical_low:
|
|
190
|
+
colors.append('darkred')
|
|
191
|
+
elif g < self.config.target_low:
|
|
192
|
+
colors.append('red')
|
|
193
|
+
elif g > self.config.critical_high:
|
|
194
|
+
colors.append('darkred')
|
|
195
|
+
elif g > self.config.target_high:
|
|
196
|
+
colors.append('orange')
|
|
197
|
+
else:
|
|
198
|
+
colors.append(self.config.glucose_color)
|
|
199
|
+
|
|
200
|
+
ax.plot(
|
|
201
|
+
data.timestamps, data.glucose_values,
|
|
202
|
+
color=self.config.glucose_color,
|
|
203
|
+
linewidth=2,
|
|
204
|
+
label='Actual Glucose',
|
|
205
|
+
zorder=4
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Fill below line with gradient
|
|
209
|
+
ax.fill_between(
|
|
210
|
+
data.timestamps,
|
|
211
|
+
0,
|
|
212
|
+
data.glucose_values,
|
|
213
|
+
alpha=0.1,
|
|
214
|
+
color=self.config.glucose_color
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return ax
|
|
218
|
+
|
|
219
|
+
def _add_annotations(self,
|
|
220
|
+
ax: plt.Axes,
|
|
221
|
+
data: UncertaintyData,
|
|
222
|
+
time_range: Tuple[float, float]):
|
|
223
|
+
"""Add decision point annotations"""
|
|
224
|
+
if not self.config.show_annotations or not data.decision_points:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
# Find annotation-worthy points
|
|
228
|
+
for point in data.decision_points:
|
|
229
|
+
timestamp = point.get('timestamp', 0)
|
|
230
|
+
glucose = point.get('glucose', 0)
|
|
231
|
+
reason = point.get('reason', '')
|
|
232
|
+
uncertainty = point.get('uncertainty', 0.5)
|
|
233
|
+
|
|
234
|
+
# Skip if outside time range
|
|
235
|
+
if timestamp < time_range[0] or timestamp > time_range[1]:
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# Only annotate significant events
|
|
239
|
+
if abs(uncertainty) > 0.3 or glucose < 70 or glucose > 200:
|
|
240
|
+
# Create annotation text
|
|
241
|
+
if glucose < 70:
|
|
242
|
+
text = f" Low: {glucose:.0f}"
|
|
243
|
+
elif glucose > 200:
|
|
244
|
+
text = f" High: {glucose:.0f}"
|
|
245
|
+
else:
|
|
246
|
+
text = f"Conf: {1-uncertainty:.0%}"
|
|
247
|
+
|
|
248
|
+
ax.annotate(
|
|
249
|
+
text,
|
|
250
|
+
xy=(timestamp, glucose),
|
|
251
|
+
xytext=(timestamp, glucose + 30),
|
|
252
|
+
fontsize=self.config.annotation_fontsize,
|
|
253
|
+
ha='center',
|
|
254
|
+
arrowprops=dict(
|
|
255
|
+
arrowstyle='->',
|
|
256
|
+
color='gray',
|
|
257
|
+
lw=0.5
|
|
258
|
+
),
|
|
259
|
+
bbox=dict(
|
|
260
|
+
boxstyle='round,pad=0.2',
|
|
261
|
+
facecolor='white',
|
|
262
|
+
alpha=0.8
|
|
263
|
+
),
|
|
264
|
+
zorder=5
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _format_axes(self,
|
|
268
|
+
ax: plt.Axes,
|
|
269
|
+
data: UncertaintyData,
|
|
270
|
+
time_range: Tuple[float, float]):
|
|
271
|
+
"""Format plot axes"""
|
|
272
|
+
# Set limits
|
|
273
|
+
ax.set_xlim(time_range)
|
|
274
|
+
ax.set_ylim(40, 350)
|
|
275
|
+
|
|
276
|
+
# Labels
|
|
277
|
+
ax.set_xlabel('Time (minutes)', fontsize=12)
|
|
278
|
+
ax.set_ylabel('Glucose (mg/dL)', fontsize=12)
|
|
279
|
+
|
|
280
|
+
# Title
|
|
281
|
+
ax.set_title(
|
|
282
|
+
'Glucose Prediction with Uncertainty Cloud',
|
|
283
|
+
fontsize=14,
|
|
284
|
+
fontweight='bold'
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Grid
|
|
288
|
+
ax.grid(True, alpha=0.3, linestyle='--')
|
|
289
|
+
|
|
290
|
+
# Y-axis reference lines
|
|
291
|
+
ax.axhline(y=120, color='gray', linestyle=':', alpha=0.5, linewidth=1)
|
|
292
|
+
ax.axhline(y=180, color='orange', linestyle=':', alpha=0.5, linewidth=1)
|
|
293
|
+
ax.axhline(y=70, color='red', linestyle=':', alpha=0.5, linewidth=1)
|
|
294
|
+
|
|
295
|
+
# X-axis ticks (every 60 minutes)
|
|
296
|
+
ax.set_xticks(np.arange(0, data.timestamps.max() + 60, 60))
|
|
297
|
+
|
|
298
|
+
# Legend
|
|
299
|
+
ax.legend(loc='upper right', fontsize=9)
|
|
300
|
+
|
|
301
|
+
def _add_confidence_legend(self,
|
|
302
|
+
fig: plt.Figure,
|
|
303
|
+
ax: plt.Axes):
|
|
304
|
+
"""Add confidence level legend"""
|
|
305
|
+
# Create legend for uncertainty
|
|
306
|
+
legend_elements = [
|
|
307
|
+
mpatches.Patch(
|
|
308
|
+
color=self.config.uncertainty_color,
|
|
309
|
+
alpha=0.5,
|
|
310
|
+
label='High Confidence (>80%)'
|
|
311
|
+
),
|
|
312
|
+
mpatches.Patch(
|
|
313
|
+
color=self.config.uncertainty_color,
|
|
314
|
+
alpha=0.2,
|
|
315
|
+
label='Low Confidence (<50%)'
|
|
316
|
+
)
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
ax.legend(
|
|
320
|
+
handles=legend_elements,
|
|
321
|
+
loc='lower right',
|
|
322
|
+
fontsize=9,
|
|
323
|
+
title='Uncertainty Level'
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def plot(self,
|
|
327
|
+
data: UncertaintyData,
|
|
328
|
+
time_range: Optional[Tuple[float, float]] = None,
|
|
329
|
+
save_path: Optional[str] = None) -> plt.Figure:
|
|
330
|
+
"""
|
|
331
|
+
Generate uncertainty cloud visualization.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
data: UncertaintyData with all required arrays
|
|
335
|
+
time_range: Optional (min_time, max_time) to display
|
|
336
|
+
save_path: Optional path to save the figure
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
matplotlib Figure object
|
|
340
|
+
"""
|
|
341
|
+
self._apply_style()
|
|
342
|
+
|
|
343
|
+
# Determine time range
|
|
344
|
+
if time_range is None:
|
|
345
|
+
time_range = (data.timestamps.min(), data.timestamps.max())
|
|
346
|
+
|
|
347
|
+
# Create figure
|
|
348
|
+
fig, ax = plt.subplots(
|
|
349
|
+
figsize=self.config.figure_size,
|
|
350
|
+
dpi=self.config.dpi
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Create target zones
|
|
354
|
+
self._create_target_zones(ax, time_range)
|
|
355
|
+
|
|
356
|
+
# Plot uncertainty cloud
|
|
357
|
+
self._plot_uncertainty_cloud(ax, data)
|
|
358
|
+
|
|
359
|
+
# Plot actual glucose
|
|
360
|
+
self._plot_glucose_line(ax, data, time_range)
|
|
361
|
+
|
|
362
|
+
# Add annotations
|
|
363
|
+
self._add_annotations(ax, data, time_range)
|
|
364
|
+
|
|
365
|
+
# Format axes
|
|
366
|
+
self._format_axes(ax, data, time_range)
|
|
367
|
+
|
|
368
|
+
# Add confidence legend
|
|
369
|
+
self._add_confidence_legend(fig, ax)
|
|
370
|
+
|
|
371
|
+
plt.tight_layout()
|
|
372
|
+
|
|
373
|
+
# Save if requested
|
|
374
|
+
if save_path:
|
|
375
|
+
plt.savefig(save_path, dpi=self.config.dpi, bbox_inches='tight')
|
|
376
|
+
print(f" Saved uncertainty cloud to: {save_path}")
|
|
377
|
+
|
|
378
|
+
return fig
|
|
379
|
+
|
|
380
|
+
def plot_comparison(self,
|
|
381
|
+
data_list: List[Tuple[str, UncertaintyData]],
|
|
382
|
+
save_path: Optional[str] = None) -> plt.Figure:
|
|
383
|
+
"""
|
|
384
|
+
Plot multiple uncertainty clouds for comparison.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
data_list: List of (name, data) tuples
|
|
388
|
+
save_path: Optional path to save
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
matplotlib Figure
|
|
392
|
+
"""
|
|
393
|
+
self._apply_style()
|
|
394
|
+
|
|
395
|
+
# Create figure with subplots
|
|
396
|
+
n_plots = len(data_list)
|
|
397
|
+
fig, axes = plt.subplots(
|
|
398
|
+
n_plots, 1,
|
|
399
|
+
figsize=(self.config.figure_size[0],
|
|
400
|
+
self.config.figure_size[1] * n_plots / 2),
|
|
401
|
+
dpi=self.config.dpi
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
if n_plots == 1:
|
|
405
|
+
axes = [axes]
|
|
406
|
+
|
|
407
|
+
time_range = (data_list[0][1].timestamps.min(),
|
|
408
|
+
data_list[0][1].timestamps.max())
|
|
409
|
+
|
|
410
|
+
for idx, (name, data) in enumerate(data_list):
|
|
411
|
+
ax = axes[idx]
|
|
412
|
+
|
|
413
|
+
# Target zones
|
|
414
|
+
self._create_target_zones(ax, time_range)
|
|
415
|
+
|
|
416
|
+
# Uncertainty cloud
|
|
417
|
+
self._plot_uncertainty_cloud(ax, data)
|
|
418
|
+
|
|
419
|
+
# Glucose line
|
|
420
|
+
self._plot_glucose_line(ax, data, time_range)
|
|
421
|
+
|
|
422
|
+
# Format
|
|
423
|
+
ax.set_ylabel(f'{name}\nGlucose (mg/dL)', fontsize=10)
|
|
424
|
+
ax.grid(True, alpha=0.3)
|
|
425
|
+
ax.set_xlim(time_range)
|
|
426
|
+
ax.set_ylim(40, 350)
|
|
427
|
+
|
|
428
|
+
# Calculate average confidence
|
|
429
|
+
avg_conf = data.confidence_scores.mean()
|
|
430
|
+
ax.set_title(f'{name} - Avg Confidence: {avg_conf:.0%}', fontsize=11)
|
|
431
|
+
|
|
432
|
+
axes[-1].set_xlabel('Time (minutes)', fontsize=12)
|
|
433
|
+
|
|
434
|
+
plt.tight_layout()
|
|
435
|
+
|
|
436
|
+
if save_path:
|
|
437
|
+
plt.savefig(save_path, dpi=self.config.dpi, bbox_inches='tight')
|
|
438
|
+
|
|
439
|
+
return fig
|
|
440
|
+
|
|
441
|
+
def create_dashboard_widget(self,
|
|
442
|
+
data: UncertaintyData,
|
|
443
|
+
width: int = 400,
|
|
444
|
+
height: int = 200) -> plt.Figure:
|
|
445
|
+
"""
|
|
446
|
+
Create a compact widget for dashboard display.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
data: UncertaintyData
|
|
450
|
+
width: Widget width in pixels
|
|
451
|
+
height: Widget height in pixels
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
matplotlib Figure
|
|
455
|
+
"""
|
|
456
|
+
self._apply_style()
|
|
457
|
+
|
|
458
|
+
# Create compact figure
|
|
459
|
+
fig, ax = plt.subplots(
|
|
460
|
+
figsize=(width / 100, height / 100),
|
|
461
|
+
dpi=100
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Simplified visualization
|
|
465
|
+
ax.fill_between(
|
|
466
|
+
data.timestamps,
|
|
467
|
+
data.lower_bounds,
|
|
468
|
+
data.upper_bounds,
|
|
469
|
+
alpha=0.3,
|
|
470
|
+
color=self.config.uncertainty_color,
|
|
471
|
+
label='Uncertainty'
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
ax.plot(
|
|
475
|
+
data.timestamps, data.glucose_values,
|
|
476
|
+
color=self.config.glucose_color,
|
|
477
|
+
linewidth=1.5
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
ax.plot(
|
|
481
|
+
data.timestamps, data.predictions,
|
|
482
|
+
color=self.config.prediction_color,
|
|
483
|
+
linewidth=1,
|
|
484
|
+
linestyle='--',
|
|
485
|
+
alpha=0.7
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Target zone
|
|
489
|
+
ax.axhspan(70, 180, alpha=0.1, color='green')
|
|
490
|
+
|
|
491
|
+
# Formatting
|
|
492
|
+
ax.set_xlim(data.timestamps.min(), data.timestamps.max())
|
|
493
|
+
ax.set_ylim(40, 300)
|
|
494
|
+
ax.set_xticks([])
|
|
495
|
+
ax.set_yticks([70, 120, 180, 250])
|
|
496
|
+
ax.set_yticklabels(['70', '120', '180', '250'], fontsize=8)
|
|
497
|
+
ax.grid(True, alpha=0.2)
|
|
498
|
+
|
|
499
|
+
# Confidence indicator
|
|
500
|
+
avg_conf = data.confidence_scores.mean()
|
|
501
|
+
conf_color = 'green' if avg_conf > 0.7 else 'orange' if avg_conf > 0.5 else 'red'
|
|
502
|
+
ax.text(
|
|
503
|
+
0.98, 0.95,
|
|
504
|
+
f'Conf: {avg_conf:.0%}',
|
|
505
|
+
transform=ax.transAxes,
|
|
506
|
+
fontsize=8,
|
|
507
|
+
ha='right',
|
|
508
|
+
color=conf_color,
|
|
509
|
+
fontweight='bold'
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
return fig
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def generate_sample_data() -> UncertaintyData:
|
|
516
|
+
"""Generate sample data for demonstration"""
|
|
517
|
+
np.random.seed(42)
|
|
518
|
+
n_points = 97 # 8 hours at 5-min intervals
|
|
519
|
+
|
|
520
|
+
timestamps = np.arange(0, n_points * 5, 5)
|
|
521
|
+
|
|
522
|
+
# Simulate glucose with realistic patterns
|
|
523
|
+
base_glucose = 120 + 30 * np.sin(timestamps / (24 * 12 / (2 * np.pi)))
|
|
524
|
+
glucose = base_glucose + np.random.normal(0, 10, n_points)
|
|
525
|
+
glucose = np.clip(glucose, 40, 350)
|
|
526
|
+
|
|
527
|
+
# Predictions (slightly ahead of actual)
|
|
528
|
+
predictions = np.roll(glucose, 1)
|
|
529
|
+
predictions[0] = glucose[0]
|
|
530
|
+
predictions = predictions + np.random.normal(0, 5, n_points)
|
|
531
|
+
|
|
532
|
+
# Confidence scores (lower during meals/changes)
|
|
533
|
+
confidence = 0.9 - 0.3 * np.abs(np.gradient(glucose)) / 10
|
|
534
|
+
confidence = np.clip(confidence, 0.4, 0.95)
|
|
535
|
+
|
|
536
|
+
# Uncertainty bounds
|
|
537
|
+
uncertainty = 1 - confidence
|
|
538
|
+
lower_bounds = predictions - 20 * (1 + uncertainty)
|
|
539
|
+
upper_bounds = predictions + 20 * (1 + uncertainty)
|
|
540
|
+
lower_bounds = np.clip(lower_bounds, 20, 350)
|
|
541
|
+
upper_bounds = np.clip(upper_bounds, 20, 350)
|
|
542
|
+
|
|
543
|
+
# Decision points
|
|
544
|
+
decision_points = [
|
|
545
|
+
{'timestamp': 100, 'glucose': 170, 'reason': 'High glucose correction', 'uncertainty': 0.2},
|
|
546
|
+
{'timestamp': 250, 'glucose': 65, 'reason': 'Low glucose detected', 'uncertainty': 0.1},
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
return UncertaintyData(
|
|
550
|
+
timestamps=timestamps,
|
|
551
|
+
glucose_values=glucose,
|
|
552
|
+
predictions=predictions,
|
|
553
|
+
lower_bounds=lower_bounds,
|
|
554
|
+
upper_bounds=upper_bounds,
|
|
555
|
+
confidence_scores=confidence,
|
|
556
|
+
decision_points=decision_points
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def demo_uncertainty_cloud():
|
|
561
|
+
"""Demonstrate uncertainty cloud visualization"""
|
|
562
|
+
print("=" * 70)
|
|
563
|
+
print("UNCERTAINTY CLOUD VISUALIZATION DEMONSTRATION")
|
|
564
|
+
print("=" * 70)
|
|
565
|
+
|
|
566
|
+
# Generate sample data
|
|
567
|
+
print("\n Generating sample data...")
|
|
568
|
+
data = generate_sample_data()
|
|
569
|
+
|
|
570
|
+
print(f" Data points: {len(data.timestamps)}")
|
|
571
|
+
print(f" Time range: {data.timestamps.min()}-{data.timestamps.max()} minutes")
|
|
572
|
+
print(f" Avg confidence: {data.confidence_scores.mean():.1%}")
|
|
573
|
+
|
|
574
|
+
# Create visualizer
|
|
575
|
+
cloud = UncertaintyCloud()
|
|
576
|
+
|
|
577
|
+
# Generate main plot
|
|
578
|
+
print("\n Generating main uncertainty cloud...")
|
|
579
|
+
fig = cloud.plot(data, save_path="results/visualization/uncertainty_cloud_main.png")
|
|
580
|
+
|
|
581
|
+
# Generate comparison plot
|
|
582
|
+
print("\n Generating comparison visualization...")
|
|
583
|
+
|
|
584
|
+
# Create second dataset (improved algorithm)
|
|
585
|
+
data2 = generate_sample_data()
|
|
586
|
+
data2.predictions = data2.predictions * 0.95 # Better predictions
|
|
587
|
+
data2.confidence_scores = np.clip(data2.confidence_scores + 0.05, 0, 1)
|
|
588
|
+
|
|
589
|
+
comparison_fig = cloud.plot_comparison(
|
|
590
|
+
[("Original Algorithm", data), ("Improved Algorithm", data2)],
|
|
591
|
+
save_path="results/visualization/uncertainty_cloud_comparison.png"
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Generate dashboard widget
|
|
595
|
+
print("\n Generating dashboard widget...")
|
|
596
|
+
widget = cloud.create_dashboard_widget(data)
|
|
597
|
+
widget.savefig("results/visualization/uncertainty_widget.png",
|
|
598
|
+
dpi=100, bbox_inches='tight')
|
|
599
|
+
|
|
600
|
+
print("\n Visualization files saved:")
|
|
601
|
+
print(" - results/visualization/uncertainty_cloud_main.png")
|
|
602
|
+
print(" - results/visualization/uncertainty_cloud_comparison.png")
|
|
603
|
+
print(" - results/visualization/uncertainty_widget.png")
|
|
604
|
+
|
|
605
|
+
print("\n" + "=" * 70)
|
|
606
|
+
print("UNCERTAINTY CLOUD DEMONSTRATION COMPLETE")
|
|
607
|
+
print("=" * 70)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
if __name__ == "__main__":
|
|
611
|
+
demo_uncertainty_cloud()
|
|
612
|
+
|