openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
ap_analyzer.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: LicenseRef-APL-1.1
3 
19 #include <stdlib.h>
20 #include <string.h>
21 #include <math.h>
22 
23 #include "ap_analyzer.h"
24 #include "basics.h"
25 
26 namespace limpet {
27 
31 using ::opencarp::timer_manager;
32 
33 void shift_vm_trace(double vm, action_potential *AP, timer_manager *tm);
34 bool check_threshold(ap_event *e, timer_manager *tm);
35 bool check_mx_rate(ap_event *e, timer_manager *tm);
36 bool check_mx_val (ap_event *e, timer_manager *tm);
39 
40 
44 
45 
55 int
57 {
58  memset(AP,0,sizeof(action_potential));
59  AP->beat = -1;
60  // define which beat to use for calibration
62  AP->pmat = false;
63  int num_act_events = nActEvents;
64  int num_repol_events = nRepolEvents;
65  int num_ampl_events = nAmplEvents;
66 
67 #define BOGUS 9999
68  // analyze action potentials
69  // find activation marker
70  AP->acts.num_events = num_act_events,
71  AP->acts.events = (ap_event *)calloc(num_act_events,sizeof(ap_event));
72 
73  // set event type
74  ap_event *e = AP->acts.events;
75  e->c.m = X_THRESHOLD;
76  e->c.x_thresh.name = dupstr("LAT_xAPD90");
78  e->c.x_thresh.p_slope = true;
79  // initialize event values
80  e->s = BOGUS;
81  e->t = -10.0;
82  e->trace = AP->vm_trc; // either vm or dvm trace
83 
84  e++;
85  e->c.m = MX_VAL;
86  e->c.x_thresh.name = dupstr("LAT_dVdt");
87  e->c.x_thresh.p_slope = true;
88  // initialize event values
89  e->s = -1e10;
90  e->t = -10.;
91  e->trace = AP->dvm_trc; // find max of dvm trace
92 
93 
94  // find repolarisation marker
95  AP->repols.num_events = num_repol_events,
96  AP->repols.events = (ap_event *)calloc(num_repol_events,sizeof(ap_event));
97  // set event type
98  e = AP->repols.events;
99  e->c.m = X_THRESHOLD;
100  e->c.x_thresh.name = dupstr("REPOL_90");
102  e->c.x_thresh.p_slope = false;
103  // initialize event values
104  e->s = BOGUS;
105  e->t = -10.0;
106  e->trace = AP->vm_trc;
107 
108  e++;
109  e->c.m = X_THRESHOLD;
110  e->c.x_thresh.name = dupstr("REPOL_30");
112  e->c.x_thresh.p_slope = false;
113  // initialize event values
114  e->s = BOGUS;
115  e->t = -10.0;
116  e->trace = AP->vm_trc;
117 
118  // find extremal signal values
119  AP->ampls.num_events = num_ampl_events,
120  AP->ampls.events = (ap_event *)calloc(num_ampl_events,sizeof(ap_event));
121  // set event type
122  e = AP->ampls.events;
123  e->c.m = MX_VAL;
124  e->c.mx_val.name = dupstr("VM_MX");
125  e->c.mx_val.p_slope = true;
126  // initialize event values
127  e->s = -1e10;
128  e->t = -10.0;
129  e->trace = AP->vm_trc;
130 
131  e++;
132  e->c.m = MX_VAL;
133  e->c.mx_val.name = dupstr("VM_MN");
134  e->c.mx_val.p_slope = false;
135  // initialize event values
136  e->s = 1e10;
137  e->t = -10.0;
138  e->trace = AP->vm_trc;
139 
140  if(!get_rank())
141  fprintf(stderr,"Parameter sequence written to action potential statistics file.\n");
142 
143  return 0;
144 }
145 
153 void
155 {
156 
157  if(AP->stats!=NULL)
158  fclose(AP->stats);
159 
160  if(AP->rstats!=NULL)
161  fclose(AP->rstats);
162 
163  if((AP->acts.num_events>0) && (AP->acts.events !=NULL)) {
164  for(int i=0;i<AP->acts.num_events;i++)
165  free(AP->acts.events[i].c.x_thresh.name);
166  free(AP->acts.events);
167  }
168 
169  if((AP->repols.num_events>0) && (AP->repols.events !=NULL)) {
170  for(int i=0;i<AP->repols.num_events;i++)
171  free(AP->repols.events[i].c.x_thresh.name);
172  free(AP->repols.events);
173  }
174 
175  if((AP->ampls.num_events>0) && (AP->ampls.events !=NULL)) {
176  for(int i=0;i<AP->ampls.num_events;i++)
177  free(AP->ampls.events[i].c.x_thresh.name);
178  free(AP->ampls.events);
179  }
180 }
181 
182 
191 void
192 shift_vm_trace(double vm, action_potential *AP, timer_manager *tm)
193 {
194  int vm_hist_len = VM_HIST_LEN;
195  for(int i=1;i<vm_hist_len;i++) {
196  AP->vm_trc [i] = AP->vm_trc [i-1];
197  AP->dvm_trc[i] = AP->dvm_trc[i-1];
198  }
199 
200  AP->vm_trc [0] = vm;
201  AP->dvm_trc[0] = (AP->vm_trc[0]-AP->vm_trc[1])/tm->time_step;
202 }
203 
211 void
213 {
214  double vm_mx = AP->ampls.events[VmMX].s;
215  double vm_mn = AP->ampls.events[VmMN].s;
216  double AP_amplitude = vm_mx-vm_mn;
217  double thresh90 = vm_mn+AP_amplitude*(1.-90./100.);
218  double thresh30 = vm_mn+AP_amplitude*(1.-30./100.);
219 
220  log_msg(NULL, 0, 0,"Measuring AP parameters for calibration." );
221 
222  ap_event *e = AP->acts.events;
223  log_msg(NULL, 0, 0,"Changing activation threshold xAPD90 (%.1f->%.1f).",
224  e[xAPD90].c.x_thresh.threshold,thresh90);
225  e[xAPD90].c.x_thresh.threshold = thresh90;
226 
227  e = AP->repols.events;
228  log_msg(NULL, 0, 0,"Changing repolarisation threshold APD90 (%.1f->%.1f).",
229  e[APD90].c.x_thresh.threshold,thresh90);
230  e[APD90].c.x_thresh.threshold = thresh90;
231 
232  log_msg(NULL, 0, 0,"Changing repolarisation threshold APD30 (%.1f->%.1f).",
233  e[APD30].c.x_thresh.threshold,thresh30);
234  e[APD30].c.x_thresh.threshold = thresh30;
235 }
236 
247 bool
248 check_events(double vm, action_potential *AP, timer_manager *tm)
249 {
250  // add new sample to trace
251  shift_vm_trace(vm,AP,tm);
252 
253  bool act_triggered = false;
254  double prevAct = AP->acts.events->t;
255 
256  for(int i=0; i<AP->acts.num_events; i++) {
257  ap_event *e = AP->acts.events+i;
258  if(e->c.m==X_THRESHOLD)
259  check_threshold(e,tm);
260  else if (e->c.m==MX_RATE)
261  check_mx_rate(e,tm);
262  else if (e->c.m==MX_VAL)
263  check_mx_val(e,tm);
264  else
265  fprintf( stderr, "Not implemented yet.\n" );
266  }
267 
268  for(int i=0; i<AP->repols.num_events; i++) {
269  ap_event *e = AP->repols.events+i;
270  if(e->c.m==X_THRESHOLD)
271  check_threshold(e,tm);
272  else if (e->c.m==MX_RATE)
273  check_mx_rate(e,tm);
274  else
275  fprintf( stderr, "Not implemented yet.\n" );
276  }
277 
278  for(int i=0; i<AP->ampls.num_events; i++) {
279  ap_event *e = AP->ampls.events+i;
280  if (e->c.m==MX_VAL)
281  check_mx_val(e,tm);
282  else
283  fprintf( stderr, "Not implemented yet.\n" );
284  }
285 
286  if(AP->acts.events->trg) {
287 
288  // detect false activation during calibration beats
289  if((AP->beat==AP->calBeat) && (tm->time - prevAct < 5.))
290  return false;
291 
292  AP->APD = AP->repols.events->t-prevAct;
293  AP->DI = tm->time - AP->repols.events->t;
294  AP->triang = AP->repols.events[APD90].t-AP->repols.events[APD30].t;
295 
296  // onset of AP
297  AP->state = AP_STATE;
298  act_triggered = true;
299 
300  AP->beat++;
301  if(AP->beat==AP->calBeat) calibrate_thresholds(AP);
302  if(AP->beat>0) print_AP_stats(AP);
303 
304  AP->DIp = AP->DI;
305 
306  bool is_steady = update_steady_state(AP);
307 
308  log_msg( NULL, 0, 0, "Activation event %s triggered at %.3f ms.",
309  AP->acts.events->c.x_thresh.name, tm->time );
310 
311  // reset min/max search for each AP
312  for(int i=0;i<AP->ampls.num_events;i++) {
313  ap_event *e = AP->ampls.events+i;
314  if(e->c.mx_val.p_slope)
315  e->s = -10e10;
316  else
317  e->s = 10e10;
318  e->t = -10.;
319  }
320  }
321 
322  // entering systolic phase
323  if(AP->acts.events->trg) AP->state = AP_STATE;
324 
325  // entering diastolic phase
326  if(AP->repols.events->trg) AP->state = DI_STATE;
327 
328  return act_triggered;
329 }
330 
339 bool
341 {
342  static int init = 0;
343  bool is_stedy = false;
344 
345  #define NUM_STEADY_APs 5
346  #define SS_PERC_ERR 0.01
347  if(!init) {
348  AP->ss.num_APs = NUM_STEADY_APs;
349  AP->ss.num_params = 5;
350  AP->ss.cnt = 0;
351  AP->ss.c = (float *)calloc(AP->ss.num_params,sizeof(float));
352  AP->ss.p = (float *)calloc(AP->ss.num_params,sizeof(float));
353  init = 1;
354  }
355 
356  // first we update current
357  AP->ss.c[0] = AP->APD;
358  AP->ss.c[1] = AP->DI;
359  AP->ss.c[2] = AP->triang;
360  AP->ss.c[3] = AP->ampls.events[0].s; // maximum Vm over AP
361  AP->ss.c[4] = AP->ampls.events[1].s; // minimum Vm over AP
362 
363  int d = 0;
364  for(int i=0;i<AP->ss.num_params;i++) {
365  float r_err = fabs(AP->ss.c[i]-AP->ss.p[i])/AP->ss.c[i]*100.;
366  if(r_err>SS_PERC_ERR) d++;
367  AP->ss.p[i] = AP->ss.c[i];
368  }
369  if(!d) AP->ss.cnt++;
370  else AP->ss.cnt = 0;
371 
372  return check_steady_state(&AP->ss);
373 }
374 
382 bool
384 {
385  if(ss->cnt>=ss->num_APs)
386  return true;
387  else
388  return false;
389 }
390 
399 bool
400 check_threshold(ap_event *e, timer_manager *tm)
401 {
402  e->trg = false;
403  double *s = e->trace;
404  if(e->c.x_thresh.p_slope) {
405  if(s[0]>=e->c.x_thresh.threshold && s[1]<e->c.x_thresh.threshold) {
406  e->s = e->c.x_thresh.threshold;
407  e->t = tm->time + (e->c.x_thresh.threshold-s[1])/(s[0]-s[1])*tm->time_step;
408  e->trg = true;
409  }
410  }
411  else {
412  if(s[0]<=e->c.x_thresh.threshold && s[1]>e->c.x_thresh.threshold) {
413  e->s = e->c.x_thresh.threshold;
414  e->t = tm->time - (e->c.x_thresh.threshold-s[1])/(s[0]-s[1])*tm->time_step;
415  e->trg = true;
416  }
417  }
418  return e->trg;
419 }
420 
429 bool
430 check_mx_rate(ap_event *e, timer_manager *tm)
431 {
432  e->trg = false;
433  double *s = e->trace;
434  if(e->c.mx_rate.p_slope) {
435  if(s[0]>=e->s) {
436  e->s = s[0];
437  e->t = tm->time - tm->time_step*0.5;
438  e->trg = true;
439  }
440  }
441  else {
442  if(s[0]<e->s) {
443  e->s = s[0];
444  e->t = tm->time - tm->time_step*0.5;
445  e->trg = true;
446  }
447  }
448  return e->trg;
449 }
450 
459 bool
460 check_mx_val(ap_event *e, timer_manager* tm)
461 {
462  e->trg = false;
463  double *s = e->trace;
464  if(e->c.mx_val.p_slope) {
465  if(e->s<s[0]) { e->s = s[0]; e->t = tm->time; }
466  }
467  else
468  if(e->s>s[0]) { e->s = s[0]; e->t = tm->time; }
469 
470  return e->trg;
471 }
472 
482 void
484 {
485  if(get_rank()) return;
486 
487  double vm_mx = AP->ampls.events[0].s;
488  double vm_mn = AP->ampls.events[1].s;
489  double act_tm = AP->acts.events[0].t;
490  bool is_steady = AP->ss.cnt>=AP->ss.num_APs;
491  fprintf(AP->stats,"%d %1s %1d %.2f %.2f %.2f %.2f %.2f %.2f %.3f\n",
492  AP->beat,AP->pmat?"P":"*",is_steady,AP->APD,AP->DI,AP->DIp,
493  AP->triang,vm_mx, vm_mn, act_tm);
494 
495  // running restitution protocol?
496  if(AP->rstats!=NULL && AP->pmat)
497  fprintf(AP->rstats,"%d %1s %1d %.2f %.2f %.2f %.2f %.2f %.2f %.3f\n",
498  AP->beat,AP->pmat?"P":"*",is_steady,AP->APD,AP->DI,AP->DIp,
499  AP->triang,vm_mx, vm_mn, act_tm);
500 }
501 
510 void
512 {
513  if(get_rank()) return;
514  fprintf(outbuf, "# Beat Prematurity(P||*) steady_state ");
515  fprintf(outbuf, "APD(n) DI(n) DI(n-1) Triangulation VmMax VmMin tAct\n" );
516 }
517 
518 } // namespace limpet
#define SS_PERC_ERR
#define NUM_STEADY_APs
#define BOGUS
#define CALIBRATION_BEAT
Definition: ap_analyzer.h:14
#define def_APD90_thresh
Definition: ap_analyzer.h:17
#define def_APD30_thresh
Definition: ap_analyzer.h:18
#define VM_HIST_LEN
Definition: ap_analyzer.h:72
Basic utility structs and functions, mostly IO related.
#define log_msg(F, L, O,...)
Definition: filament.h:8
bool check_steady_state(steady_state_ap *ss)
Definition: ap_analyzer.cc:383
enum limpet::_ampl_events ampl_events
@ DI_STATE
Definition: ap_analyzer.h:70
@ AP_STATE
Definition: ap_analyzer.h:70
@ nActEvents
Definition: ap_analyzer.cc:41
int initialize_AP_analysis(action_potential *AP)
Definition: ap_analyzer.cc:56
bool check_mx_rate(ap_event *e, timer_manager *tm)
Definition: ap_analyzer.cc:430
void print_AP_stats_header(action_potential *AP, FILE *outbuf)
Definition: ap_analyzer.cc:511
void cleanup_AP_analysis(action_potential *AP)
Definition: ap_analyzer.cc:154
enum limpet::_repol_events repol_events
@ nAmplEvents
Definition: ap_analyzer.cc:43
bool check_threshold(ap_event *e, timer_manager *tm)
Definition: ap_analyzer.cc:400
void calibrate_thresholds(action_potential *AP)
Definition: ap_analyzer.cc:212
enum limpet::_act_events act_events
@ nRepolEvents
Definition: ap_analyzer.cc:42
bool update_steady_state(action_potential *AP)
Definition: ap_analyzer.cc:340
@ MX_RATE
Definition: ap_analyzer.h:20
@ X_THRESHOLD
Definition: ap_analyzer.h:20
@ MX_VAL
Definition: ap_analyzer.h:20
void print_AP_stats(action_potential *AP)
Definition: ap_analyzer.cc:483
bool check_events(double vm, action_potential *AP, timer_manager *tm)
Definition: ap_analyzer.cc:248
void shift_vm_trace(double vm, action_potential *AP, timer_manager *tm)
Definition: ap_analyzer.cc:192
bool check_mx_val(ap_event *e, timer_manager *tm)
Definition: ap_analyzer.cc:460
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:269
char * dupstr(const char *old_str)
Definition: basics.cc:29
ap_events ampls
amplitude events (mx or mn found in a signal)
Definition: ap_analyzer.h:79
int calBeat
beat number used to calibrate thresholds
Definition: ap_analyzer.h:75
steady_state_ap ss
steady state detection
Definition: ap_analyzer.h:87
float APD
standard APD as measured by APD90
Definition: ap_analyzer.h:80
float DI
BCL-APD90.
Definition: ap_analyzer.h:81
bool pmat
indiacte premature AP
Definition: ap_analyzer.h:76
int beat
beat counter
Definition: ap_analyzer.h:74
FILE * rstats
output restitution statistics only
Definition: ap_analyzer.h:89
ap_events repols
repolarization events
Definition: ap_analyzer.h:78
FILE * stats
output statistics for each AP
Definition: ap_analyzer.h:88
double dvm_trc[VM_HIST_LEN]
Definition: ap_analyzer.h:86
float triang
AP triangulation, APD90-APD30.
Definition: ap_analyzer.h:82
float DIp
DI of the previous AP.
Definition: ap_analyzer.h:83
double vm_trc[VM_HIST_LEN]
Definition: ap_analyzer.h:85
ap_events acts
activation events
Definition: ap_analyzer.h:77
ap_states state
current state of AP, ongoing AP or diastole
Definition: ap_analyzer.h:84
double s
value of signal when event occurred
Definition: ap_analyzer.h:50
event_class c
Definition: ap_analyzer.h:53
double t
time when event was observed
Definition: ap_analyzer.h:51
double * trace
short trace of signal to analyze
Definition: ap_analyzer.h:52
ap_event * events
Definition: ap_analyzer.h:59
char * name
name of event
Definition: ap_analyzer.h:38
bool p_slope
toggle max min search
Definition: ap_analyzer.h:39
float * p
array of parameters at previous AP
Definition: ap_analyzer.h:67
int cnt
count number of beats in steady state
Definition: ap_analyzer.h:65
int num_params
number of parameters we use to decide steady state
Definition: ap_analyzer.h:64
float * c
array of parameters at current AP
Definition: ap_analyzer.h:66
int num_APs
minimum number of APs for steady state
Definition: ap_analyzer.h:63
event_x_threshold x_thresh
Definition: ap_analyzer.h:44
event_mx_val mx_val
Definition: ap_analyzer.h:46
event_mx_rate mx_rate
Definition: ap_analyzer.h:45