danielrosehill commited on
Commit
8f6524a
·
1 Parent(s): 16d73d7
Files changed (4) hide show
  1. README.md +2 -4
  2. app.py +25 -120
  3. requirements.txt +0 -1
  4. test_plot.py +24 -0
README.md CHANGED
@@ -12,7 +12,7 @@ short_description: Model split-dose protocols for lisdexamfetamine/Vyvanse
12
 
13
  # 💊 Lisdexamfetamine Split-Dose Modeller
14
 
15
- An interactive pharmacokinetic modeling tool for visualizing and optimizing split-dosing protocols of **lisdexamfetamine** (Vyvanse®).
16
 
17
  ## Overview
18
 
@@ -21,7 +21,6 @@ This tool helps ADHD patients and healthcare providers explore different split-d
21
  ## Features
22
 
23
  - **Manual Protocol Design**: Configure custom dosing schedules with 2-4 doses per day
24
- - **Automatic Optimization**: Find the smoothest concentration curve using differential evolution
25
  - **Visual Feedback**: Real-time visualization of predicted blood concentration curves
26
  - **Downloadable Protocols**: Generate markdown protocols with precise timing and dosing instructions
27
  - **Dose Range**: Support for 30mg to 70mg total daily doses
@@ -52,8 +51,7 @@ The model is based on published PK parameters:
52
  ## Technology
53
 
54
  - **Framework**: Gradio
55
- - **Modeling**: SciPy (one-compartment PK model)
56
- - **Optimization**: Differential evolution algorithm
57
  - **Visualization**: Matplotlib
58
 
59
  ## License
 
12
 
13
  # 💊 Lisdexamfetamine Split-Dose Modeller
14
 
15
+ An interactive pharmacokinetic modeling tool for visualizing split-dosing protocols of **lisdexamfetamine** (Vyvanse®).
16
 
17
  ## Overview
18
 
 
21
  ## Features
22
 
23
  - **Manual Protocol Design**: Configure custom dosing schedules with 2-4 doses per day
 
24
  - **Visual Feedback**: Real-time visualization of predicted blood concentration curves
25
  - **Downloadable Protocols**: Generate markdown protocols with precise timing and dosing instructions
26
  - **Dose Range**: Support for 30mg to 70mg total daily doses
 
51
  ## Technology
52
 
53
  - **Framework**: Gradio
54
+ - **Modeling**: NumPy (one-compartment PK model)
 
55
  - **Visualization**: Matplotlib
56
 
57
  ## License
app.py CHANGED
@@ -2,7 +2,6 @@ import gradio as gr
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
  from datetime import datetime, timedelta
5
- from scipy.optimize import differential_evolution
6
 
7
  # Pharmacokinetic parameters for Lisdexamfetamine/d-amphetamine
8
  # Based on standard adult male parameters
