4. TCI simulation¶
This notebook is a simulation of the TCI (Target-Controlled Infusion) system. The TCI system is a system that controls the infusion of a drug into a patient’s body to maintain a desired concentration of the drug in the blood. It is an open-loop controller that uses a pharmacokinetic model to predict the drug concentration in the blood based on the infusion rate. The system is used in anesthesia to maintain a desired level of anesthesia in the patient.
[33]:
import matplotlib.pyplot as plt
import pandas as pd
from python_anesthesia_simulator import Patient, TCIController, Simulator
import vitaldb as vdb
pd.options.mode.chained_assignment = None
4.1. Definition of the patient model and the TCI system¶
In this cell, we create an instance of the Patient class, which represents the patient model. Then we insert it in the simulator model and activate the tci function for propofol and remifentanil
[34]:
age = 50
height = 170
weight = 70
sex = 0
patient_info = [age, height, weight, sex]
#init the patient simulation
patient = Patient(patient_info, model_propo='Schnider', model_remi='Minto')
simulator = Simulator(
patient,
tci_propo='Effect_site',
tci_remi='Effect_site',
)
4.2. Simulation¶
[35]:
sampling_time = 1 # seconds
N_simu = 5*60 // sampling_time # 10 minutes
propo_target = 4 # ug/ml
remi_target = 3 # ng/ml
for time_step in range(N_simu):
simulator.one_step(input_propo=propo_target, input_remi=remi_target)
4.3. Results¶
[36]:
# plot effect site concentration and control input
plt.subplot(2, 1, 1)
plt.plot(simulator.dataframe['Time']/60, simulator.dataframe['u_propo'], label='Propofol (mg/s)')
plt.plot(simulator.dataframe['Time']/60, simulator.dataframe['u_remi'], label='Remifentanil (ug/s)')
plt.ylabel('Drug rate')
plt.grid()
plt.legend()
plt.subplot(2, 1, 2)
plt.plot(simulator.dataframe['Time']/60, simulator.dataframe['x_propo_4'], label='Propofol (ug/ml)')
plt.plot(simulator.dataframe['Time']/60, simulator.dataframe['x_remi_4'], label='Remifentanil (ng/ml)')
#plot target
plt.plot(simulator.dataframe['Time']/60, [propo_target]*len(simulator.dataframe['Time']), '--', label='Propofol target')
plt.plot(simulator.dataframe['Time']/60, [remi_target]*len(simulator.dataframe['Time']), '--', label='Remifentanil target')
plt.ylabel('Effect site concentration ')
plt.xlabel('Time (min)')
plt.legend()
plt.grid()
plt.show()
4.4. Test on clinical data¶
In this section we compare the output of the TCI algorithm with the clinical data comming from the open source vitalDB database (https://vitaldb.net)
[37]:
#load vital DB data
main_signals = ['BIS/BIS',
'Orchestra/PPF20_RATE', # Propofole rate in ml/h
'Orchestra/PPF20_CT', # Propofol target concentration in ug/ml
'Orchestra/PPF20_CE', # Propofol effect site concentration in ug/ml
'Orchestra/RFTN20_RATE', # Remifentanil rate in ml/h
'Orchestra/RFTN20_CT', # Remifentanil target concentration in ng/ml
'Orchestra/RFTN20_CE', # Remifentanil effect site concentration in ng/ml
]
#all the drugs measurement are from the TCI device
sampling_time = 1 # seconds
case_id = 46
# get the data from the vitaldb
data_vitalDB = vdb.VitalFile(int(case_id), track_names=main_signals).to_pandas(
track_names=main_signals, interval=sampling_time)
start = data_vitalDB[(data_vitalDB['Orchestra/PPF20_RATE'] > 0) | (data_vitalDB['Orchestra/RFTN20_RATE'] > 0)].index[0]
data_vitalDB = data_vitalDB.loc[start:]
data_vitalDB.ffill(inplace=True)
data_vitalDB.fillna(0, inplace=True)
# get patient personnal information
patient_info = pd.read_csv("https://api.vitaldb.net/cases")
patient_info = patient_info[patient_info['caseid'] == case_id]
age = patient_info['age'].values[0]
height = patient_info['height'].values[0]
weight = patient_info['weight'].values[0]
sex = int(patient_info['sex'].values[0] == 'M')
concentration_vdb = 20
[38]:
#plot clinical data
plt.subplot(3, 1, 1)
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/PPF20_CT'], label='Propofol target')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/RFTN20_CT'], label='Remifentanil target')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/PPF20_CE'], label='Propofol effect site')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/RFTN20_CE'], label='Remifentanil effect site')
plt.ylabel('Target concentration')
plt.grid()
plt.legend()
plt.subplot(3, 1, 2)
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/PPF20_RATE'], label='Propofol rate')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/RFTN20_RATE'], label='Remifentanil rate')
plt.ylabel('Drug rate')
plt.legend()
plt.grid()
plt.subplot(3, 1, 3)
plt.plot(data_vitalDB.index/60, data_vitalDB['BIS/BIS'], label='BIS')
plt.ylabel('BIS')
plt.xlabel('Time (min)')
plt.legend()
plt.grid()
plt.show()
Here we create the TCIController class by ourselves, we only use the Simulator class to save data. As we do not use TCI function of simulator class, the inputs of the one_step function of simulator are drug rates.
[39]:
# simulate with the clinical data as input
TCI_propo = TCIController([age, height, weight, sex], drug_name='Propofol', model_used="Marsh_modified", maximum_rate= 500*concentration_vdb/3600)
TCI_remi = TCIController([age, height, weight, sex], drug_name='Remifentanil', model_used="Minto", maximum_rate= 500*concentration_vdb/3600)
Patient_simu_TCI = Patient([age, height, weight, sex], model_propo='Marsh_modified', model_remi='Minto')
simu_tci = Simulator(Patient_simu_TCI)
Patient_simu_VitaldDB = Patient([age, height, weight, sex], model_propo='Marsh_modified', model_remi='Minto')
simu_vdb = Simulator(Patient_simu_VitaldDB)
for i in data_vitalDB.index:
propo_target = data_vitalDB['Orchestra/PPF20_CT'].loc[i] # in ug/ml
remi_target = data_vitalDB['Orchestra/RFTN20_CT'].loc[i] # in ng/ml
data_vitalDB.at[i, 'simulated_propo_rate'] = TCI_propo.one_step(propo_target) /concentration_vdb*3600, #in ml/hr
data_vitalDB.at[i, 'simulated_remi_rate'] = TCI_remi.one_step(remi_target) /concentration_vdb*3600, #in ml/hr
simu_tci.one_step(input_propo=data_vitalDB['simulated_propo_rate'].loc[i]*concentration_vdb/3600, #in mg/s
input_remi=data_vitalDB['simulated_remi_rate'].loc[i]*concentration_vdb/3600) # in µg/s
simu_vdb.one_step(input_propo=data_vitalDB['Orchestra/PPF20_RATE'].loc[i]*concentration_vdb/3600, #in mg/s
input_remi=data_vitalDB['Orchestra/RFTN20_RATE'].loc[i]*concentration_vdb/3600) # in µg/s
[40]:
# plot the data
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/PPF20_RATE'], label='Measured Propofol rate')
plt.plot(data_vitalDB.index/60, data_vitalDB['simulated_propo_rate'], '--', label='Simulated Propofol rate')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/RFTN20_RATE'], label='Measured Remifentanil rate')
plt.plot(data_vitalDB.index/60, data_vitalDB['simulated_remi_rate'], '--', label='Simulated Remifentanil rate')
plt.xlabel('Time (min)')
plt.ylabel('Drug rate')
plt.legend()
plt.grid()
plt.show()
[41]:
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/PPF20_RATE']-data_vitalDB['simulated_propo_rate'], label='difference Propofol rate')
plt.plot(data_vitalDB.index/60, data_vitalDB['Orchestra/RFTN20_RATE']-data_vitalDB['simulated_remi_rate'], label='difference Remifentanil rate')
plt.xlabel('Time (min)')
plt.ylabel('Difference')
plt.legend()
plt.grid()
plt.show()
Here we can observe some difference between the clinical data and the TCI algorithm implemented in PAS. While those differences are significant when looking at the drug rate, the drug concentrations are quite similar.
[42]:
# plot the data
plt.subplot(2, 1, 1)
plt.plot((data_vitalDB.index - data_vitalDB.index[0])/60, data_vitalDB['Orchestra/PPF20_CE'], label='Propofol effect site concentration from vitaldb TCI')
plt.plot(simu_vdb.dataframe['Time']/60, simu_vdb.dataframe['x_propo_4'],'--', label='Simulated Propofol ce from VitalDB inputs')
plt.plot(simu_tci.dataframe['Time']/60, simu_tci.dataframe['x_propo_4'],'--', label='Simulated Propofol ce from TCI inputs')
plt.ylabel('Drug rate')
plt.legend()
plt.grid()
plt.subplot(2, 1, 2)
plt.plot((data_vitalDB.index - data_vitalDB.index[0])/60, data_vitalDB['Orchestra/RFTN20_CE'], label='Remifentanil effect site concentration from vitaldb TCI')
plt.plot(simu_vdb.dataframe['Time']/60, simu_vdb.dataframe['x_remi_4'],'--', label='Simulated Remifentanil ce from VitalDB inputs')
plt.plot(simu_tci.dataframe['Time']/60, simu_tci.dataframe['x_remi_4'],'--', label='Simulated Remifentanil ce from TCI inputs')
plt.ylabel('Drug rate')
plt.xlabel('Time (min)')
plt.legend()
plt.grid()
plt.show()
The difference at the end might come from the forward fill method used to fill the missing values in the clinical data.