openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
IGBapd.cc
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // openCARP is an open cardiac electrophysiology simulator.
3 //
4 // Copyright (C) 2020 openCARP project
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 // ----------------------------------------------------------------------------
19 
20 
21 /*
22  * compute APD of all nodes in an IGB file
23  * or detect an upstroke
24  *
25  * In detection mode, the program exits with status code 3 when an upstroke is detected.
26  * The node and time of the upstroke are output. If no APs are detected, it outputs nothing
27  * and exits with status 0.
28  */
29 #include <iostream>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <math.h>
33 #include <fstream>
34 #include <set>
35 #include <vector>
36 #include <zlib.h>
37 #include "IGBheader.h"
38 #include "apd_cmdline.h"
39 
41 
42 using namespace std;
43 
44 const float UNCOMPUTED = -1;
45 const float UNSTARTED = -1;
46 const int NO_PRIOR = -1;
47 
50 float
51 lininterp( float y0, float y1, float x0, float x1, float interp )
52 {
53  float m = (y1-y0)/(x1-x0);
54  float b = y1-m*x1;
55 
56  return (interp-b)/m;
57 }
58 
59 
76 bool
77 process_specifier( char *sp, set<int> &nodes, int max )
78 {
79  int inc = 1;
80  if( strchr( sp, ':' ) ){
81  char *colptr = strchr( sp, ':' );
82  int consumed;
83  sscanf( colptr+1, "%d%n", &inc, &consumed );
84  if( !consumed || consumed!=strlen(colptr+1) ) {
85  fprintf( stderr, "Illegal range specified: %s\n", sp );
86  exit(1);
87  }
88  *colptr = '\0';
89  }
90 
91  if( !strcmp( "*", sp ) ) {
92  if( inc==1 ) {
93  nodes.clear();
94  for( int i=0; i<max; i+=inc )
95  nodes.insert(nodes.end(),i);
96  } else {
97  for( int i=0; i<max; i+=inc )
98  nodes.insert(i);
99  }
100  return inc==1;
101  }
102 
103  if( strchr( sp, '-' ) ){
104  int s;
105  char fs[100];
106  int nr = sscanf( sp, "%d-%s", &s, fs );
107  if( nr<2 ) {
108  fprintf( stderr, "Illegal range specified: %s\n", sp );
109  exit(1);
110  }
111 
112  int f;
113  if( !strcmp(fs, "*") )
114  f = max-1;
115  else {
116  int consumed;
117  sscanf( fs, "%d%n", &f, &consumed );
118  if( consumed != strlen(fs) ) {
119  fprintf( stderr, "Illegal range specified: %s\n", sp );
120  exit(1);
121  }
122  }
123 
124  if( s<0 || f<s ) {
125  fprintf( stderr, "Illegal range specified: %s\n", sp );
126  exit(1);
127  }
128  if( f>=max || s>=max ) {
129  fprintf( stderr, "Illegal range, maximum node number exceeded: %s\n", sp );
130  exit(1);
131  }
132  for( int i=s; i<=f; i+=inc )
133  nodes.insert( i );
134 
135  } else {
136 
137  int num;
138  sscanf( sp, "%d", &num );
139 
140  if( !sscanf( sp, "%d", &num ) || num<0 || num>=max ) {
141  fprintf( stderr, "Illegal node number specified in list: %d\n", num );
142  exit(1);
143  }
144  nodes.insert( num );
145  }
146  return false;
147 }
148 
149 
150 void
151 extract_nodes( const char *list, vector<int> &nodes, int max )
152 {
153  char *lc = strdup( list );
154  char *sp = strtok( lc, "," );
155  set<int> ch_nodes; // deal with overlapping ranges
156 
157  while( sp != NULL ) {
158 
159  if( process_specifier( sp, ch_nodes, max ) ) break;
160 
161  sp = strtok( NULL, "," );
162  }
163  free( lc );
164 
165  if( !ch_nodes.size() ) {
166  fprintf( stderr, "No nodes specified: %s\n", list );
167  exit(1);
168  }
169 
170  // convert set to vector
171  nodes.resize( ch_nodes.size() );
172  int idx=0;
173  auto end=ch_nodes.end();
174  for( auto sit=ch_nodes.cbegin(); sit!=end; sit++ )
175  nodes[idx++] = *sit;
176 
177 }
178 
179 
180 int
181 main( int argc, char *argv[] )
182 {
183  gengetopt_args_info args;
184 
185  // let's call our cmdline parser
186  if (cmdline_parser (argc, argv, &args) != 0)
187  exit(1);
188 
189  // the last arg must be a file name
190  IGBheader* h;
191  FILE *fin = stdin;
192  try {
193  if( strcmp(args.inputs[0],"-") )
194  fin = fopen( args.inputs[0], "r" );
195  h= new IGBheader( fin, true );
196  }
197  catch( int a ) {
198  cerr << "File is not a proper IGB file: "<< args.inputs[0] << endl;
199  exit(1);
200  }
201 
202  float VmREST = -80; //default value
203  float DVDT_THRESH = args.dvdt_arg*h->inc_t();
204  float repolarize = args.repol_arg; // %repolarization
205  float v_thresh = args.rep_level_arg;
206  float plusthresh = 10.;
207 
208  thresh_t threshtype;
209  if( args.threshold_mode_counter )
210  threshtype = Fixed;
211  else
212  threshtype = PerCentRepol;
213 
214  int MINAPD = args.minapd_arg; // min APD
215  if( args.peak_value_arg == peak_value_arg_plateau )
216  if( MINAPD<args.plateau_duration_arg + args.plateau_start_arg ) {
217  MINAPD += args.plateau_duration_arg + args.plateau_start_arg;
218  cerr << "warning: changing apdmin to "<<MINAPD<<" from "<<MINAPD<<endl;
219  }
220 
221  float *oldervm = (float *)calloc(h->slice_sz(),sizeof(float));
222  float *oldvm = (float *)calloc(h->slice_sz(),sizeof(float));
223  float *vm = (float *)calloc(h->slice_sz(),sizeof(float));
224  float *apd = (float *)calloc(h->slice_sz(),sizeof(float));
225  float *vmthresh = (float *)calloc(h->slice_sz(),sizeof(float));
226  float *vmrest = (float *)calloc(h->slice_sz(),sizeof(float));
227  bool *peaked = (bool *)calloc(h->slice_sz(),sizeof(bool));
228  vector<float> repol_tm( h->slice_sz(),-1.);
229  vector<float> apdstart( h->slice_sz(),UNSTARTED);
230 
231  int *last_apstart = NULL;
232  if( args.mode_arg==mode_arg_act) last_apstart = (int *)calloc(h->slice_sz(),sizeof(int));
233 
234  for( int i=0; i<h->slice_sz(); i++ ) {
235  apd[i] = UNCOMPUTED;
236  peaked[i] = false;
237  vmrest[i] = VmREST;
238  vmthresh[i] = threshtype==MinDeriv?0:v_thresh;
239  if( args.mode_arg==mode_arg_act) last_apstart[i] = NO_PRIOR;
240  }
241 
242  int first = 0;
243  if( args.first_given )
244  first = args.first_arg;
245  else if( args.first_time_given )
246  first = (args.first_time_arg-h->org_t())/h->inc_t();
247  for( int t=0; t<first; t++ )
248  h->read_data( oldvm );
249 
250  vector<int> nodes;
251  extract_nodes( args.check_nodes_arg, nodes, h->slice_sz() );
252  int unfound = nodes.size();
253 
254  for( int t=first; t<h->t() && unfound; t++ ){
255 
256  h->read_data( vm );
257 
258  if( !t )
259  memcpy( oldvm, vm, h->slice_sz()*sizeof(float) );
260 
261 #pragma omp parallel for
262  for( int j=0; j<nodes.size(); j++ ) {
263 
264  int i = nodes[j];
265 
266  if( apdstart[i] == UNSTARTED ) {
267 
268  // find resting level
269  if( t && fabs(vm[i]-oldervm[i])<0.001 )
270  vmrest[i] = oldvm[i];
271 
272  if( (vm[i]-oldvm[i])>DVDT_THRESH && vm[i]>args.vup_arg ) { // AP start found
273 
274  if( oldvm[i]<=args.vup_arg )
275  apdstart[i] = lininterp( oldvm[i], vm[i], t-1, t, args.vup_arg );
276  else {
277  apdstart[i] = lininterp( oldvm[i]-oldervm[i], vm[i]-oldvm[i],
278  t-1, t, DVDT_THRESH )*h->inc_t();
279  }
280 
281  if( args.mode_arg == mode_arg_detect ) {
282  cout << i << " @ " << apdstart[i]+h->org_t() << endl;
283  exit(3);
284  }
285  }
286  }
287 
288  // find peak Vm for repolarization
289  if( !peaked[i] && apdstart[i]!=UNSTARTED ) {
290  switch( threshtype ) {
291  case Fixed:
292  if( vm[i]<oldvm[i] )
293  peaked[i] = true;
294  break;
295  case AboveRest:
296  if( vm[i]<oldvm[i] ) {
297  peaked[i] = true;
298  vmthresh[i] = vmrest[i]+plusthresh;
299  }
300  break;
301  case PerCentRepol:
302  if( args.peak_value_arg == peak_value_arg_upstroke ) { // find peak upstoke
303  if( vm[i]<oldvm[i] ) {
304  peaked[i] = true;
305  vmthresh[i] = vmrest[i] + (1.-repolarize/100.)*(oldvm[i] -vmrest[i] );
306  }
307  } else { // find plateau peak
308  if( t*h->inc_t()-apdstart[i] < args.plateau_start_arg ) {
309  vmthresh[i] = vm[i];
310  } else if( t*h->inc_t()-apdstart[i] < args.plateau_start_arg+args.plateau_duration_arg ) {
311  if( vm[i] > vmthresh[i] )
312  vmthresh[i] = vm[i];
313  } else {
314  vmthresh[i] = vmrest[i] + (1.-repolarize/100.)*(vmthresh[i] -vmrest[i] );
315  peaked[i] = true;
316  }
317  }
318  break;
319  default: // do nothing
320  break;
321  }
322  // ensure the minimum peak upstroke was met
323  if( peaked[i] && vm[i]-vmrest[i]<args.mindv_arg ) {
324  apdstart[i] = UNSTARTED;
325  peaked[i] = false;
326  if( args.mode_arg==mode_arg_debug )
327  cout << "node: " << i << " failed to reach min dv/dt at frame " << t<< endl;
328  }
329  }
330 
331  if( peaked[i] && apd[i]==UNCOMPUTED && args.mode_arg==mode_arg_act_first ) {
332  unfound--;
333  } else if( peaked[i] && apd[i]==UNCOMPUTED && args.mode_arg==mode_arg_act) {
334  // look at activations
335  if( last_apstart[i]==NO_PRIOR || (t-last_apstart[i])*h->inc_t()>MINAPD ) {
336  cout << i << " : " << apdstart[i]+h->org_t() << endl;
337  last_apstart[i] = t;
338  }
339  apdstart[i] = UNSTARTED;
340  peaked[i] = false;
341  } else if( peaked[i] && apd[i]==UNCOMPUTED ) {
342  // look fpr AP end
343  bool ended=false;
344  if( threshtype==MinDeriv && oldervm[i]-oldvm[i] > oldvm[i]-vm[i] &&
345  oldervm[i]-oldvm[i]>vmthresh[i] ) {
346  vmthresh[i] = oldervm[i] -oldvm[i] ;
347  apd[i] = (t - 1)*h->inc_t() - apdstart[i];
348  ended = true;
349  }else if(vm[i]<=vmthresh[i] && oldvm[i]>vmthresh[i] ) {
350  apd[i] = lininterp( oldvm[i], vm[i], t-1, t, vmthresh[i] )*h->inc_t()-apdstart[i];
351  ended = true;
352  }
353  if( ended ) {
354  if( apd[i] < MINAPD ) { // too short so throw it away and keep looking
355  apdstart[i] = UNSTARTED;
356  peaked[i] = false;
357  apd[i] = UNCOMPUTED;
358  if( args.mode_arg==mode_arg_debug )
359  cerr << "node: " << i << " APD too short at frame " << t<< endl;
360  } else if( args.mode_arg == mode_arg_first ) {
361  --unfound;
362  } else if( args.mode_arg == mode_arg_all ) {
363  cout << i << " : " << apd[i];
364  if( args.start_times_flag )
365  cout << "\t\tstarted : " << apdstart[i] + h->org_t();
366  cout << endl;
367  } else if( args.mode_arg == mode_arg_repol_all ) {
368  cout << i << " : " << apd[i] + apdstart[i] + h->org_t();
369  if( args.start_times_flag )
370  cout << "\t\tstarted : " << apdstart[i] + h->org_t();
371  cout << endl;
372  } else if( args.mode_arg == mode_arg_repol_first ){
373  if( repol_tm[i] < 0. ) {
374  repol_tm[i] =apd[i] + apdstart[i] + h->org_t();
375  }
376  } else if( args.mode_arg == mode_arg_debug ) {
377  cout << "node:" << i << "\tAPD: " << apd[i] << "\tstarted: " << apdstart[i] + h->org_t();
378  cout << "\trest: " << vmrest[i];
379  cout << "\tvmthresh: " << vmthresh[i];
380  cout << endl;
381  }
382  }
383  }
384  // reset if looking for more APs
385  if( (args.mode_arg==mode_arg_all || args.mode_arg==mode_arg_debug ||
386  args.mode_arg==mode_arg_repol_all ) &&
387  apd[i]!=UNCOMPUTED && t*h->inc_t()-apdstart[i]+apd[i]>args.blank_arg ) {
388  apdstart[i] = UNSTARTED;
389  peaked[i] = false;
390  apd[i] = UNCOMPUTED;
391  }
392  }
393  float *tmp = oldervm;
394  oldervm = oldvm;
395  oldvm = vm;
396  vm = tmp;
397  }
398 
399  FILE *out = stdout;
400 
401  if( args.mode_arg == mode_arg_first ) {
402 
403  if( strcmp(args.output_file_arg,"-") )
404  out = fopen( args.output_file_arg, "w" );
405 
406  for( int i=0; i<h->slice_sz(); i++ ){
407  if( args.start_times_flag )
408  fprintf( out, "%f\t%f\n", apd[i], apdstart[i]+h->org_t() );
409  else
410  fprintf( out, "%f\n", apd[i] );
411  }
412  fclose( out );
413  } else if( args.mode_arg == mode_arg_repol_first ) {
414 
415  if( strcmp(args.output_file_arg,"-") )
416  out = fopen( args.output_file_arg, "w" );
417 
418  for( int i=0; i<repol_tm.size(); i++ )
419  if( args.start_times_flag )
420  fprintf( out, "%f\t%f\n", repol_tm[i], apdstart[i]+h->org_t() );
421  else
422  fprintf( out, "%f\n", repol_tm[i] );
423 
424  } else if( args.mode_arg == mode_arg_act_first ) {
425 
426  if( strcmp(args.output_file_arg,"-") )
427  out = fopen( args.output_file_arg, "w" );
428 
429  for( int i=0; i<apdstart.size(); i++ )
430  fprintf( out, "%f\n", apdstart[i]+h->org_t() );
431  }
432  exit(0);
433 }
434 
void extract_nodes(const char *list, vector< int > &nodes, int max)
Definition: IGBapd.cc:151
int main(int argc, char *argv[])
Definition: IGBapd.cc:181
thresh_t
Definition: IGBapd.cc:40
@ MinDeriv
Definition: IGBapd.cc:40
@ PerCentRepol
Definition: IGBapd.cc:40
@ Fixed
Definition: IGBapd.cc:40
@ AboveRest
Definition: IGBapd.cc:40
const int NO_PRIOR
Definition: IGBapd.cc:46
float lininterp(float y0, float y1, float x0, float x1, float interp)
Definition: IGBapd.cc:51
const float UNSTARTED
Definition: IGBapd.cc:45
bool process_specifier(char *sp, set< int > &nodes, int max)
Definition: IGBapd.cc:77
const float UNCOMPUTED
Definition: IGBapd.cc:44
float org_t(void)
Definition: IGBheader.h:317
size_t t(void)
Definition: IGBheader.h:277
int read_data(T *dp, size_t numt=1, char *buf=NULL)
Definition: IGBheader.h:431
size_t slice_sz()
Definition: IGBheader.h:263
void inc_t(float a)
Definition: IGBheader.h:329
constexpr T max(T a, T b)
Definition: ion_type.h:31