@@ -93,42 +92,7 @@ def create_protocol_markdown(total_dose, doses, dose_times, start_time_str, tota
93
 
94
  return md
95
 
96
- def optimize_protocol(total_dose, num_doses, max_interval=6):
97
- """Find the smoothest dosing curve (minimal peaks and troughs)."""
98
-
99
- def objective(params):
100
- """Minimize variation in concentration (standard deviation)."""
101
- intervals = params[:num_doses-1]
102
- percentages = params[num_doses-1:]
103
-
104
- time, concentration, _, _, _ = simulate_protocol(
105
- total_dose, num_doses, intervals, percentages
106
- )
107
-
108
- # Minimize standard deviation to get smoother curve
109
- # Also penalize if concentration drops too low
110
- std_dev = np.std(concentration)
111
- min_conc = np.min(concentration)
112
-
113
- # Penalty for very low concentrations
114
- penalty = 0
115
- if min_conc < 0.1 * np.max(concentration):
116
- penalty = 1000
117
-
118
- return std_dev + penalty
119
-
120
- # Bounds: intervals (0.5 to max_interval hours), percentages (5% to 60%)
121
- bounds = [(0.5, max_interval)] * (num_doses - 1) # intervals
122
- bounds += [(5, 60)] * num_doses # percentages
123
-
124
- result = differential_evolution(objective, bounds, seed=42, maxiter=100)
125
-
126
- optimal_intervals = result.x[:num_doses-1].tolist()
127
- optimal_percentages = result.x[num_doses-1:].tolist()
128
-
129
- return optimal_intervals, optimal_percentages
130
-
131
- def plot_concentration_curve(time, concentration, therapeutic_threshold=0.2):
132
  """Create matplotlib plot of concentration curve with sub-therapeutic shading."""
133
  fig, ax = plt.subplots(figsize=(10, 6))
134
 
@@ -147,7 +111,27 @@ def plot_concentration_curve(time, concentration, therapeutic_threshold=0.2):
147
  # Add therapeutic threshold line
148
  ax.axhline(y=threshold, color='#FF6B6B', linestyle='--', linewidth=1, alpha=0.7)
149
 
150
- ax.set_xlabel('Time (hours)', fontsize=12)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  ax.set_ylabel('Relative Blood Concentration', fontsize=12)
152
  ax.set_title('Predicted Blood Concentration Over Time', fontsize=14, fontweight='bold')
153
  ax.grid(True, alpha=0.3)
@@ -157,8 +141,9 @@ def plot_concentration_curve(time, concentration, therapeutic_threshold=0.2):
157
 
158
  # Add max marker
159
  max_idx = np.argmax(concentration)
 
160
  ax.plot(time[max_idx], concentration[max_idx], 'ro', markersize=8)
161
- ax.annotate(f'Peak: {time[max_idx]:.1f}h',
162
  xy=(time[max_idx], concentration[max_idx]),
163
  xytext=(10, 10), textcoords='offset points',
164
  bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.7))
@@ -185,7 +170,7 @@ def manual_protocol(total_dose, num_doses, interval1, interval2, interval3,
185
  total_dose, int(num_doses), intervals, percentages, start_time
186
  )
187
 
188
- fig = plot_concentration_curve(time, concentration)
189
  md = create_protocol_markdown(total_dose, doses, dose_times, start_time, total_water)
190
 
191
  # Create dose summary
