Skip to content
Snippets Groups Projects
Select Git revision
  • 3348cead578a34a66642d84a606f685d23b0919d
  • master default protected
  • v0.8.1
  • v0.8.0
  • v0.7.9
  • v0.7.8
  • v0.7.7
  • v0.7.6
  • v0.7.5
  • v0.7.4
  • v0.7.3
  • v0.7.2
  • v0.7.1
  • v0.7.0
  • v0.6.20
  • v0.6.19
  • v0.6.18
  • v0.6.17
  • 0.6.16
  • v0.6.15
20 results

absscal.c

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    absscal.c 46.07 KiB
    /** @file absscal.c
     *  @brief Extraction and calibration of blood sampler data (previously blo2kbq).
     *  @copyright (c) Turku PET Centre
     *  @author Vesa Oikonen
     */
    /// @cond
    /*****************************************************************************/
    #include "tpcclibConfig.h"
    /*****************************************************************************/
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <ctype.h>
    #include <time.h>
    #include <math.h>
    /*****************************************************************************/
    #include "libtpcmisc.h"
    /*****************************************************************************/
    
    /*****************************************************************************/
    static char *info[] = {
      "Extraction and calibration of blood TACs measured using",
      "automatic blood sampling systems (ABSS, \"blood pump\") (1).",
      "Application name was previously blo2kbq, version 3.8.1.",
      " ",
      "Usage: @P [Options] ABSS_file [BTAC_file]",
      " ",
      "Options:",
      " -c=<filename>",
      "     Calibration coefficients for ABSS and well-counter should be specified",
      "     with calibration file (2), given with option -c.",
      " -i=<isotope>",
      "     If ABSS data file does not contain the isotope code or halflife, then",
      "     it can be given with this option, using codes",
      "     C-11, F-18, Ga-68, Ge-68, O-15, ...",
      " -decay=<on|off>",
      "     Blood data is corrected for radioactive decay (on, default), or not",
      "     corrected (off).",
    //"     Possible background subtraction is done before decay correction.",
      " -start=<t>",
      "     If ABSS measurement was not started at the time of injection,",
      "     the delay t (sec) must be entered with option -start=<t>. If data",
      "     collection was started before injection, enter a negative value for t;",
      "     activities before that time are removed from the output data.",
      " -density[=<blood density>]",
      "     With this option blood radioactivity concentrations (kBq/mL) are",
      "     divided by the given blood density (by default 1.06)",
      "     to get concentrations in units kBq/g.",
      " -min",
      "     Sample times are written in minutes (in seconds by default).",
      " -mean, -left, -right, -both",
      "     The counts or radioactivity concentrations are by default calculated",
      "     as the mean of the channels (-mean). With options -left and -right ",
      "     only the left (ch2) or right (ch1) channel is used.",
      "     With option -both the results from both channels are saved.",
      "     Only applicable to Scanditronics and GEMS data.",
    /*
      " -z",
      "     For reading of the measurement date, local time zone can be used with",
      "     option -z instead of Greenwich main time (default).",
    */
      " -abss=<device>",
      "     ABSS produces device-specific file formats, and detectors have",
      "     calibration factors. This option is needed if ABSS can not be correctly",
      "     determined from the filename (*.lis, *.bld, *.alg, or *.txt).",
      "     Accepted devices are Scanditronics, GEMS, Allogg1, and Allogg2, or",
      "     numbers 1-4, accordingly.",
      " -stdoptions", // List standard options like --help, -v, etc
      " ",
      "Filename for corrected blood TAC data can be entered as the last command-line",
      "argument. If not given, then file is written to the directory where ABSS file",
      "is; calibrated file is named *.kbq and non-calibrated *.dat by default.",
      " ",
      "Example:",
      "  @P -i=O-15 -c=S:/Lab/plasma/bsampler_calibration/pump_cal.dat sr452_blo.bld",
      " ",
    //"In case of the GEMS online sampler (GE Advange/PET CT), the injection",
    //"time is always taken from the first sample time in the *.bld file,",
    //"not from the header of the file, like with Scanditronics data.",
    //" ",
      "References:",
      "1. http://www.turkupetcentre.net/petanalysis/input_abss.html",
      "2. http://www.turkupetcentre.net/petanalysis/input_abss_calibration.html",
      " ",
      "See also: absszero, absstime, abssexam, abssfch, dftcat, disp4dft, fitdelay",
      " ",
      "Keywords: blood, input, ABSS, calibration",
      0};
    /*****************************************************************************/
    
    /*****************************************************************************/
    /* Types and defaults */
    #define MAX_PUMPS 4
    #define BLOOD_DENSITY 1.06
    typedef enum {SCANDITRONICS,ALLOGG,ALLOGG2} Format;
    static int both_cols=0;
    static int gm_timez=1; /*time zone = Greenwich main time*/
    static int pump=0;
    char isotope[10]="UNKNOWN";
    double halflife=-1.0;
    char studynr[MAX_STUDYNR_LEN+1];
    /*****************************************************************************/
    /* Results data */
    struct BDataResults {
      double t, ch1, ch2;
    };
    /*****************************************************************************/
    /* Scanditronics data */
    struct BDataS {
      struct BDataResults results;
      double time_of_day;
      double time_since_start;
      double meas_interval;
      int pair1_coincid;
      int pair1_det1_cnts;
      int pair1_det2_cnts;
      int pair2_coincid;
      int pair2_det1_cnts;
      int pair2_det2_cnts;
      int aux_cnts;
    };
    
    /* Allogg data */
    struct BDataA {
      struct BDataResults results;
      double time_since_start;
      double meas_interval; /* This is calculated from the data */
      int data[2];
    };
    
    struct DataList {
      union {
        struct BDataS scanditronics;
        struct BDataA allogg;
        struct BDataResults results;
      };
      struct DataList *prev, *next;
    };
    
    struct Blood {
      /** Date acquired from the file header (comments) */
      struct tm date;
      /** Date in time_t format. Possibly altered by time data from actual data */
      time_t time;
      /** When was the background radiation measured */
      char bgdate[11];
      /** Background radiation */
      float background;    
      /** List of datapoints */
      struct DataList *data;
    };
    /*****************************************************************************/
    /* Local functions */
    int read_scanditronics_data(
      const char *inputfile, struct Blood *blood, int verbose);
    int read_allogg_data(
      const char *inputfile, struct Blood *blood, int verbose);
    int read_allogg2_data(
      char *inputfile, struct Blood *blood, int verbose, char *msg);
    void correct_scanditronics(struct Blood *blood, int verbose);
    int correct_allogg(struct Blood *blood);
    int correct_allogg2(struct Blood *blood, int verbose);
    void subtract_background(struct Blood *blood);
    int get_calfactors(const char *file,time_t bloodt, double *wcf,
      double *pumpf, int pump, char *isotope, char *caldate, int verbose);
    int find_date(char *str, struct Blood *blood, int verbose);
    int find_halflife(char *str, int verbose);
    /*****************************************************************************/
    
    /*****************************************************************************/
    /* Turn on the globbing of the command line, since it is disabled by default in
       mingw-w64 (_dowildcard=0); in MinGW32 define _CRT_glob instead, if necessary;
       In Unix&Linux wildcard command line processing is enabled by default. */
    /*
    #undef _CRT_glob
    #define _CRT_glob -1
    */
    int _dowildcard = -1;
    /*****************************************************************************/
    
    /*****************************************************************************/
    /**
     *  Main
     */
    int main(int argc, char *argv[])
    {
      int ai, help=0, version=0, verbose=1;
    
      double startTime=0.0, density=1.0, wc_factor=-1.0, pump_factor=-1.0;
      int decay_correct=1; // 0=no, 1=yes
      char pumpfile[FILENAME_MAX], calfile[FILENAME_MAX], outfile[FILENAME_MAX];
      char tmp[FILENAME_MAX];
      float sum1, sum2;
      Format format=SCANDITRONICS;
      struct DataList *ptr;
      int timeInMinutes=0;
      struct Blood blood;
      char caldate[25];
      int column=0; // use mean (0), left (1), or right (2) channel
      char *cptr;
      FILE *fp;
      int r;
      int zero1=1, zero2=1;
      time_t t_injection, t_samp;
      struct tm tm_injection, tm_samp;
     
    
      /*
       *  Get arguments
       */
      if(argc==1) {tpcPrintUsage(argv[0], info, stderr); return(1);}
      pumpfile[0]=calfile[0]=outfile[0]=(char)0;
      studynr[0]=(char)0;
    
      /* Options */
      for(ai=1; ai<argc; ai++) if(*argv[ai]=='-') {
        //printf("argv[%d] := '%s'\n", ai, argv[ai]);
        cptr=argv[ai]+1; if(*cptr=='-') cptr++; if(cptr==NULL) continue;
        if(tpcProcessStdOptions(argv[ai], &help, &version, &verbose)==0) continue;
        if(strncasecmp(cptr, "C=", 2)==0 && strlen(cptr)>2) {
          strlcpy(calfile, cptr+2, FILENAME_MAX); continue;
        } else if(strncasecmp(cptr, "O=", 2)==0 && strlen(cptr)>2) { // deprecated
          strlcpy(outfile, cptr+2, FILENAME_MAX); continue;
        } else if(strncasecmp(cptr, "I=", 2)==0 && strlen(cptr)>2) {
          if(hlCorrectIsotopeCode(cptr+2)!=NULL) {
            strcpy(isotope, hlCorrectIsotopeCode(cptr+2));
            halflife=hlFromIsotope(isotope);
            continue;
          }
          printf("Error: Incorrect isotope code '%s'\n", cptr+2);
          return(1);
        } else if(strncasecmp(cptr, "DECAY=", 6)==0 && strlen(cptr)>6) {
          cptr+=6;
          if(strcasecmp(cptr, "OFF")==0) {decay_correct=0; continue;}
          if(strcasecmp(cptr, "ON")==0) {decay_correct=1; continue;}
        } else if(strcasecmp(cptr, "DENSITY")==0) {
          density=BLOOD_DENSITY; continue;
        } else if(strncasecmp(cptr, "DENSITY=", 8)==0) {
          cptr+=8; density=atof_dpi(cptr); if(density>0.0) continue;
        } else if(strncasecmp(cptr, "MIN", 1)==0) {
          timeInMinutes=1; continue;
        } else if(strcasecmp(cptr, "SEC")==0) {
          timeInMinutes=0; continue;
        } else if(strncasecmp(cptr, "Z", 1)==0) {
          gm_timez=0; continue;
        } else if(strncasecmp(cptr, "LEFT", 1)==0) {
          column=1; continue;
        } else if(strncasecmp(cptr, "RIGHT", 1)==0) {
          column=2; continue;
        } else if(strcasecmp(cptr, "BOTH")==0) {
          both_cols=1; continue;
        } else if(strncasecmp(cptr, "PUMP=", 5)==0 && strlen(cptr)>5) {
          cptr+=5;
          if(strncasecmp(cptr, "SCANDITRONICS", 1)==0) {format=SCANDITRONICS; pump=1; continue;}
          if(strncasecmp(cptr, "GEMS", 1)==0) {format=SCANDITRONICS; pump=2; continue;}
          if(strncasecmp(cptr, "ALLOGG1", 1)==0) {format=ALLOGG; pump=3; continue;}
          if(strcasecmp(cptr, "ALLOGG2")==0) {format=ALLOGG2; pump=4; continue;}
          pump=atoi(cptr); 
          if(pump==1 || pump==2) {format=SCANDITRONICS; continue;}
          if(pump==3) {format=ALLOGG; continue;}
          if(pump==4) {format=ALLOGG2; continue;}
        } else if(strncasecmp(cptr, "START=", 6)==0 && strlen(cptr)>6) {
          startTime=atof_dpi(cptr+6); continue;
        }
        fprintf(stderr, "Error: invalid option '%s'.\n", argv[ai]);
        return(1);
      } else break;
      
      /* Print help or version? */
      if(help==2) {tpcHtmlUsage(argv[0], info, ""); return(0);}
      if(help) {tpcPrintUsage(argv[0], info, stdout); return(0);}
      if(version) {tpcPrintBuild(argv[0], stdout); return(0);}
    
      /* Process other arguments, starting from the first non-option */
      if(ai<argc) strlcpy(pumpfile, argv[ai++], FILENAME_MAX);
      if(ai<argc) strlcpy(outfile, argv[ai++], FILENAME_MAX);
      if(ai<argc) {fprintf(stderr, "Error: too many arguments: '%s'.\n", argv[ai]); return(1);}
    
      /* Is something missing? */
      if(!pumpfile[0]) {
        fprintf(stderr, "Error: missing ABSS file name.\n");
        return(1);
      }
    
      /* In verbose mode print arguments and options */
      if(verbose>1) {
        printf("pumpfile := %s\n", pumpfile);
        printf("isotope := %s\n", isotope);
        printf("halflife := %g\n", halflife);
        printf("decay_correct := %d\n", decay_correct);
        printf("startTime := %g\n", startTime);
        printf("density := %g\n", density);
        printf("timeInMinutes := %d\n", timeInMinutes);
        printf("column := %d\n", column);
        printf("both_cols := %d\n", both_cols);
        fflush(stdout);
      }
    
      /* Decide which pump we are dealing with, if not given by user */
      if(pump==0) {
        cptr=pumpfile+strlen(pumpfile)-4;
        if(strcasecmp(cptr, ".lis")==0) {pump=1; format=SCANDITRONICS;}
        else if(strcasecmp(cptr, ".bld")==0) {pump=2; format=SCANDITRONICS;}
        else if(strcmp(cptr, ".alg")==0) {pump=3; format=ALLOGG;}
        else if(strcmp(cptr, ".txt")==0) {pump=4; format=ALLOGG2;}
        else {
          fprintf(stderr, "Error: cannot identify the pump;\n");
          fprintf(stderr, "please rename the file to *.lis, *.bld, *.alg or *.txt\n");
          fprintf(stderr, "or use option -abss=<id>.\n");
          return(1);
        }
      }
      if(verbose>1) {
        printf("pump := %d\n", pump);
        printf("format := %d\n", format);
      }
    
    
      /* 
       *  Read in data
       */
      if(verbose>0) printf("reading %s\n", pumpfile);
      switch(format) {
        case SCANDITRONICS:
          if(read_scanditronics_data(pumpfile, &blood, verbose-2)) return 2;
          break;
        case ALLOGG:
          if(read_allogg_data(pumpfile, &blood, verbose-2)) return 2;
          break;
        case ALLOGG2:
          if(read_allogg2_data(pumpfile, &blood, verbose-2, tmp)) {
            fprintf(stderr, "Error in reading %s: %s.\n", pumpfile, tmp);
            return 2;
          }
          break;
      }
      /* Check that isotope is specified with decay correction */
      if(decay_correct!=0) {
        if(halflife<=0.0 || strcasecmp(isotope, "UNKNOWN")==0) {
          fprintf(stderr,"Error: isotope must be specified for decay correction.\n");
          return 1;
        }
      }
      /* Check that isotope is specified with calfile */
      if(calfile[0] && strcasecmp(isotope, "UNKNOWN")==0) {
        fprintf(stderr,"Error: isotope must be specified for calibration.\n");
        return 1;
      }
    
      /* Process the data */
      if(verbose>0) printf("processing the data\n");
      switch(format) {
        case SCANDITRONICS:
          correct_scanditronics(&blood, verbose-2);
          break;
        case ALLOGG:
          if(correct_allogg(&blood) == 1) return 3;
          break;
        case ALLOGG2:
          if(correct_allogg2(&blood, verbose-2) == 1) return 3;
          break;
      }
    
      /* Check that neither of the channels has all values zero*/
      if(verbose>1) printf("verifying that data is not all zeroes\n");
      zero1=zero2=1;
      if(format==SCANDITRONICS) { 
        ptr=blood.data; 
        while(ptr) {
          if(fabs(ptr->results.ch1) > 1.0e-10) zero1=0;
          if(fabs(ptr->results.ch2) > 1.0e-10) zero2=0;
          ptr=ptr->next;
        }
        if(zero1){
          fprintf(stderr, "Error: All values from channel 1 are zeros. Contact the physicist!\n");
          if(column!=2) return 4;
        }
        if(zero2){
          fprintf(stderr, "Error: All values from channel 2 are zeros. Contact the physicist!\n");
          if(column!=1) return 4;
        }
      }
    
      /* Subtract background radiation; must be done before decay correction */
      if(blood.background>0.0) {
        if(verbose>=0) printf("subtracting background\n"); 
        subtract_background(&blood);
      } else if(blood.background<-1.0) {
        if(format==ALLOGG || format==ALLOGG2) {
          if(calfile[0]) {
            fprintf(stderr, "Error: %s does not contain background.\n", pumpfile);
            return(5);
          } else
            fprintf(stderr, "Warning: %s does not contain background.\n", pumpfile);        
        }
      }
    
    
      /* 
       *  Calibration 
       */
      if(calfile[0]) {
      
        /* Check that we have the measurement date */
        if(blood.date.tm_year==0) {
          fprintf(stderr, "\nError: Blood file must contain the measurement date!\n");
          fprintf(stderr, "Open the blood file in a text editor and add the date to\n");
          fprintf(stderr, "the beginning as its own comment line, e.g. # 1999-11-31\n\n");
          return(6);
        }
      
        if(verbose>0) printf("calibrating\n");
        ptr=blood.data;
        if(get_calfactors(calfile, timegm(&blood.date), &wc_factor, &pump_factor,
                          pump, isotope, caldate, verbose-1))
    //    if(get_calfactors(calfile,mktime(&blood.date),&wc_factor,&pump_factor,
    //                      pump,isotope,caldate))
          return 7;
        if(verbose>=0)
          printf("  PC := %g\n  WC/BR := %g\n  calibration_date := %s\n",
            pump_factor, wc_factor, caldate);
        while(ptr) {
          ptr->results.ch1*=wc_factor*pump_factor;
          ptr->results.ch2*=wc_factor*pump_factor;
          ptr=ptr->next;
        }
      } else if(verbose>=0) {
        printf("Warning: no calibration file was entered; data is not calibrated.\n");
      }
    
    
      /* Correct the start time for decay correction */
      /* If negative, then correct them back again later */
      /* Set earlier activities to 0 */
      if(startTime!=0.0) {
        if(verbose>1) printf("correcting the start time\n");
        ptr=blood.data;
        while(ptr) {
          ptr->results.t+=startTime;
          if(format==SCANDITRONICS)
            ptr->scanditronics.time_since_start+=startTime;
          else
            ptr->allogg.time_since_start+=startTime;
          if(ptr->results.t<0.0) {
            ptr->results.ch1=0;
            ptr->results.ch2=0;
          }
          ptr=ptr->next;
        }
      }
    
      /* Print the time of the first sample */
      if(format==SCANDITRONICS) {
        ptr=blood.data; t_samp=ptr->scanditronics.time_of_day;
        if(gmtime_r(&t_samp, &tm_samp)==NULL) {
          fprintf(stderr, "Warning: invalid sample time.\n");
        } else {
          tm_samp.tm_year=blood.date.tm_year;
          tm_samp.tm_mon=blood.date.tm_mon;
          tm_samp.tm_mday=blood.date.tm_mday;
          t_samp=timegm(&tm_samp);
          char buf[32];
          if(!ctime_r_int(&t_samp, buf)) {
            fprintf(stderr, "Warning: invalid sample time.\n");
          } else {
            fprintf(stdout, "Time point of the first sample is: %s\n", buf);
          }
        }
      }
    
      /* Decay correction */
      if(decay_correct!=0) {
        double lambda=hl2lambda(halflife*60.0);
        double dc=0;
    
        ptr=blood.data;
        if(verbose>=0) printf("decay correction with half-life %.2f min.\n", halflife);
        while(ptr) {
          dc=1.0;
          if(format==SCANDITRONICS) {
            if(ptr->scanditronics.time_since_start>=0.0) {
              dc = lambda2factor(lambda,
                     ptr->scanditronics.time_since_start, ptr->scanditronics.meas_interval);
            }
          } else {
            if(ptr->allogg.time_since_start>=0.0) {
              dc = lambda2factor(lambda, ptr->allogg.time_since_start, ptr->allogg.meas_interval);
              if(verbose>5) printf("dc:%f %f\n",dc,ptr->allogg.time_since_start);
            }
          }
          ptr->results.ch1*=dc;
          ptr->results.ch2*=dc;
          ptr=ptr->next;
        }
      }
    
    #if(0)  // REMOVED IN BLO2KBQ VERSION 3.4.3
      /* If start time is negative, then set orig times */
      if(startTime<0.0) {
        ptr=blood.data;
        while(ptr) {
          ptr->results.t-=startTime;
          ptr=ptr->next;
        }
      }
    #endif
    
      /* Blood density correction */
      if(density!=1.0) {
        if(verbose>0)
          printf("kBq/mL to kBq/g correction with blood density of %.2f g/mL.\n", density);
        ptr=blood.data;
        while(ptr) {
          ptr->results.ch1/=density;
          ptr->results.ch2/=density;
          ptr=ptr->next;
        }
      }
    
      /* Check that channels are not too different */
      ptr=blood.data; sum1=sum2=0.0;
      while(ptr) {
        if(format==ALLOGG || format==ALLOGG2) {
          if(ptr->results.ch1 < -2.0)
            printf("Warning: channel below zero:%lf\n", ptr->results.ch1);
        } else {
          if(ptr->results.ch1 < 0)
            printf("Warning: channel one below zero:%lf\n", ptr->results.ch1);
          if(ptr->results.ch2 < 0)
            printf("Warning: channel two below zero:%lf\n", ptr->results.ch2);
        }
        sum1+=ptr->results.ch1;
        sum2+=ptr->results.ch2;
        ptr=ptr->next;
      }
      if(column!=1 && column!=2 && (fabs(sum1-sum2)/(1.0+sum1+sum2))>0.3)
        fprintf(stderr, "Warning: Large difference between channels of online blood sampler!\n");
    
    
      /*
       *  Write output 
       */
    
      /* Create output filename, if not given */
      if(!outfile[0]) {
        if(studynr_validity_check2(studynr, 1)) strcpy(outfile, studynr);
        else strcpy(outfile, pumpfile);
        cptr=strrchr(outfile,'.'); if(cptr!=NULL) *cptr='\0';
        if(calfile[0]) strcat(outfile,".kbq"); else strcat(outfile,".dat");
        if(verbose>1) printf("outputfile := %s\n", outfile);
      }
      /* Check if output file exists; backup, if necessary */
      if(backupExistingFile(outfile, NULL, tmp)) {
        fprintf(stderr, "Error: %s\n", tmp);
      }
      /* Open the file */
      if(verbose>=0) printf("writing output in file %s\n", outfile);
      fp=fopen(outfile, "w");
      if(fp==NULL) {
        fprintf(stderr, "Error: cannot write %s\n", outfile);
        perror("fopen");
        return 11;
      }
    
      /*
       *  Write header information
       */
      if(verbose>2) printf("writing 'header'\n");
    
      /* measurement start time */
      if(gmtime_r(&t_injection, &tm_samp)==NULL) {
        fprintf(stderr, "Warning: invalid measurement start time.\n");
      }
    #if(0)
      } else {
        /* Greenwich main time->Hki time zone*/
        if(blood.date.tm_hour==tm_samp.tm_hour-2) 
          blood.date.tm_hour+=2; 
        else if(blood.date.tm_hour==tm_samp.tm_hour-3) 
          blood.date.tm_hour+=3; 
      }
    #endif
      fprintf(fp, "# measurement_start_time := %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n",
        blood.date.tm_year+1900,blood.date.tm_mon+1,blood.date.tm_mday,
        blood.date.tm_hour,blood.date.tm_min,blood.date.tm_sec);
      /* detector id */
      fprintf(fp, "# detector := %d\n", pump);
      /* sample time unit */
      if(timeInMinutes==1) fprintf(fp, "# time_unit := min\n");
      else fprintf(fp, "# time_unit := sec\n");
      /* calibration */
      if(calfile[0]) {
        fprintf(fp, "# calibrated := yes\n");
        fprintf(fp, "# unit := kBq/ml\n");
        fprintf(fp, "# calibration_date := %s\n", caldate);
        fprintf(fp, "# pump_calibration_factor := %g\n", pump_factor);
        fprintf(fp, "# wc_calibration_factor_per_br := %g\n", wc_factor);
        fprintf(fp, "# calibration_file := %s\n", calfile);
      } else {
        fprintf(fp, "# calibrated := no\n");
        fprintf(fp, "# unit := cps\n");
      }
      /* isotope */
      fprintf(fp, "# isotope := %s\n", isotope);
      /* decay correction */
      if(decay_correct!=0) {
        fprintf(fp, "# decay_correction := yes\n");
        fprintf(fp, "# halflife := %g [min]\n", halflife);
      } else {
        fprintf(fp, "# decay_correction := no\n");
      }
      /* injection time */
      r=3600*blood.date.tm_hour + 60*blood.date.tm_min + blood.date.tm_sec;
      if(startTime==0.0) { /* injection time = measurement start time */
        fprintf(fp, "# injection_time := %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n",
          blood.date.tm_year+1900, blood.date.tm_mon+1, blood.date.tm_mday,
          blood.date.tm_hour, blood.date.tm_min, blood.date.tm_sec);
      } else if(r==0) {
        fprintf(fp, "# injection_time := %g [sec]\n", startTime);
      } else {
        t_injection=timegm(&blood.date);
        if(t_injection==-1) {
          fprintf(stderr, "Error: invalid injection time.\n");
          fclose(fp); return(12);
        } else {
          t_injection-=startTime;
          if(gmtime_r(&t_injection, &tm_injection)==NULL) {
            fprintf(stderr, "Error: invalid injection time.\n");
            fclose(fp); return(12);
          }
        }
        fprintf(fp, "# injection_time := %.4d-%.2d-%.2d %.2d:%.2d:%.2d\n",
          tm_injection.tm_year+1900, tm_injection.tm_mon+1, tm_injection.tm_mday,
          tm_injection.tm_hour, tm_injection.tm_min, tm_injection.tm_sec);
      }
      /* background activity */
      if(blood.background>0.0) {
        if(blood.bgdate[0])
          fprintf(fp,"# background_date := %s\n", blood.bgdate);
        fprintf(fp,"# background := %g [cps]\n", blood.background);
      }
      /* Study number */
      if(studynr_validity_check2(studynr, 1)) {
        fprintf(fp, "# studynr := %s\n", studynr);
      }
    
      /*
       *  Write the data lines
       */
      if(verbose>2) printf("Writing data lines\n");
      ptr=blood.data;
      while(ptr) {
        /* Don't print if sample time is negative */
        if(ptr->results.t<0.0) {ptr=ptr->next; continue;}
        /* Write the sample time */
        if(timeInMinutes) fprintf(fp, "%12.4f", ptr->results.t/60.0);
        else fprintf(fp, "%12.3f", ptr->results.t);
        /* Write the channel avg, or both separately, or only one */
        if(both_cols) {
          fprintf(fp, " %12.3f %12.3f\n", ptr->results.ch1, ptr->results.ch2);
        } else if(column==1) {
          fprintf(fp, " %12.3f\n", ptr->results.ch1);
        } else if(column==2) {
          fprintf(fp, " %12.3f\n", ptr->results.ch2);
        } else {
          fprintf(fp, " %12.3f\n", 0.5*(ptr->results.ch1+ptr->results.ch2));
        }
        ptr=ptr->next;
      }
      
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Try to find the date from a comment line. 
    
        Works on both Scanditronics and Allogg formats.
        @return Returns 1 if not found
     */
    int find_date(
      /** String to process. */
      char *str,
      /** Data structure to write date and time into. */
      struct Blood *blood,
      /** Verbose prints; set to 0 to print nothing in stdout. */
      int verbose
    ) {
      int c,yyyy,MM,dd,hh=0,mm=0,ss=0;
      if(str[6]=='-' && (str[8]=='-'||str[9]=='-')) {
        c=sscanf(str,"# %d-%d-%d %d:%d:%d",&yyyy,&MM,&dd,&hh,&mm,&ss);
        /* Found all fields */
        if(c==6 || c==3) { 
          blood->date.tm_year=yyyy-1900;
          blood->date.tm_mday=dd;
          blood->date.tm_mon=MM-1;
          blood->date.tm_hour=hh;
          blood->date.tm_min=mm;
          blood->date.tm_sec=ss;
          blood->date.tm_isdst=-1;
          if(verbose>0) 
            printf("find_date(): %04d-%02d-%02d %02d:%02d:%02d\n", yyyy, MM, dd, hh, mm, ss);
          return 0;
        }
      }
      return 1;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Try to find the isotope half-life from a comment line. 
    
        Works at least on the new Scanditronics format.
        @return Returns 1 if not found
     */
    int find_halflife(
      /** String to process. */
      char *str,
      /** Verbose prints; set to 0 to print nothing in stdout. */
      int verbose
    ) {
      char *cptr=strcasestr(str, "half-life: ");
      if(cptr!=NULL) {
        cptr+=11; 
      } else {
        cptr=strcasestr(str, "halflife: ");
        if(cptr!=NULL) {
          cptr+=10; 
        } else {
          return(1);
        }
      }
      if(verbose>0) printf("%s(): %s\n", __func__, cptr);
      double f=atof_dpi(cptr); if(!isnormal(f)) return(1);
      if(!isnan(halflife) && halflife>0.0 && !doubleMatch(halflife, f, 0.1)) {
        fprintf(stderr, "Error: different isotope in file and given by user.\n");
        exit(1);
      }
      halflife=f;
      int c=hlIsotopeFromHalflife(halflife); if(c<0) return(1);
      strcpy(isotope, hlIsotopeCode(c));
      return(1);
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Try to find the background radiation comment line.
        @return Returns 1 if not found
     */
    int find_background(
      /** String to process. */
      const char *str,
      /** Data structure to write result into. */
      struct Blood *blood
    ) {
      char tmp[11];
    
      while(isspace((int)*str) || *str=='#') str++;
      if(*str) {
        if(strncasecmp(str, "Background:", 11)==0) {
          str+=11;
          if(sscanf(str, "%f", &blood->background) == 1) {
            return 0;
          }
        } else if(strncasecmp(str, "Background on", 13)==0) {
          str+=13;
          if(sscanf(str,"%8s: %f", tmp, &blood->background) == 2) {
            sprintf(blood->bgdate, "%4.4s-%2.2s-%2.2s", tmp, tmp+4, tmp+6);
            return 0;
          } else if(sscanf(str, "%10s: %f", blood->bgdate, &blood->background)==2) {
            return 0;
          }
        }
      }
      return 1;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Read and parse datafile in the format used by the Scanditronics Automated
        Blood Measurement system. The program 'bsampler' outputs this format.
     */
    int read_scanditronics_data(
      /** Pointer to file name. */
      const char *inputfile,
      /** Data structure to write results into. */
      struct Blood *blood,
      /** Verbose prints; set to 0 to print nothing in stdout. */
      int verbose
    ) {
      char line[512],*str;
      struct DataList *points;
      struct BDataS *bdata;
      int datenotfound=1;
      int hlnotfound=1;
      FILE *fp;
      int n;
      //time_t t;
    
      if(verbose>0) printf("%s(%s)\n", __func__, inputfile);
      blood->bgdate[0]='\0';
      blood->background = 0.0;
    
      fp=fopen(inputfile,"r");
      if(!fp) {
        fprintf(stderr,"Error: cannot open \"%s\".\n",inputfile);
        perror("fopen");
        return 1;
      }
      /*
      t=0; if(gm_timez) blood->date=*gmtime(&t); else blood->date=*localtime(&t);
      */
      points=malloc(sizeof(struct DataList));
      points->prev=NULL; points->next=NULL;
      bdata=&points->scanditronics;
      memset(bdata,0,sizeof(struct BDataS));
      blood->data=points;
      /* Read data */
      while(fgets(line,sizeof(line),fp)) {
        str=line;
        while(*str && isspace((unsigned char)*str)) str++;
        if(*str=='#') {	/* Comment line; find date */
          if(datenotfound) datenotfound=find_date(str, blood, verbose);
          if(hlnotfound) hlnotfound=find_halflife(str, verbose);
          continue;
        }
        if(datenotfound) {
          fprintf(stderr, "Error: valid measurement date was not found in header.\n");
          fclose(fp); return 5;
        }
        if(*str=='*' || *str=='\0') continue;
        n=sscanf(str, "%lf %lf %lf %i %i %i %i %i %i %i",
              &bdata->time_of_day, &bdata->time_since_start, &bdata->meas_interval, 
              &bdata->pair1_coincid, &bdata->pair1_det1_cnts, &bdata->pair1_det2_cnts,
              &bdata->pair2_coincid, &bdata->pair2_det1_cnts, &bdata->pair2_det2_cnts, 
              &bdata->aux_cnts);
        if(n==10) {
          points->next=malloc(sizeof(struct DataList));
          points->next->prev=points;
          points=points->next;
          points->next=NULL;
          bdata=&points->scanditronics;
          memset(bdata,0,sizeof(struct BDataS));
        } else {
          if(n>0 && n!=7) fprintf(stderr,"Warning: malformed line!:{%s}\n",str);
        }
      }
      fclose(fp);
    
      if(bdata->time_of_day==0 && bdata->meas_interval==0) {
        /* Remove extra entry if any */
        struct DataList *prev=points->prev;
        free(points);
        points=prev;
        if(points) {
          points->next=NULL;
        } else {
          blood->data=NULL;
        }
      }
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Read and parse datafile in the format used by the Allogg Automated Blood Sampling system.
    
        The program 'bloodsampler' outputs this format.
        @return Returns 0 if successful and >0 in case of an error.
     */
    int read_allogg_data(
      /** Pointer to file name. */
      const char *inputfile,
      /** Data structure to write results into. */
      struct Blood *blood,
      /** Verbose prints; set to 0 to print nothing in stdout. */
      int verbose
    ) {
      char line[512],*str;
      struct DataList *points;
      struct BDataA *bdata;
      int datenotfound=1, bgnotfound=1, timenotfound=1;
      FILE *fp;
      int n;
      //time_t t;
    
      if(verbose>0) printf("%s(%s)\n", __func__, inputfile);
    
      blood->bgdate[0] = '\0';
      blood->background = -1.0E20;
    
      fp=fopen(inputfile,"r");
      if(!fp) {
        fprintf(stderr,"Error: cannot open \"%s\".\n",inputfile);
        perror("fopen");
        return 1;
      }
      /*
      t=0; if(gm_timez) blood->date=*gmtime(&t); else blood->date=*localtime(&t);
      */
      memset(&blood->date,0,sizeof(struct tm));
      points=malloc(sizeof(struct DataList));
      points->prev=NULL; points->next=NULL;
      bdata=&points->allogg;
      memset(bdata,0,sizeof(struct BDataA));
      blood->data=points;
      /* Read data */
      while(fgets(line,sizeof(line),fp)) {
        str=line;
        while(isspace((unsigned char)*str)) str++;
        if(*str=='#') { /* Comment line; find date */
          if(datenotfound) datenotfound=find_date(str, blood, verbose-2);
          if(bgnotfound) bgnotfound=find_background(str, blood);
          continue;
        }
        if(*str=='\0') continue;
        n=sscanf(str,"%lf %d %d", &bdata->time_since_start,&bdata->data[0],&bdata->data[1]);
        if(timenotfound && strlen(str)>6&&strlen(str)<9) {
                blood->date.tm_hour=(str[0]-'0')*10+(str[1]-'0');
                blood->date.tm_min=(str[2]-'0')*10+(str[3]-'0');
                blood->date.tm_sec=(str[4]-'0')*10+(str[5]-'0');
                blood->date.tm_isdst=-1; timenotfound=0;
        } else if(n<2) {
          fprintf(stderr,"Error: malformed line %sCorrect syntax is hhmmss.\n",str);
          return 2;
        } else {
          points->next=malloc(sizeof(struct DataList));
          points->next->prev=points;
          points=points->next;
          points->next=NULL;
          bdata=&points->allogg;
          memset(bdata,0,sizeof(struct BDataA));
        }
      }
      fclose(fp);
      if(bdata->time_since_start==0) { /* Remove extra entry if any */
        struct DataList *prev=points->prev;
        free(points);
        points=prev;
        if(points) {
          points->next=NULL;
        } else {
          blood->data=NULL;
        }
      }
    
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Read and parse datafile in the format used by the new Allogg Automated Blood Sampling system.
        @return Returns 0 if successful and >0 in case of an error.
     */
    int read_allogg2_data(
      /** Name of file to be read */
      char *inputfile,
      /** Pointer to struct where data is written */
      struct Blood *blood,
      /** Verbose level */
      int verbose,
      /** Pointer to string where error message is written; NULL if not needed. */
      char *msg
    ) {
      IFT ift;
      int ii, n, ret;
      int singles, coincidents;
      char tmp[1024];
      double f, tas;
      //time_t t;
      struct DataList *points;
      struct BDataA *bdata;
    
    
      if(verbose>0) printf("%s(%s)\n", __func__, inputfile);
      /* Check the arguments */
      if(inputfile==NULL || blood==NULL || strlen(inputfile)<1) {
        if(msg!=NULL) strcpy(msg, "program error"); 
        return(1);
      }
      blood->bgdate[0] = '\0';
      blood->background = -1.0E20;
    
      /* Read Allogg2 file into IFT struct */
      iftInit(&ift); ret=iftRead(&ift, inputfile, 0, 0);
      if(ret) {if(msg!=NULL) strcpy(msg, ift.status); iftEmpty(&ift); return(2);}
      if(verbose>=12) {iftWrite(&ift, "stdout", 0); fflush(stdout);}
    
      /* Check that this is TPC Allogg2 */
      strcpy(tmp, "System ID"); ii=iftGet(&ift, tmp, 0);
      if(ii<0) {
        if(msg!=NULL) strcpy(msg, "wrong format"); 
        iftEmpty(&ift); return(3);
      }
      if(strcmp(ift.item[ii].value, "ABSS09282")!=0) {
        if(msg!=NULL) strcpy(msg, "wrong sampler ID");
        iftEmpty(&ift); return(4);
      }
    
      /* Read the background */
      strcpy(tmp, "//Average background counts"); ii=iftFindNthKey(&ift, tmp, 1, 0);
      if(ii>=0) {
        if(verbose>3) printf("key := '%s'\n", ift.item[ii].key);
        /* Get background date from the key */
        ret=isdate(ift.item[ii].key+28);
        if(ret!=0) ret=isdate2(ift.item[ii].key+28, tmp);
        if(ret!=0) ret=isdate3(ift.item[ii].key+28, tmp);
        if(ret==0) {
          if(verbose>1) printf("background_date := %s\n", tmp);
          strcpy(blood->bgdate, tmp);
        } else if(verbose>0) printf("background_date not valid\n");
        /* Get background counts/sec from value */
        if(verbose>1) printf("background_value := %s\n", ift.item[ii].value);
        blood->background=atof(ift.item[ii].value);
      } else if(verbose>0) printf("background not found\n");
    
      /* Read the isotope (half-life) */
      strcpy(tmp, "HalfTime"); ii=iftGet(&ift, tmp, 0);
      if(ii>=0) {
        f=atof(ift.item[ii].value);
        ii=hlIsotopeFromHalflife(f);
        /* Use it only if user did not specify the isotope */
        if(strcmp(isotope, "UNKNOWN")==0) {
          strcpy(isotope, hlIsotopeCode(ii));
          halflife=hlFromIsotope(isotope);
          if(verbose) printf("half-life := %g min\n", halflife);
        }
      }
    
      /* Read study number */
      strcpy(tmp, "Run number"); ii=iftGet(&ift, tmp, 0);
      if(ii>=0) {
    #if(1)
        studynr_from_fname2(ift.item[ii].value, studynr, 0);
    #else
        int len, i, j;
        strncpy(studynr, ift.item[ii].value, MAX_STUDYNR_LEN);
        /* Remove possible extra tail */
        len=strlen(studynr);
        for(i=0; i<len; i++) if(!isalnum((int)studynr[i])) {strcpy(studynr, ""); len=0;}
        for(i=0; i<len; i++) if(!isalpha((int)studynr[i])) break;
        if(i<1 || i>5) {strcpy(studynr, ""); len=0;}
        for(j=0; (i+j)<len; j++) if(!isdigit((int)studynr[i+j])) {studynr[i+j]=(char)0; break;}
        if(j<1 || j>5) {strcpy(studynr, ""); len=0;}
    #endif
        if(verbose>0) printf("studynr := %s\n", studynr);
      }
    
    
      /* Find the first line which starts with date and time, that is, */
      /* the first count data line */
      for(ii=0; ii<ift.keyNr; ii++) if(!ift.item[ii].key[0]) {
        if(isdatetime(ift.item[ii].value, NULL)!=0) continue;
        if(verbose>7) printf("%s\n", ift.item[ii].value);
        break;
      } 
      /* Get start time from it */
      /*
      t=0; if(gm_timez) blood->date=*gmtime(&t); else blood->date=*localtime(&t);
      */
      //memset(&blood->date,0,sizeof(struct tm));
      //find_date(ift.item[ii].value, blood);
      ret=get_datetime(ift.item[ii].value, &(blood->date), verbose-4);
      if(ret!=0 && verbose>1) {
        fprintf(stderr, "Error %d in reading date and time in %s\n", ret, inputfile);
      }
    
      /* Read the count data lines */
      if(verbose>1) printf("reading count data\n");
      /* Initiate the list */
      points=malloc(sizeof(struct DataList));
      points->prev=NULL; points->next=NULL;
      bdata=&points->allogg;
      memset(bdata,0,sizeof(struct BDataA));
      blood->data=points;
      n=0;
      for(; ii<ift.keyNr; ii++) {
        /* Stop if item key is found */
        if(ift.item[ii].key[0]) break;
        /* Ignore comment lines */
        if(strncmp(ift.item[ii].value, "//", 2)==0) continue;
        if(strncmp(ift.item[ii].value, "#", 1)==0) continue;
        if(strncmp(ift.item[ii].value, ";", 1)==0) continue;
        /* Stop if line does not start with valid date and time */
        if(isdatetime(ift.item[ii].value, NULL)!=0) break;
        /* Read time after start, singles and coincidents */
        ret=sscanf(ift.item[ii].value+19, "%lf %d %d", &tas, &singles, &coincidents);
        if(ret!=3) continue;
        if(verbose>10 && n<4) printf("    %f  %d\n", tas, coincidents);
        /* Increase the list */
        if(n>0) {
          points->next=malloc(sizeof(struct DataList));
          points->next->prev=points;
          points=points->next;
          points->next=NULL;
          bdata=&points->allogg;
          memset(bdata,0,sizeof(struct BDataA));
        }
        /* Copy to list */
        bdata->time_since_start=tas;
        bdata->data[0]=coincidents;
        bdata->data[1]=0;
        n++;
      }
    
      iftEmpty(&ift);
      if(n<1) {if(msg!=NULL) strcpy(msg, "no valid count data"); return(9);}
      if(msg!=NULL) strcpy(msg, "ok");
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Calculate mid times and duration corrected activity values in Scanditronics format.
     */
    void correct_scanditronics(
      /** Pointer to data structure. */
      struct Blood *blood,
      /** Verbose level. */
      int verbose
    ) {
      struct DataList *data;
      struct tm date; //*date;
      time_t tim;
    
      if(verbose>0) printf("%s()\n", __func__);
    
      data=blood->data;
      tim=data->scanditronics.time_of_day;
      if(verbose>2) {
        char tmp[32];
        if(!ctime_r_int(&tim, tmp)) strcpy(tmp, "1900-01-01 00:00:00");
        printf("   %s\n", tmp);
      }
      if(verbose>3) {
        char buf[32];
        strftime(buf, 32, "%Y-%m-%d %H:%M:%S", &blood->date);
        printf("   blood->date: %s\n", buf);
      }
      /* Set time if it was not properly set in the comments */
      if(pump==2 || (blood->date.tm_hour==0 && blood->date.tm_min==0 && blood->date.tm_sec==0)) {
        //if(gm_timez) date=gmtime(&tim); else date=localtime(&tim);
        if(gmtime_r(&tim, &date)!=NULL) {
          blood->date.tm_hour=date.tm_hour;
          blood->date.tm_min=date.tm_min;
          blood->date.tm_sec=date.tm_sec;
        } else {
          fprintf(stderr, "Warning: invalid date in file.\n");
        }
      }
      while(data) {
        struct BDataS *d=&data->scanditronics;
        d->results.t=d->time_since_start+0.5*d->meas_interval;
        d->results.ch1=(double)d->pair1_coincid;
        d->results.ch2=(double)d->pair2_coincid;
        if(d->meas_interval>0) {
          d->results.ch1/=(double)d->meas_interval;
          d->results.ch2/=(double)d->meas_interval;
        }
        data=data->next;
      }
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Calculate measurement interval for Allogg data and set result values.
       @returns 0 on success, 1 otherwise.
     */
    int correct_allogg(
      /** Data structure for correction. */
      struct Blood *blood
    ) {
      struct DataList *data;
      double meas_interval=0.0;
    
      data=blood->data;
      if(data->next)
        meas_interval=data->next->allogg.time_since_start-data->allogg.time_since_start;
      else {
        printf("Error: Only one blood data line found, cannot calculate frame time.\n");
        return 1;
      }
      while(data) {
        struct BDataA *d=&data->allogg;
        d->meas_interval=meas_interval;
        d->results.t=d->time_since_start-0.5*meas_interval; /* '+'->'-' by VO */
    #if 0
        d->results.ch1=(d->data[0]-d->data[1])/d->meas_interval;
        d->results.ch2=(d->data[0]-d->data[1])/d->meas_interval;
    #else	/* Use only second discriminator */
        if(meas_interval == 0) {
          printf("Error: zero measuring interval found, would lead to divide by zero\n");
          return 1;
        }
        d->results.ch1=d->data[1]/d->meas_interval;
        d->results.ch2=d->data[1]/d->meas_interval;
    #endif
        data=data->next;
      }
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Calculate measurement interval for allogg2 data and set result values.
        @return Returns 0 on success, <>0 otherwise
     */
    int correct_allogg2(
      /** Blood data structure for correction. */
      struct Blood *blood,
      /** Verbose level. */
      int verbose
    ) {
      if(verbose>0) printf("%s()\n", __func__);
    
      struct DataList *data;
      double meas_interval=0.0;
    
      data=blood->data;
      if(data->next) {
        meas_interval=data->next->allogg.time_since_start-data->allogg.time_since_start;
        if(verbose>1) {
          printf("time_since_start := %g\n", data->allogg.time_since_start);
          printf("time_since_start_next := %g\n", data->next->allogg.time_since_start);
          printf("data0 := %d\n", data->allogg.data[0]);
          printf("data0 next := %d\n", data->next->allogg.data[0]);
        }
      } else {
        printf("Error: Only one blood data line found, cannot calculate frame time.\n");
        return 1;
      }
      while(data) {
        struct BDataA *d=&data->allogg;
        d->meas_interval=meas_interval;
        if(meas_interval == 0) {
          printf("Error: zero measuring interval found, would lead to divide by zero\n");
          if(verbose>1) {
            printf("time_since_start := %g\n", data->allogg.time_since_start);
            printf("time_since_start_next := %g\n", data->next->allogg.time_since_start);
          }
          return 1;
        }
        d->results.t=d->time_since_start+0.5*meas_interval;
        d->results.ch1=d->data[0]/d->meas_interval;
        d->results.ch2=d->data[0]/d->meas_interval;
        data=data->next;
      }
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Subtract background radiation values */
    void subtract_background(
      /** Blood data structure for correction. */
      struct Blood *blood
    ) {
      struct DataList *data=blood->data;
      while(data) {
        struct BDataResults *res=&data->results;
    
        res->ch1 -= blood->background;
        res->ch2 -= blood->background;
        data=data->next;
      }
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /** Reads calibration factors from file
    
      @param file absolute or relative path to file
      @param bloodt blood data measurement date 
      @param wcf for writing of well-counter/branching-ratio factor
      @param pumpf for writing of pump calibration factor
      @param pump pump number, in [1..3]
      @param isotope used isotope 
      @param caldate for writing of calibration date
      @param verbose verbose printing
    
      @return Returns 0 on success, <>0 otherwise
     */
    int get_calfactors(
      const char *file,
      time_t bloodt, 
      double *wcf, 
      double *pumpf,
      int pump, 
      char *isotope,
      char *caldate,
      int verbose
    ) {
      if(verbose>0) printf("%s(%s, ..., %d, %s, ...)\n", __func__, file, pump, isotope);
    
      const time_t too_old = 60*60*24*31*6;  /* 6 months */
      struct tm calibrdate; //*calibrdate;
      char line[512],*str;
      int i,yyyy,mm,dd;
      time_t calibrt, last_date=0;
      char sdate[25];
      FILE *fp;
    
      strcpy(sdate, "");
      if(pump<1) {
        fprintf(stderr,"Warning: pump number %d too low, set to 1\n",pump);
        pump=1;
      } else if(pump>MAX_PUMPS) {
        fprintf(stderr,"Warning: pump number %d too high, set to %d\n", pump, MAX_PUMPS);
        pump=MAX_PUMPS;
      }
    
      fp=fopen(file,"r");
      if(!fp) {
        fprintf(stderr,"Error: cannot open file %s\n",file);
        perror("fopen");
        return 1;
      }
      while(fgets(line,sizeof(line),fp)) {
        str=line;
        while(*str && isspace((unsigned char)*str)) str++;
        if(*str=='#' || *str=='\0') continue;
    
        double p[MAX_PUMPS]; /* Pumps */
        double w; /* Well-counter */
        i=sscanf(str,"%d-%d-%d %lf %lf %lf %lf %lf",
                 &yyyy, &mm, &dd, &p[0], &p[1], &p[2], &p[3], &w);
        if(i<5) {
          printf("Error: Malformed line in calibration file, cannot continue\n");
          fclose(fp); return 1;
        }
        if(pump>i-4) {
          printf("Error: no calibration for this blood sampler in %s\n", file);
          fclose(fp); return 2;
        }
        if(i==5) {w=p[1];} else if(i==6) {w=p[2];} else if(i==7) {w=p[3];}
    
        calibrdate.tm_mday=dd; calibrdate.tm_mon=mm-1;
        calibrdate.tm_year=yyyy-1900;
        calibrdate.tm_hour=calibrdate.tm_min=calibrdate.tm_sec=0;
        calibrdate.tm_isdst=-1;
        calibrt=timegm(&calibrdate);
        if(calibrt==-1) calibrt=0;
        if(calibrt>=last_date) last_date=calibrt;
    /*
        calibrt=time(NULL); calibrdate=localtime(&calibrt);
        calibrdate->tm_mday=dd; calibrdate->tm_mon=mm-1;
        calibrdate->tm_year=yyyy-1900;
        calibrdate->tm_isdst=-1;
        calibrt=mktime(calibrdate);
        if(calibrt>last_date) last_date=calibrt;
    */
    
        /* Check that calibration date is not later than measurement date */
        if(calibrt>bloodt) break;
        //if(calibrt!=(time_t)-1 && difftime(bloodt, calibrt)<0.0) break;
        strftime(sdate, 24, "%Y-%m-%d", &calibrdate);
    
        /* It was not, so set calibration factors and try the next line */
        *pumpf=p[pump-1];
        *wcf=w;
      }
    
      if(verbose>1) {
        printf("    pump_factor: %g\n", *pumpf);
        printf("    wc_factor: %g\n", *wcf);
      }
    
      /* Correct for branching ratio */
      if(strcasecmp(isotope, "C-11")==0) *wcf/=BRANCHING_C;
      else if(strcasecmp(isotope, "F-18")==0) *wcf/=BRANCHING_F; 
      else if(strcasecmp(isotope, "O-15")==0) *wcf/=BRANCHING_O; 
      else if(strcasecmp(isotope, "GE-68")==0) *wcf/=BRANCHING_Ge;
      else if(strcasecmp(isotope, "GA-68")==0) *wcf/=BRANCHING_Ga;
      else if(strcasecmp(isotope, "Cu-64")==0) *wcf/=BRANCHING_Cu64;
      else fprintf(stderr, "Warning: branching factor not included in calibration.\n");
    
      if(verbose>1) {
        printf("    wc_factor_including_branching_correction: %g\n", *wcf);
      }
     
      fclose(fp);
      /* Warn user if latest calibration date is older than 6 months */
      if(bloodt>last_date && bloodt-last_date > too_old) {
        fprintf(stderr, "Warning: check that %s contains the latest calibration coefficients.\n", file);
      }
    
      /* Set return value for calibration date */
      if(caldate!=NULL) strcpy(caldate, sdate);
      return 0;
    }
    /*****************************************************************************/
    
    /*****************************************************************************/
    /// @endcond