@@ -195,28 +180,6 @@ def manual_protocol(total_dose, num_doses, interval1, interval2, interval3,
195
 
196
  return fig, md, summary
197
 
198
- def optimize_and_display(total_dose, num_doses, max_interval, start_time, total_water):
199
- """Optimize protocol and display results."""
200
- intervals, percentages = optimize_protocol(total_dose, num_doses, max_interval)
201
-
202
- time, concentration, doses, dose_times, norm_pct = simulate_protocol(
203
- total_dose, num_doses, intervals, percentages, start_time
204
- )
205
-
206
- fig = plot_concentration_curve(time, concentration)
207
- md = create_protocol_markdown(total_dose, doses, dose_times, start_time, total_water)
208
-
209
- # Create optimization results summary
210
- summary = "**Optimized Protocol:**\n\n"
211
- summary += "**Intervals (hours between doses):**\n"
212
- for i, interval in enumerate(intervals):
213
- summary += f"- After dose {i+1}: {interval:.1f} hours\n"
214
- summary += "\n**Dose Breakdown:**\n"
215
- for i, (dose, pct) in enumerate(zip(doses, norm_pct)):
216
- summary += f"- Dose {i+1}: {dose:.1f}mg ({pct:.1f}%)\n"
217
-
218
- return fig, md, summary, *intervals, *percentages
219
-
220
  # Create Gradio interface
221
  with gr.Blocks(title="Lisdexamfetamine Split-Dose Modeller", theme=gr.themes.Soft()) as app:
222
  gr.Markdown("""
@@ -292,61 +255,6 @@ with gr.Blocks(title="Lisdexamfetamine Split-Dose Modeller", theme=gr.themes.Sof
292
  outputs=[download_btn_manual]
293
  )
294
 
295
- with gr.Tab("Optimize for Smoothest Curve"):
296
- gr.Markdown("""
297
- ### Automatic Optimization
298
-
299
- Find the smoothest possible concentration curve by automatically optimizing dose timing and amounts.
300
- """)
301
-
302
- with gr.Row():
303
- total_dose_opt = gr.Dropdown(label="Total Daily Dose (mg)",
304
- choices=[30, 40, 50, 60, 70],
305
- value=70)
306
- num_doses_opt = gr.Slider(label="Number of Doses", minimum=2, maximum=4, step=1, value=2)
307
- max_interval_opt = gr.Slider(label="Maximum Interval Between Doses (hours)",
308
- minimum=2, maximum=8, step=0.5, value=6)
309
-
310
- with gr.Row():
311
- start_time_opt = gr.Textbox(label="First Dose Time (HH:MM)", value="07:00")
312
- total_water_opt = gr.Number(label="Total Water Volume (ml)", value=700, minimum=100, maximum=1000)
313
-
314
- optimize_btn = gr.Button("Find Optimal Protocol", variant="primary")
315
-
316
- with gr.Row():
317
- plot_output_opt = gr.Plot(label="Optimized Concentration Curve")
318
-
319
- with gr.Row():
320
- summary_opt = gr.Markdown()
321
-
322
- # Hidden components to store optimized values
323
- with gr.Row(visible=False):
324
- opt_interval1 = gr.Number()
325
- opt_interval2 = gr.Number()
326
- opt_interval3 = gr.Number()
327
- opt_pct1 = gr.Number()
328
- opt_pct2 = gr.Number()
329
- opt_pct3 = gr.Number()
330
- opt_pct4 = gr.Number()
331
-
332
- with gr.Row():
333
- protocol_output_opt = gr.Textbox(label="Optimized Dosing Protocol", lines=15)
334
- download_btn_opt = gr.DownloadButton(label="Download Protocol")
335
-
336
- optimize_btn.click(
337
- fn=optimize_and_display,
338
- inputs=[total_dose_opt, num_doses_opt, max_interval_opt, start_time_opt, total_water_opt],
339
- outputs=[plot_output_opt, protocol_output_opt, summary_opt,
340
- opt_interval1, opt_interval2, opt_interval3,
341
- opt_pct1, opt_pct2, opt_pct3, opt_pct4]
342
- )
343
-
344
- protocol_output_opt.change(
345
- fn=lambda x: gr.DownloadButton(value=x, visible=True),
346
- inputs=[protocol_output_opt],
347
- outputs=[download_btn_opt]
348
- )
349
-
350
  with gr.Tab("About"):
351
  gr.Markdown("""
352
  ## About This Tool
@@ -388,7 +296,6 @@ with gr.Blocks(title="Lisdexamfetamine Split-Dose Modeller", theme=gr.themes.Sof
388
  This tool is designed to help ADHD patients and their healthcare providers:
389
  - Visualize the effects of different split-dosing strategies
390
  - Understand how dose timing affects blood concentration patterns
391
- - Explore optimization strategies for smoother therapeutic coverage
392
  - Plan and document split-dosing protocols
393
 
394
  ### Technical Details
@@ -398,8 +305,6 @@ with gr.Blocks(title="Lisdexamfetamine Split-Dose Modeller", theme=gr.themes.Sof
398
  - First-order elimination (Ke ≈ 0.069 h⁻¹, from t½ = 10h)
399
  - Linear dose-response relationship
400
 
401
- For optimization, the tool uses differential evolution to minimize concentration variance while maintaining adequate therapeutic levels.
402
-
403
  ---
404
 
405
  **Version**: 1.0
 
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
  from datetime import datetime, timedelta
 
5
 
6
  # Pharmacokinetic parameters for Lisdexamfetamine/d-amphetamine
7
  # Based on standard adult male parameters
 
92
 
93
  return md
94
 
95
+ def plot_concentration_curve(time, concentration, therapeutic_threshold=0.2, start_time_str="07:00"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  """Create matplotlib plot of concentration curve with sub-therapeutic shading."""
97
  fig, ax = plt.subplots(figsize=(10, 6))
98
 
 
111
  # Add therapeutic threshold line
112
  ax.axhline(y=threshold, color='#FF6B6B', linestyle='--', linewidth=1, alpha=0.7)
113
 
114
+ # Parse start time and create custom x-axis labels
115
+ start_time = datetime.strptime(start_time_str, "%H:%M")
116
+
117
+ # Create x-axis tick positions (every 3 hours)
118
+ tick_positions = np.arange(0, 25, 3)
119
+ tick_labels = []
120
+
121
+ for hours_offset in tick_positions:
122
+ clock_time = start_time + timedelta(hours=float(hours_offset))
123
+ if hours_offset == 0:
124
+ # First dose - show only clock time
125
+ label = clock_time.strftime('%H:%M')
126
+ else:
127
+ # Subsequent times - show clock time with relative hours
128
+ label = f"{clock_time.strftime('%H:%M')}\n(+{int(hours_offset)})"
129
+ tick_labels.append(label)
130
+
131
+ ax.set_xticks(tick_positions)
132
+ ax.set_xticklabels(tick_labels, fontsize=9)
133
+
134
+ ax.set_xlabel('Time', fontsize=12)
135
  ax.set_ylabel('Relative Blood Concentration', fontsize=12)
136
  ax.set_title('Predicted Blood Concentration Over Time', fontsize=14, fontweight='bold')
137
  ax.grid(True, alpha=0.3)
 
141
 
142
  # Add max marker
143
  max_idx = np.argmax(concentration)
144
+ peak_time = start_time + timedelta(hours=time[max_idx])
145
  ax.plot(time[max_idx], concentration[max_idx], 'ro', markersize=8)
146
+ ax.annotate(f'Peak: {peak_time.strftime("%H:%M")} (+{time[max_idx]:.1f}h)',
147
  xy=(time[max_idx], concentration[max_idx]),
148
  xytext=(10, 10), textcoords='offset points',
149
  bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.7))
 
170
  total_dose, int(num_doses), intervals, percentages, start_time
171
  )
172
 
173
+ fig = plot_concentration_curve(time, concentration, start_time_str=start_time)
174
  md = create_protocol_markdown(total_dose, doses, dose_times, start_time, total_water)
175
 
176
  # Create dose summary
 
180
 
181
  return fig, md, summary
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  # Create Gradio interface
184
  with gr.Blocks(title="Lisdexamfetamine Split-Dose Modeller", theme=gr.themes.Soft()) as app:
185
  gr.Markdown("""
 
255
  outputs=[download_btn_manual]
256
  )
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  with gr.Tab("About"):
259
  gr.Markdown("""
260
  ## About This Tool
 
296
  This tool is designed to help ADHD patients and their healthcare providers:
297
  - Visualize the effects of different split-dosing strategies
298
  - Understand how dose timing affects blood concentration patterns
 
299
  - Plan and document split-dosing protocols
300
 
301
  ### Technical Details
 
305
  - First-order elimination (Ke ≈ 0.069 h⁻¹, from t½ = 10h)
306
  - Linear dose-response relationship
307
 
 
 
308
  ---
309
 
310
  **Version**: 1.0
requirements.txt CHANGED
@@ -1,4 +1,3 @@
1
  gradio>=5.9.1
2
  numpy>=1.24.0
3
  matplotlib>=3.7.0
4
- scipy>=1.10.0
 
1
  gradio>=5.9.1
2
  numpy>=1.24.0
3
  matplotlib>=3.7.0
 
test_plot.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Quick test to verify the plot displays time labels correctly."""
3
+
4
+ import numpy as np
5
+ from datetime import datetime, timedelta
6
+ from app import plot_concentration_curve, simulate_protocol
7
+
8
+ # Test with a simple 2-dose protocol starting at 07:00
9
+ time, concentration, doses, dose_times, norm_pct = simulate_protocol(
10
+ total_dose=70,
11
+ num_doses=2,
12
+ intervals=[6],
13
+ percentages=[50, 50],
14
+ start_time_str="07:00"
15
+ )
16
+
17
+ # Create the plot
18
+ fig = plot_concentration_curve(time, concentration, start_time_str="07:00")
19
+
20
+ # Save to file for inspection
21
+ fig.savefig('/tmp/test_plot.png', dpi=100, bbox_inches='tight')
22
+ print("✓ Plot created successfully!")
23
+ print(f"✓ Saved to /tmp/test_plot.png")
24
+ print(f"✓ Time labels should show: 07:00, 10:00 (+3), 13:00 (+6), etc.")