#include <math.h>

#include "amdms.h"
#include "amdmsFits.h"

#define amdmsLOWER_PE 0.50
#define amdmsUPPER_PE 1.30

#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))

static amdmsCOMPL amdmsCalculateMeanVariance(amdmsALGO_STAT_ENV *env,
					     amdmsFITS *file);
static amdmsCOMPL amdmsCalculateElectronicBiasQ1(amdmsALGO_STAT_ENV *env);
static amdmsCOMPL amdmsCalculateElectronicBiasQ4(amdmsALGO_STAT_ENV *env);
static amdmsCOMPL amdmsCreatePixelStatMap(amdmsALGO_STAT_ENV *env,
					  amdmsFITS *outfile,
					  amdmsFITS_FLAGS flags);
static amdmsCOMPL amdmsCreateElectronicBiasMap(amdmsALGO_STAT_ENV *env,
					       amdmsFITS *outfile,
					       amdmsFITS_FLAGS flags);
static amdmsCOMPL amdmsCreateBadPixelMap(amdmsFITS *outfile,
					 amdmsFITS_FLAGS flags,
					 amdmsDATA *bpm);
static amdmsCOMPL amdmsCreatePixelBiasMap(amdmsALGO_STAT_ENV *env,
					  amdmsFITS *outfile,
					  amdmsFITS_FLAGS flags);
static amdmsCOMPL amdmsCreateParticleEventMap(amdmsALGO_STAT_ENV *env,
					      amdmsFITS *outfile,
					      amdmsFITS_FLAGS flags);
static amdmsCOMPL amdmsCreateReadoutNoiseMap(amdmsALGO_STAT_ENV *env,
					     amdmsFITS *outfile,
					     amdmsFITS_FLAGS flags);
static amdmsCOMPL amdmsCreatePhotonNoiseMap(amdmsALGO_STAT_ENV *env,
					    amdmsFITS *outfile,
					    amdmsFITS_FLAGS flags);

static amdmsCOMPL amdmsDetectParticleEvents(amdmsPARTICLE_EVENT_SETUP *setup,
					    amdmsDATA_FILTER_SETUP *filter,
					    amdmsCALIBRATION_SETUP *calib,
					    amdmsFITS *file,
					    amdmsDATA *meanPixels,
					    amdmsDATA *varPixels);
static amdmsCOMPL amdmsFindPEs(amdmsPARTICLE_EVENT_SETUP *setup, amdmsDATA *var, int nValues);
static void amdmsAnalyzePE(amdmsPARTICLE_EVENT_SETUP *setup, int iPE, amdmsPIXEL var);
static void amdmsFindPEInterval(amdmsPARTICLE_EVENT_SETUP *setup, int iPE);
static void amdmsVerifyPE(amdmsPARTICLE_EVENT_SETUP *setup, int iPE);
static void amdmsRecalculateMeanVar(amdmsPARTICLE_EVENT_SETUP *setup,
				   amdmsDATA_FILTER_SETUP *filter,
				   int iPE,
				   amdmsDATA *meanPixels,
				   amdmsDATA *varPixels);
static void amdmsCalcMeanVar(amdmsPARTICLE_EVENT_SETUP *setup,
			    int iPE,
			    int from,
			    int to,
			    amdmsPIXEL *mean,
			    amdmsPIXEL *var);

amdmsCOMPL amdmsCreateStatisticsAlgo(amdmsALGO_STAT_ENV **env)
{
  amdmsALGO_ENV           *henv = NULL;
  amdmsALGO_STAT_ENV      *hhenv = NULL;

  /* allocate the structure if neccessary */
  if (*env == NULL) {
    hhenv = (amdmsALGO_STAT_ENV*)calloc(1L, sizeof(amdmsALGO_STAT_ENV));
    if (hhenv == NULL) {
      return amdmsFAILURE;
    }
    henv = &(hhenv->env);
    /* call the initialization of a simple algorithm environment */
    if (amdmsCreateAlgo(&henv) != amdmsSUCCESS) {
      free(hhenv);
      return amdmsFAILURE;
    }
    hhenv->allocated = 1;
    *env = hhenv;
  } else {
    /* call the initialization of a simple algorithm environment */
    hhenv = *env;
    henv = &(hhenv->env);
    if (amdmsCreateAlgo(&henv) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    hhenv->allocated = 0;
  }
  /* initialize only the additional members */
  hhenv->cf = 4.2;
  hhenv->ebNIter = 2;
  hhenv->ebLower = 10.0;
  hhenv->ebUpper = 10.0;
  hhenv->ebHFlag = amdmsTRUE;
  hhenv->ebVFlag = amdmsTRUE;
  hhenv->analyzeFlag = amdmsFALSE;
  hhenv->histoFlag = amdmsFALSE;
  hhenv->projMode = amdmsNO_PROJ;
  hhenv->histoData = NULL;
  amdmsInitData(&(hhenv->meanPixels));
  amdmsInitData(&(hhenv->varPixels));
  amdmsInitParticleEventSetup(&(hhenv->events));
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDestroyStatisticsAlgo(amdmsALGO_STAT_ENV **env)
{
  amdmsALGO_STAT_ENV      *henv = NULL;
  amdmsALGO_ENV           *hhenv = NULL;

  if (env == NULL) {
    return amdmsFAILURE;
  }
  if (*env == NULL) {
    return amdmsSUCCESS;
  }
  henv = *env;
  if (henv->histoData != NULL) {
    free(henv->histoData);
    henv->histoData = NULL;
  }
  amdmsFreeData(&(henv->meanPixels));
  amdmsFreeData(&(henv->varPixels));
  amdmsFreeParticleEventSetup(&(henv->events));
  hhenv = &(henv->env);
  amdmsDestroyAlgo(&hhenv);
  if (henv->allocated) {
    henv->allocated = amdmsFALSE;
    free(henv);
    *env = NULL;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsDoPixelStatistics(amdmsALGO_STAT_ENV *env)
{
  amdmsCOMPL        retVal = amdmsSUCCESS;
  amdmsFITS        *infile = NULL;
  int              iOutput;
  amdmsFITS        *outfile = NULL;
  amdmsBOOL         psmRequested = amdmsTRUE;
  amdmsBOOL         ebmRequested = amdmsFALSE;
  amdmsBOOL         pemRequested = amdmsFALSE;

  amdmsDebug(__FILE__, __LINE__, "amdmsDoPixelStatistics(...)");
  if (env->env.inFiles.nNames != 1) {
    /* no or too many filename(s) given but we need exactly one file */
    amdmsError(__FILE__, __LINE__, "wrong number of filenames given, exactly one input file is needed");
    return amdmsFAILURE;
  }
  if (amdmsReadAllMaps(&(env->env.calib)) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsOpenFitsFile(&infile, env->env.inFiles.names[0]) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsOpenData(infile, env->env.inFiles.flags[0], 1) != amdmsSUCCESS) {
    amdmsCloseFitsFile(&infile);
    return amdmsFAILURE;
  }
  for (iOutput = 0; iOutput < env->env.outFiles.nNames; iOutput++) {
    switch (env->env.outFiles.flags[iOutput].content) {
    case amdmsPIXEL_STAT_CONTENT:
    case amdmsPIXEL_BIAS_CONTENT:
    case amdmsREADOUT_NOISE_CONTENT:
    case amdmsPHOTON_NOISE_CONTENT:
      psmRequested = amdmsTRUE;
      break;
    case amdmsELECTRONIC_BIAS_CONTENT:
    case amdmsBAD_PIXEL_CONTENT:
      ebmRequested = amdmsTRUE;
      break;
    case amdmsPARTICLE_EVENT_CONTENT:
      pemRequested = amdmsTRUE;
      break;
    default:
      amdmsError(__FILE__, __LINE__,
		 "unsupported file content (%d)",
		 env->env.outFiles.flags[iOutput].content);
    }
  }
  if (psmRequested) {
    retVal = amdmsCalculateMeanVariance(env, infile);
    if (retVal != amdmsSUCCESS) {
      amdmsError(__FILE__, __LINE__, "could not calculate mean flux and temporal variance");
    }
  }
  if (pemRequested && (retVal == amdmsSUCCESS)) {
    retVal = amdmsDetectParticleEvents(&(env->events),
				      &(env->env.filter),
				      &(env->env.calib),
				      infile,
				      &(env->meanPixels),
				      &(env->varPixels));
    if (retVal != amdmsSUCCESS) {
      amdmsError(__FILE__, __LINE__, "could not detect particle events");
    }
  }
  if (ebmRequested && (retVal == amdmsSUCCESS)) {
    if (env->env.q4Flag) {
      retVal = amdmsCalculateElectronicBiasQ4(env);
    } else {
      retVal = amdmsCalculateElectronicBiasQ1(env);
    }
    if (retVal != amdmsSUCCESS) {
      amdmsError(__FILE__, __LINE__, "could not calculate electronic bias");
    }
  }
  /* generate all outputs */
  for (iOutput = 0; (iOutput < env->env.outFiles.nNames) && (retVal == amdmsSUCCESS); iOutput++) {
    if (env->env.outFiles.flags[iOutput].format == amdmsTEXT_FORMAT) {
      FILE *fp = NULL;
      int   i;
      fp = fopen(env->env.outFiles.names[iOutput], "w+");
      if (fp == NULL) {
	amdmsError(__FILE__, __LINE__, "could not create histogram file %s", env->env.outFiles.names[iOutput]);
	continue;
      }
      if (env->histoFlag) {
	for (i = 0; i < 65536; i++) {
	  if (env->histoData[i] != 0) {
	    fprintf(fp, "%d %d\n", i, env->histoData[i]);
	  }
	}
      }
      fclose(fp);
      continue;
    }
    retVal = amdmsCreateFitsFile(&outfile, env->env.outFiles.names[iOutput]);
    if (retVal != amdmsSUCCESS) break;
    retVal = amdmsCopyHeader(outfile, infile);
    if (retVal != amdmsSUCCESS) {
      amdmsCloseFitsFile(&outfile);
      break;
    }
    amdmsSetRegions(outfile, infile);
    switch (env->env.outFiles.flags[iOutput].content) {
    case amdmsPIXEL_STAT_CONTENT:
      retVal = amdmsCreatePixelStatMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    case amdmsELECTRONIC_BIAS_CONTENT:
      retVal = amdmsCreateElectronicBiasMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    case amdmsPIXEL_BIAS_CONTENT:
      retVal = amdmsCreatePixelBiasMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    case amdmsBAD_PIXEL_CONTENT:
      retVal = amdmsCreateBadPixelMap(outfile, env->env.outFiles.flags[iOutput], &(env->env.calib.bpmData));
      break;
    case amdmsPARTICLE_EVENT_CONTENT:
      retVal = amdmsCreateParticleEventMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    case amdmsREADOUT_NOISE_CONTENT:
      retVal = amdmsCreateReadoutNoiseMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    case amdmsPHOTON_NOISE_CONTENT:
      retVal = amdmsCreatePhotonNoiseMap(env, outfile, env->env.outFiles.flags[iOutput]);
      break;
    default:
      amdmsError(__FILE__, __LINE__,
		 "unsupported file content (%d)",
		 env->env.outFiles.flags[iOutput].content);
      retVal = amdmsFAILURE;
    }
    retVal = amdmsCloseFitsFile(&outfile);
  }
  amdmsCloseFitsFile(&infile);
  return retVal;
}

amdmsCOMPL amdmsCalculateMeanVariance(amdmsALGO_STAT_ENV *env, amdmsFITS *file)
{
  int          iPixel;
  int          iImage;
  amdmsPIXEL   nr;
  int          iX, iY;
  amdmsDATA     rawData;
  amdmsDATA     calData;
  amdmsDATA     countData;
  amdmsPIXEL   *dp = NULL;
  amdmsPIXEL   *fip = NULL;
  amdmsPIXEL   *lip = NULL;
  amdmsPIXEL   *mp = NULL;
  amdmsPIXEL   *vp = NULL;
  amdmsPIXEL   *cp = NULL;
  amdmsPIXEL    mean, var;
  double       n = 0.0;
  double       sx = 0.0;
  double       sxx = 0.0;

  /* check the validity of all arguments including the current state of the input file */
  if ((env == NULL) || (file == NULL)) {
    return amdmsFAILURE;
  }
  if ((file->currStateFile != amdmsFILE_OPENED_STATE)
      ||
      ((file->currStateHDU != amdmsCUBE_OPENED_STATE)
       &&
       (file->currStateHDU != amdmsTABLE_OPENED_STATE))) {
    return amdmsFAILURE;
  }
  /* initialize the temporary memory for raw and calibrated data */
  amdmsInitData(&rawData);
  amdmsInitData(&calData);
  amdmsInitData(&countData);
  /* adjust the data filter setup to the current input file */
  if (amdmsAdjustDataFilterSetup(&(env->env.filter), file) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (env->histoFlag) {
    env->histoData = (int*)calloc(65536, sizeof(int));
    if (env->histoData == NULL) {
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (histoData)");
      return amdmsFAILURE;
    }
  }
  /* allocate memory for the result */
  if (amdmsAllocateData(&(env->meanPixels), file->nx, file->ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsAllocateData(&(env->varPixels), file->nx, file->ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsAllocateData(&countData, file->nx, file->ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  /* initialize the result */
  mp = env->meanPixels.data;
  vp = env->varPixels.data;
  cp = countData.data;
  for (iPixel = 0; iPixel < file->nPixels; iPixel++) {
    *mp++ = 0.0;
    *vp++ = 0.0;
    *cp++ = 0.0;
  }
  /* the first pass is the mean pass */
  for (iImage = env->env.filter.ioiFrom; iImage <= env->env.filter.ioiTo; iImage++) {
    nr = (amdmsPIXEL)iImage;
    /* read one image into memory, the memory is also allocated */
    if (amdmsReadData(file, &rawData, iImage, 0) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    /* calibrate the raw data according to the setup, the memory is also allocated */
    if (amdmsCalibrateData(&(env->env.calib), &rawData, &calData) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    if (env->analyzeFlag && env->env.filter.aoiFlag) {
      amdmsCalcStat(&(env->env.calib), &calData, iImage,
		   env->env.filter.aoiX, env->env.filter.aoiY,
		   env->env.filter.aoiWidth, env->env.filter.aoiHeight,
		   &mean, &var);
      sx += mean;
      sxx += mean*mean;
      amdmsInfo(__FILE__, __LINE__, "  %4d %12.4f %12.4f",
	       iImage, mean, var);
    }
    switch (env->projMode) {
    case amdmsHOROZONTAL_PROJ:
      amdmsInfo(__FILE__, __LINE__, "#image %4d horizontal projection", iImage);
      for (iY = 0; iY < env->env.filter.aoiHeight; iY++) {
	amdmsCalcStat(&(env->env.calib), &calData, iImage,
		   env->env.filter.aoiX, env->env.filter.aoiY + iY, env->env.filter.aoiWidth, 1, &mean, &var);
	amdmsInfo(__FILE__, __LINE__, "  %4d %12.4f %12.4f", env->env.filter.aoiY + iY, mean, var);
      }
      break;
    case amdmsVERTICAL_PROJ:
      amdmsInfo(__FILE__, __LINE__, "#image %4d vertical projection", iImage);
      for (iX = 0; iX < env->env.filter.aoiWidth; iX++) {
	amdmsCalcStat(&(env->env.calib), &calData, iImage,
		     env->env.filter.aoiX + iX, env->env.filter.aoiY, 1, env->env.filter.aoiHeight, &mean, &var);
	amdmsInfo(__FILE__, __LINE__, "  %4d %12.4f %12.4f\n", env->env.filter.aoiX + iX, mean, var);
      }
      break;
    default:;
    }
    dp = calData.data;
    mp = env->meanPixels.data;
    cp = countData.data;
    fip = env->env.calib.pemDataFI.data;
    lip = env->env.calib.pemDataLI.data;
    for (iPixel = 0; iPixel < file->nPixels; iPixel++) {
      if ((nr > *lip) || (nr < *fip)) {
	*mp += *dp;
	*cp += 1.0;
      }
      dp++;
      mp++;
      cp++;
      fip++;
      lip++;
    }
    if (env->histoFlag) {
      int  v, x, y, width, height;
      if (env->env.filter.aoiFlag) {
	x = env->env.filter.aoiX;
	y = env->env.filter.aoiY;
	width = env->env.filter.aoiWidth;
	height = env->env.filter.aoiHeight;
      } else {
	x = 0;
	y = 0;
	width  = calData.nx;
	height = calData.ny;
      }
      for (iX = x; iX < (x + width); iX++) {
	for (iY = y; iY < (y + height); iY++) {
	  iPixel = calData.nx*iY + iX;
	  if (env->env.calib.bpmData.data[iPixel] != amdmsGOOD_PIXEL) continue;
	  if (amdmsIsPixelAffectedByPE(&(env->env.calib), iImage, iPixel)) continue;
	  v = (int)calData.data[iPixel];
	  if ((v >= 0) && (v < 65536)) env->histoData[v]++;
	}
      }
    }
  }
  for (iPixel = 0; iPixel < file->nPixels; iPixel++) {
    if (countData.data[iPixel] == 0.0) {
      env->meanPixels.data[iPixel] = 0.0;
    } else {
      env->meanPixels.data[iPixel] /= countData.data[iPixel];
    }
  }
  /* the second pass is the variance pass */
  if (env->env.outFiles.nNames != 0) {
    for (iImage = env->env.filter.ioiFrom; iImage <= env->env.filter.ioiTo; iImage++) {
      nr = (amdmsPIXEL)iImage;
      /* read one image into memory, the memory is also allocated */
      if (amdmsReadData(file, &rawData, iImage, 0) != amdmsSUCCESS) {
	return amdmsFAILURE;
      }
      /* calibrate the raw data according to the setup, the memory is also allocated */
      if (amdmsCalibrateData(&(env->env.calib), &rawData, &calData) != amdmsSUCCESS) {
	return amdmsFAILURE;
      }
      dp = calData.data;
      mp = env->meanPixels.data;
      vp = env->varPixels.data;
      fip = env->env.calib.pemDataFI.data;
      lip = env->env.calib.pemDataLI.data;
      for (iPixel = 0; iPixel < file->nPixels; iPixel++) {
	if ((nr > *lip) || (nr < *fip)) {
	  *vp += (*dp - *mp)*(*dp - *mp);
	}
	dp++;
	mp++;
	vp++;
	fip++;
	lip++;
      }
    }
    for (iPixel = 0; iPixel < file->nPixels; iPixel++) {
      if (countData.data[iPixel] <= 1.0) {
	env->varPixels.data[iPixel] = 1.0;
      } else {
	env->varPixels.data[iPixel] /= countData.data[iPixel] - 1.0;
      }
    }
  }
  if (env->analyzeFlag && env->env.filter.aoiFlag) {
    n = (double)(env->env.filter.ioiTo - env->env.filter.ioiFrom + 1);
    mean = (amdmsPIXEL)(sx/n);
    var = (amdmsPIXEL)(1.0/(n - 1.0)*(sxx - sx*sx/n));
    amdmsInfo(__FILE__, __LINE__, "area stat = %12.4f, %12.4f",
	      mean, var);
  }
  if (env->env.filter.aoiFlag) {
    amdmsCalcStat(&(env->env.calib), &(env->meanPixels), 0,
		 env->env.filter.aoiX, env->env.filter.aoiY,
		 env->env.filter.aoiWidth, env->env.filter.aoiHeight,
		 &mean, &var);
    amdmsInfo(__FILE__, __LINE__, "mean stat = %12.4f, %12.4f",
	      mean, var);
    amdmsCalcStat(&(env->env.calib), &(env->varPixels), 0,
		 env->env.filter.aoiX, env->env.filter.aoiY,
		 env->env.filter.aoiWidth, env->env.filter.aoiHeight,
		 &mean, &var);
    amdmsInfo(__FILE__, __LINE__, "var stat  = %12.4f, %12.4f",
	      mean, var);
  }
  if (env->env.filter.poiFlag) {
    iPixel = env->env.filter.poiX + env->env.filter.poiY*env->meanPixels.ny;
    amdmsNotice(__FILE__, __LINE__, " stat at %d %d = %12.4f %14.4f",
	       env->env.filter.poiX, env->env.filter.poiY,
	       env->meanPixels.data[iPixel],
	       env->varPixels.data[iPixel]);
  }
  amdmsFreeData(&rawData);
  amdmsFreeData(&calData);
  amdmsFreeData(&countData);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCalculateElectronicBiasQ1(amdmsALGO_STAT_ENV *env)
{
  amdmsDATA     meanData;
  int          iIter;
  int          iX;
  int          iY;
  int          iPixel;
  amdmsPIXEL    m;
  amdmsPIXEL    v;
  amdmsPIXEL    ll;
  amdmsPIXEL    ul;
  int          nGood;
  int          nBad;
  amdmsDATA    *ebmData = &(env->env.calib.ebmData);
  amdmsDATA    *bpmData = &(env->env.calib.bpmData);
  double      *y = NULL;
  double      *z = NULL;

  amdmsInitData(&meanData);
  if (amdmsCopyData(&meanData, &(env->meanPixels)) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  /* initialize the electronic bias map */
  amdmsSetData(ebmData, 0.0);
  y = (double*)calloc((size_t)meanData.ny, sizeof(double));
  if (y == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (y)");
    return amdmsFAILURE;
  }
  z = (double*)calloc((size_t)meanData.ny, sizeof(double));
  if (z == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (z)");
    return amdmsFAILURE;
  }
  /* calculate the horizontal projection */
  if (env->ebHFlag) {
    for (iY = 0; iY < meanData.ny; iY++) {
      amdmsCalcStat(&(env->env.calib), &meanData, 0, 0, iY, meanData.nx, 1, &m, &v);
      ll = m - env->ebLower;
      ul = m + env->ebUpper;
      for (iIter = 0; iIter < env->ebNIter; iIter++) {
	do {
	  nGood = 0;
	  nBad = 0;
	  for (iX = 0; iX < meanData.nx; iX++) {
	    iPixel = iY*meanData.nx + iX;
	    if (bpmData->data[iPixel] != amdmsBAD_PIXEL) {
	      bpmData->data[iPixel] = amdmsGOOD_PIXEL;
	      if ((meanData.data[iPixel] < ll) || (meanData.data[iPixel] > ul)) {
		bpmData->data[iPixel] = -1.0;
	      } else {
		nGood++;
	      }
	    } else {
	      nBad++;
	    }
	  }
	  if ((nBad > meanData.nx/2) || (nGood > meanData.nx/2)) {
	    break;
	  } else {
	    ll -= env->ebLower;
	    ul += env->ebUpper;
	  }
	} while (amdmsTRUE);
	amdmsCalcStat(&(env->env.calib), &meanData, 0, 0, iY, meanData.nx, 1, &m, &v);
	ll = m - env->ebLower;
	ul = m + env->ebUpper;
      }
      /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
      y[iY] = (double)m;
    }
  }
  /*
  if (amdmsSmoothDataByFiniteDiff1(y, z, 10.0, meanData.ny) != amdmsSUCCESS) {
    amdmsFreeData(&meanData);
    free(y);
    free(z);
    return amdmsFAILURE;
  }
  */
  for (iY = 0; iY < meanData.ny; iY++) {
    /* m = z[iY]; */
    m = y[iY];
    /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
    for (iX = 0; iX < meanData.nx; iX++) {
      iPixel = iY*meanData.nx + iX;
      if (bpmData->data[iPixel] == -1.0) {
	bpmData->data[iPixel] = amdmsBAD_PIXEL;
      }
      ebmData->data[iPixel] += m;
      meanData.data[iPixel] -= m;
    }
  }
  /* calculate the vertical projection */
  if (env->ebVFlag) {
    for (iX = 0; iX < meanData.nx; iX++) {
      amdmsCalcStat(&(env->env.calib), &meanData, 0, iX, 0, 1, meanData.ny, &m, &v);
      ll = m - env->ebLower;
      ul = m + env->ebUpper;
      for (iIter = 0; iIter < env->ebNIter; iIter++) {
	do {
	  nGood = 0;
	  nBad = 0;
	  for (iY = 0; iY < meanData.ny; iY++) {
	    iPixel = iY*meanData.nx + iX;
	    if (bpmData->data[iPixel] != amdmsBAD_PIXEL) {
	      bpmData->data[iPixel] = amdmsGOOD_PIXEL;
	      if ((meanData.data[iPixel] < ll) || (meanData.data[iPixel] > ul)) {
		bpmData->data[iPixel] = -1.0;
	      } else {
		nGood++;
	      }
	    } else {
	      nBad++;
	    }
	  }
	  if ((nBad > meanData.ny/2) || (nGood > meanData.ny/2)) {
	    break;
	  } else {
	    ll -= env->ebLower;
	    ul += env->ebUpper;
	  }
	} while (amdmsTRUE);
	amdmsCalcStat(&(env->env.calib), &meanData, 0, iX, 0, 1, meanData.ny, &m, &v);
	ll = m - env->ebLower;
	ul = m + env->ebUpper;
      }
      for (iY = 0; iY < meanData.ny; iY++) {
	iPixel = iY*meanData.nx + iX;
	if (bpmData->data[iPixel] == -1.0) {
	  bpmData->data[iPixel] = amdmsBAD_PIXEL;
	}
	ebmData->data[iPixel] += m;
	meanData.data[iPixel] -= m;
      }
    }
  }
  if (amdmsCopyData(&(env->meanPixels), &meanData) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  amdmsFreeData(&meanData);
  free(y);
  free(z);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCalculateElectronicBiasQ4(amdmsALGO_STAT_ENV *env)
{
  amdmsDATA     meanData;
  int          iIter;
  int          iX;
  int          iY;
  int          iPixel;
  amdmsPIXEL    m;
  amdmsPIXEL    v;
  amdmsPIXEL    ll;
  amdmsPIXEL    ul;
  int          nGood;
  int          nBad;
  amdmsDATA    *ebmData = &(env->env.calib.ebmData);
  amdmsDATA    *bpmData = &(env->env.calib.bpmData);
  int          nx1 = 0;
  int          nx2 = 0;
  double      *y1 = NULL;
  double      *y2 = NULL;
  double      *z = NULL;

  amdmsInitData(&meanData);
  if (amdmsCopyData(&meanData, &(env->meanPixels)) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  /* initialize the electronic bias map */
  amdmsSetData(ebmData, 0.0);
  nx1 = meanData.nx/2;
  nx2 = meanData.nx - nx1;
  y1 = (double*)calloc((size_t)meanData.ny, sizeof(double));
  if (y1 == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (y1)");
    return amdmsFAILURE;
  }
  y2 = (double*)calloc((size_t)meanData.ny, sizeof(double));
  if (y2 == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (y2)");
    return amdmsFAILURE;
  }
  z = (double*)calloc((size_t)meanData.ny, sizeof(double));
  if (z == NULL) {
    amdmsFatal(__FILE__, __LINE__, "memory allocation failure (z)");
    return amdmsFAILURE;
  }
  /* calculate the horizontal projection */
  if (env->ebHFlag) {
    for (iY = 0; iY < meanData.ny; iY++) {
      /* left quadrants */
      amdmsCalcStat(&(env->env.calib), &meanData, 0, 0, iY, nx1, 1, &m, &v);
      /* amdmsInfo(__FILE__, __LINE__, "EB(%d) = %f", iY, m); */
      ll = m - env->ebLower;
      ul = m + env->ebUpper;
      for (iIter = 0; iIter < env->ebNIter; iIter++) {
	do {
	  nGood = 0;
	  nBad = 0;
	  for (iX = 0; iX < nx1; iX++) {
	    iPixel = iY*meanData.nx + iX;
	    if (bpmData->data[iPixel] != amdmsBAD_PIXEL) {
	      bpmData->data[iPixel] = amdmsGOOD_PIXEL;
	      if ((meanData.data[iPixel] < ll) || (meanData.data[iPixel] > ul)) {
		bpmData->data[iPixel] = -1.0;
	      } else {
		nGood++;
	      }
	    } else {
	      nBad++;
	    }
	  }
	  if ((nBad > nx1/2) || (nGood > nx1/2)) {
	    break;
	  } else {
	    ll -= env->ebLower;
	    ul += env->ebUpper;
	  }
	} while (amdmsTRUE);
	amdmsCalcStat(&(env->env.calib), &meanData, 0, 0, iY, nx1, 1, &m, &v);
	ll = m - env->ebLower;
	ul = m + env->ebUpper;
      }
      /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
      y1[iY] = (double)m;
      /* right quadrants */
      amdmsCalcStat(&(env->env.calib), &meanData, 0, nx1, iY, nx2, 1, &m, &v);
      ll = m - env->ebLower;
      ul = m + env->ebUpper;
      for (iIter = 0; iIter < env->ebNIter; iIter++) {
	do {
	  nGood = 0;
	  nBad = 0;
	  for (iX = nx1; iX < nx1 + nx2; iX++) {
	    iPixel = iY*meanData.nx + iX;
	    if (bpmData->data[iPixel] != amdmsBAD_PIXEL) {
	      bpmData->data[iPixel] = amdmsGOOD_PIXEL;
	      if ((meanData.data[iPixel] < ll) || (meanData.data[iPixel] > ul)) {
		bpmData->data[iPixel] = -1.0;
	      } else {
		nGood++;
	      }
	    } else {
	      nBad++;
	    }
	  }
	  if ((nBad > nx2/2) || (nGood > nx2/2)) {
	    break;
	  } else {
	    ll -= env->ebLower;
	    ul += env->ebUpper;
	  }
	} while (amdmsTRUE);
	amdmsCalcStat(&(env->env.calib), &meanData, 0, nx1, iY, nx2, 1, &m, &v);
	ll = m - env->ebLower;
	ul = m + env->ebUpper;
      }
      /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
      y2[iY] = (double)m;
    }
  }
  /*
  if (amdmsSmoothDataByFiniteDiff1(y, z, 10.0, meanData.ny) != amdmsSUCCESS) {
    amdmsFreeData(&meanData);
    free(y);
    free(z);
    return amdmsFAILURE;
  }
  */
  for (iY = 0; iY < meanData.ny; iY++) {
    /* left quadrant */
    /* m = z[iY]; */
    m = y1[iY];
    /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
    for (iX = 0; iX < nx1; iX++) {
      iPixel = iY*meanData.nx + iX;
      if (bpmData->data[iPixel] == -1.0) {
	bpmData->data[iPixel] = amdmsBAD_PIXEL;
      }
      ebmData->data[iPixel] += m;
      meanData.data[iPixel] -= m;
    }
    /* right quadrant */
    /* m = z[iY]; */
    m = y2[iY];
    /* amdmsInfo(__FILE__, __LINE__, "EB(%f) = %f", (double)iY/(double)meanData.ny, m); */
    for (iX = nx1; iX < nx1 + nx2; iX++) {
      iPixel = iY*meanData.nx + iX;
      if (bpmData->data[iPixel] == -1.0) {
	bpmData->data[iPixel] = amdmsBAD_PIXEL;
      }
      ebmData->data[iPixel] += m;
      meanData.data[iPixel] -= m;
    }
  }
  /* calculate the vertical projection */
  if (env->ebVFlag) {
    for (iX = 0; iX < meanData.nx; iX++) {
      amdmsCalcStat(&(env->env.calib), &meanData, 0, iX, 0, 1, meanData.ny, &m, &v);
      ll = m - env->ebLower;
      ul = m + env->ebUpper;
      for (iIter = 0; iIter < env->ebNIter; iIter++) {
	do {
	  nGood = 0;
	  nBad = 0;
	  for (iY = 0; iY < meanData.ny; iY++) {
	    iPixel = iY*meanData.nx + iX;
	    if (bpmData->data[iPixel] != amdmsBAD_PIXEL) {
	      bpmData->data[iPixel] = amdmsGOOD_PIXEL;
	      if ((meanData.data[iPixel] < ll) || (meanData.data[iPixel] > ul)) {
		bpmData->data[iPixel] = -1.0;
	      } else {
		nGood++;
	      }
	    } else {
	      nBad++;
	    }
	  }
	  if ((nBad > meanData.ny/2) || (nGood > meanData.ny/2)) {
	    break;
	  } else {
	    ll -= env->ebLower;
	    ul += env->ebUpper;
	  }
	} while (amdmsTRUE);
	amdmsCalcStat(&(env->env.calib), &meanData, 0, iX, 0, 1, meanData.ny, &m, &v);
	ll = m - env->ebLower;
	ul = m + env->ebUpper;
      }
      for (iY = 0; iY < meanData.ny; iY++) {
	iPixel = iY*meanData.nx + iX;
	if (bpmData->data[iPixel] == -1.0) {
	  bpmData->data[iPixel] = amdmsBAD_PIXEL;
	}
	ebmData->data[iPixel] += m;
	meanData.data[iPixel] -= m;
      }
    }
  }
  if (amdmsCopyData(&(env->meanPixels), &meanData) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  amdmsFreeData(&meanData);
  free(y1);
  free(y2);
  free(z);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreatePixelStatMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  if (amdmsCreateData(outfile, flags, amdmsPIXEL_STAT_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->meanPixels.index = 1.0;
  if (amdmsWriteData(outfile, &(env->meanPixels), amdmsPIXEL_STAT_MEAN_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->varPixels.index = 2.0;
  if (amdmsWriteData(outfile, &(env->varPixels), amdmsPIXEL_STAT_VAR_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateElectronicBiasMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  if (amdmsCreateData(outfile, flags, amdmsELECTRONIC_BIAS_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->env.calib.ebmData.index = 1.0;
  if (amdmsWriteData(outfile, &(env->env.calib.ebmData), amdmsELECTRONIC_BIAS_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateBadPixelMap(amdmsFITS *outfile, amdmsFITS_FLAGS flags, amdmsDATA *bpm)
{
  int      nPixels = bpm->nx*bpm->ny;
  int      nGoodPixels = nPixels;
  int      nBadPixels = 0;
  int      iPixel;

  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (bpm->data[iPixel] == amdmsBAD_PIXEL) {
      nGoodPixels--;
      nBadPixels++;
    }
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR CATG", "CALIB",
			      "Observation category")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TECH", "INTERFEROMETRY",
			      "Observation technique")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordString(outfile,
			      "HIERARCH ESO DPR TYPE", "BADPIX",
			      "Observation type")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "GOODPIX", nGoodPixels,
			   "number of good pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsUpdateKeywordInt(outfile,
			   "BADPIX", nBadPixels,
			   "number of bad pixels")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsCreateData(outfile, flags, amdmsBAD_PIXEL_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  bpm->index = 1.0;
  if (amdmsWriteData(outfile, bpm, amdmsBAD_PIXEL_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreatePixelBiasMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  if (amdmsCreateData(outfile, flags, amdmsPIXEL_BIAS_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  env->meanPixels.index = 1.0;
  if (amdmsWriteData(outfile, &(env->meanPixels), amdmsPIXEL_BIAS_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateParticleEventMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  int      iPE;

  if (amdmsUpdateKeywordInt(outfile,
			   "NEVENTS", env->events.nSPE,
			   "number of particle events")
      != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsCreateData(outfile, flags, amdmsPARTICLE_EVENT_NROWS, 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  amdmsSetData(&(env->env.calib.pemDataFI), -1.0);
  amdmsSetData(&(env->env.calib.pemDataLI), -1.0);

  for (iPE = 0; iPE < env->events.nPE; iPE++) {
    if (env->events.pes[iPE].isBad) continue;
    if (!env->events.pes[iPE].isPE) continue;
    env->env.calib.pemDataFI.data[env->events.pes[iPE].idx] = env->events.pes[iPE].start + env->env.filter.ioiFrom;
    env->env.calib.pemDataLI.data[env->events.pes[iPE].idx] = env->events.pes[iPE].end + env->env.filter.ioiFrom;
  }
  env->env.calib.pemDataFI.index = 1.0;
  env->env.calib.pemDataLI.index = 2.0;
  if (amdmsWriteData(outfile, &(env->env.calib.pemDataFI), amdmsPARTICLE_EVENT_FIRST_IMG_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  if (amdmsWriteData(outfile, &(env->env.calib.pemDataLI), amdmsPARTICLE_EVENT_LAST_IMG_ROW, 0) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreateReadoutNoiseMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  int          iPixel, nPixels;
  amdmsPIXEL    mean;
  amdmsDATA     ronData;

  amdmsInitData(&ronData);
  amdmsAllocateData(&ronData, env->varPixels.nx, env->varPixels.ny);
  nPixels = env->varPixels.nx*env->varPixels.ny;
  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    ronData.data[iPixel] = env->cf*sqrt(env->varPixels.data[iPixel]);
  }
  if (env->env.filter.aoiFlag) {
    amdmsCalcStat(&(env->env.calib), &ronData, 0,
		 env->env.filter.aoiX, env->env.filter.aoiY,
		 env->env.filter.aoiWidth, env->env.filter.aoiHeight,
		 &mean, NULL);
    amdmsUpdateKeywordDouble(outfile, "RON", mean, 2, "Readout Noise in [e-]");
    amdmsNotice(__FILE__, __LINE__, "RON: = %12.2f e-", mean);
  }
  if (amdmsCreateData(outfile, flags, amdmsREADOUT_NOISE_NROWS, 1) != amdmsSUCCESS) {
    amdmsFreeData(&ronData);
    return amdmsFAILURE;
  }
  
  /*env->meanPixels.index = 1.0;  TLICHA: changed to next line after teleconf with MHEINIGER */ 
  ronData.index = 1.0;
  if (amdmsWriteData(outfile, &ronData, amdmsREADOUT_NOISE_ROW, 0) != amdmsSUCCESS) {
    amdmsFreeData(&ronData);
    return amdmsFAILURE;
  }
  amdmsFreeData(&ronData);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsCreatePhotonNoiseMap(amdmsALGO_STAT_ENV *env, amdmsFITS *outfile, amdmsFITS_FLAGS flags)
{
  int          iPixel, nPixels;
  amdmsPIXEL    mean;
  amdmsDATA     pnData;

  amdmsInitData(&pnData);
  amdmsAllocateData(&pnData, env->varPixels.nx, env->varPixels.ny);
  nPixels = env->varPixels.nx*env->varPixels.ny;
  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (env->meanPixels.data[iPixel] > 0.0) {
      /*if (env->varPixels.data[iPixel] > 0.0) {*/
      pnData.data[iPixel] = sqrt(env->cf*env->varPixels.data[iPixel]/env->meanPixels.data[iPixel]);
      /*pnData.data[iPixel] = env->varPixels.data[iPixel]/env->meanPixels.data[iPixel];*/
      /*pnData.data[iPixel] = env->meanPixels.data[iPixel]/env->varPixels.data[iPixel];*/
    } else {
      pnData.data[iPixel] = 0.0;
    }
  }
  if (env->env.filter.aoiFlag) {
    amdmsCalcStat(&(env->env.calib), &pnData, 0,
		 env->env.filter.aoiX, env->env.filter.aoiY,
		 env->env.filter.aoiWidth, env->env.filter.aoiHeight,
		 &mean, NULL);
    /*amdmsNotice(__FILE__, __LINE__, "PN: = %12.4f", mean);*/
    amdmsNotice(__FILE__, __LINE__, "CF: = %.4f", mean);
  }
  if (amdmsCreateData(outfile, flags, amdmsPHOTON_NOISE_NROWS, 1) != amdmsSUCCESS) {
    amdmsFreeData(&pnData);
    return amdmsFAILURE;
  }

  /*env->meanPixels.index = 1.0;  TLICHA: changed to next line after teleconf with MHEINIGER */ 
  pnData.index = 1.0;
  if (amdmsWriteData(outfile, &pnData, amdmsPHOTON_NOISE_ROW, 0) != amdmsSUCCESS) {
    amdmsFreeData(&pnData);
    return amdmsFAILURE;
  }
  amdmsFreeData(&pnData);
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsInitParticleEventSetup(amdmsPARTICLE_EVENT_SETUP *setup)
{
  if (setup == NULL) {
    return amdmsFAILURE;
  }
  setup->peFlag = amdmsFALSE; /* no detection requested */
  setup->peLimit = 0.0;
  setup->nPE = 0;
  setup->nSPE = 0;
  setup->nValues = 0;
  setup->pes = NULL;
  return amdmsSUCCESS;
}

amdmsCOMPL amdmsFreeParticleEventSetup(amdmsPARTICLE_EVENT_SETUP *setup)
{
   int i;
   if (setup == NULL) {
      return amdmsFAILURE;
   }
   
   if (setup->pes != NULL) {
      for(i=0; i<setup->nPE; i++){
         free(setup->pes[i].values);   
      }
   }
   setup->peFlag = amdmsFALSE; /* no detection requested */
   setup->peLimit = 0.0;
   setup->nPE = 0;
   setup->nSPE = 0;
   if (setup->pes != NULL) {
      free(setup->pes);
      setup->pes = NULL;
   }
   return amdmsSUCCESS;
}

amdmsCOMPL amdmsDetectParticleEvents(amdmsPARTICLE_EVENT_SETUP *setup,
				   amdmsDATA_FILTER_SETUP *filter,
				   amdmsCALIBRATION_SETUP *calib,
				   amdmsFITS *file,
				   amdmsDATA *meanPixels,
				   amdmsDATA *varPixels)
{
  int          iImage;
  int          iPE;
  amdmsDATA     rawData;
  amdmsDATA     calData;

  if (!setup->peFlag) {
    return amdmsSUCCESS;
  }
  /* try to mark all possible particle events */
  if (amdmsFindPEs(setup, varPixels, filter->ioiTo - filter->ioiFrom + 1) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  /* reload the data from all marked pixels */
  amdmsInitData(&rawData);
  amdmsInitData(&calData);
  for (iImage = filter->ioiFrom; iImage <= filter->ioiTo; iImage++) {
    if (amdmsReadData(file, &rawData, iImage, 0) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    if (amdmsCalibrateData(calib, &rawData, &calData) != amdmsSUCCESS) {
      return amdmsFAILURE;
    }
    for (iPE = 0; iPE < setup->nPE; iPE++) {
      setup->pes[iPE].values[iImage - filter->ioiFrom] = calData.data[setup->pes[iPE].idx];
    }
  }
  amdmsFreeData(&rawData);
  amdmsFreeData(&calData);
  for (iPE = 0; iPE < setup->nPE; iPE++) {
    amdmsAnalyzePE(setup, iPE, varPixels->data[setup->pes[iPE].idx]);
    if (!setup->pes[iPE].isBad) {
      amdmsFindPEInterval(setup, iPE);
    }
  }
  for (iPE = 0; iPE < setup->nPE; iPE++) {
    if (setup->pes[iPE].isBad) continue;
    if (setup->pes[iPE].isPE) continue;
    amdmsVerifyPE(setup, iPE);
  }
  for (iPE = 0; iPE < setup->nPE; iPE++) {
    if (setup->pes[iPE].isBad) continue;
    if (!setup->pes[iPE].isPE) continue;
    if (setup->pes[iPE].isUpToDate) continue;
    amdmsRecalculateMeanVar(setup, filter, iPE, meanPixels, varPixels);
  }
  return 1;
}

amdmsCOMPL amdmsFindPEs(amdmsPARTICLE_EVENT_SETUP *setup, amdmsDATA *var, int nValues)
{
  int       nx, ny;
  int       nPixels, iPixel;
  int       i, j;
  int       iX, iY;
  int       count;
  int       iPE;
  amdmsDATA  flags;

  if (!setup->peFlag) {
    return amdmsSUCCESS;
  }
  nx = var->nx;
  ny = var->ny;
  nPixels = nx*ny;
  amdmsInitData(&flags);
  if (amdmsAllocateData(&flags, nx, ny) != amdmsSUCCESS) {
    return amdmsFAILURE;
  }
  amdmsSetData(&flags, 0.0);
  setup->nPE = 0;
  setup->nValues = nValues;
  /* Markieren aller Pixel deren Varianz das Limit uebersteigt */
  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (var->data[iPixel] >= setup->peLimit) {
      flags.data[iPixel] = 1.0;
      setup->nPE++;
    }
  }
  /* Es sollen aber nur die Pixel uebrig bleiben, bei denen auch einige Nachbarn
   * eine erhoehte Varianz besitzen */
  for (iPixel = 0; iPixel < nPixels; iPixel++) {
    if (flags.data[iPixel] == 0.0) continue;
    count = 0;
    iX = iPixel%nx;
    iY = iPixel/nx;
    for (i = iX - 1; i <= iX + 1; i++) {
      if (i < 0) continue;
      if (i >= nx) continue;
      for (j = iY - 1; j <= iY + 1; j++) {
	if (j < 0) continue;
	if (j >= ny) continue;
	if (flags.data[j*nx + i] != 0.0) count++;
      }
    }
    if (count == 1) {
      flags.data[iPixel] = 0.0;
      setup->nPE--;
    }
  }
  amdmsInfo(__FILE__, __LINE__, "%d pixels with possible PEs", setup->nPE);
  if (setup->nPE != 0) {
    setup->pes = (amdmsPARTICLE_EVENT*)calloc((size_t)(setup->nPE), sizeof(amdmsPARTICLE_EVENT));
    if (setup->pes == NULL) {
      amdmsFatal(__FILE__, __LINE__, "memory allocation failure (pes)");
      setup->nPE = 0;
      amdmsFreeData(&flags);
      return amdmsFAILURE;
    }
    iPE = 0;
    for (iPixel = 0; iPixel < nPixels; iPixel++) {
      if (flags.data[iPixel] == 0.0) continue;
      setup->pes[iPE].idx = iPixel;
      setup->pes[iPE].x = iPixel%nx;
      setup->pes[iPE].y = iPixel/nx;
      setup->pes[iPE].isBad = amdmsFALSE;
      setup->pes[iPE].isPE = amdmsFALSE;
      setup->pes[iPE].isUpToDate = amdmsFALSE;
      setup->pes[iPE].nextPE = iPE;
      setup->pes[iPE].prevPE = iPE;
      setup->pes[iPE].start = -1;
      setup->pes[iPE].end = -1;
      setup->pes[iPE].values = (amdmsPIXEL*)calloc((size_t)nValues, sizeof(amdmsPIXEL));
      if (setup->pes[iPE].values == NULL) {
	amdmsFatal(__FILE__, __LINE__, "memory allocation failure (pes[%d].values)", iPE);
	setup->nPE = 0;
	amdmsFreeData(&flags);
	return amdmsFAILURE;
      }
      iPE++;
    }
  }
  amdmsFreeData(&flags);
  return amdmsSUCCESS;
}

void amdmsAnalyzePE(amdmsPARTICLE_EVENT_SETUP *setup, int iPE, amdmsPIXEL var)
{
  int              iPixel;
  int              start, split, end;
  amdmsPIXEL        mean1, var1;
  amdmsPIXEL        mean2, var2;

  iPixel = setup->pes[iPE].idx;
  start = 0;
  end = setup->nValues;
  split = end/2;
  /* 1. Versuch ist eine Halbierung der Daten um die echte Varianz herauszufinden */
  /* Als Test wird der Vergleich mit der globalen Varianz herangezogen */
  amdmsCalcMeanVar(setup, iPE, start, split, &mean1, &var1);
  amdmsCalcMeanVar(setup, iPE, split, end, &mean2, &var2);
  if ((var1 < amdmsLOWER_PE*var) && (var2 > amdmsUPPER_PE*var)) {
    /* das moegliche Ereignis liegt in der zweiten Haelfte der Daten, aber das muss */
    /* noch verifiziert werden indem der Aufteilungspunkt nach vorne verschoben wird. */
    split -= 20;
    amdmsCalcMeanVar(setup, iPE, start, split, &mean1, &var1);
    amdmsCalcMeanVar(setup, iPE, split, end, &mean2, &var2);
    if ((var1 < amdmsLOWER_PE*var) && (var2 > amdmsUPPER_PE*var)) {
      /* Die Lage des moeglichen Ereignisses in der zweiten Haelfte konnte */
      /* verifiziert werden! */
      setup->pes[iPE].start = split;
      setup->pes[iPE].end = end;
      setup->pes[iPE].mean = mean1;
      setup->pes[iPE].var = var1;
    } else {
      /* Die Lage des moeglichen Ereignisses konnte nicht verifiziert werden! */
      setup->pes[iPE].isBad = amdmsTRUE;
    }
  } else if ((var1 > amdmsUPPER_PE*var) && (var2 < amdmsLOWER_PE*var)) {
    /* das moegliche Ereignis liegt in der ersten Haelfte der Daten, aber das muss */
    /* noch verifiziert werden indem der Aufteilungspunkt nach hinten verschoben wird. */
    split += 20;
    amdmsCalcMeanVar(setup, iPE, start, split, &mean1, &var1);
    amdmsCalcMeanVar(setup, iPE, split, end, &mean2, &var2);
    if ((var1 > amdmsUPPER_PE*var) && (var2 < amdmsLOWER_PE*var)) {
      /* Die Lage des moeglichen Ereignisses in der ersten Haelfte konnte */
      /* verifiziert werden! */
      setup->pes[iPE].start = start;
      setup->pes[iPE].end = split;
      setup->pes[iPE].mean = mean2;
      setup->pes[iPE].var = var1;
    } else {
      /* Die Lage des moeglichen Ereignisses konnte nicht verifiziert werden! */
      setup->pes[iPE].isBad = amdmsTRUE;
    }
  } else {
    setup->pes[iPE].isBad = amdmsTRUE;
  }
  if (setup->pes[iPE].isBad) {
    amdmsDebug(__FILE__, __LINE__,
	      "no PE at pixel(%d, %d): mean = (%.4g | %.4g), var = (%.4g | %.4g | %.4g)",
	      setup->pes[iPE].x, setup->pes[iPE].y, mean1, mean2, var1, var, var2);
  } else {
    amdmsDebug(__FILE__, __LINE__,
	      "possible PE at pixel(%d, %d): mean = (%.4g | %.4g), var = (%.4g > %.4g > %.4g)",
	      setup->pes[iPE].x, setup->pes[iPE].y, mean1, mean2, var1, var, var2);
  }
}

void amdmsFindPEInterval(amdmsPARTICLE_EVENT_SETUP *setup, int iPE)
{
  int              iPixel;
  int              i;
  float            maxVal;
  int              maxIdx;

  iPixel = setup->pes[iPE].idx;
  /* Es wird der Punkt mit der maximalen Intensitaet gesucht */
  maxVal = setup->pes[iPE].values[setup->pes[iPE].start];
  maxIdx = setup->pes[iPE].start;
  for (i = setup->pes[iPE].start + 1; i < setup->pes[iPE].end; i++) {
    if (setup->pes[iPE].values[i] > maxVal) {
      maxVal = setup->pes[iPE].values[i];
      maxIdx = i;
    }
  }
  amdmsDebug(__FILE__, __LINE__, "  max intensity of %.4g at image number %d",
	    maxVal, maxIdx);
  setup->pes[iPE].start = maxIdx;
  setup->pes[iPE].end = maxIdx;
  for (i = setup->pes[iPE].start; i >= 0; i--) {
    if (setup->pes[iPE].values[i] < setup->pes[iPE].mean) break;
    setup->pes[iPE].start = i;
  }
  for (i = setup->pes[iPE].end; i < setup->nValues; i++) {
    if (setup->pes[iPE].values[i] < setup->pes[iPE].mean) break;
    setup->pes[iPE].end = i;
  }
  amdmsDebug(__FILE__, __LINE__, "  PE goes from %d to %d (%d images)",
	    setup->pes[iPE].start, setup->pes[iPE].end,
	    setup->pes[iPE].end - setup->pes[iPE].start + 1);
}

void amdmsVerifyPE(amdmsPARTICLE_EVENT_SETUP *setup, int iPE)
{
  int        i;
  int        x1, y1, x2, y2;

  x1 = setup->pes[iPE].x;
  y1 = setup->pes[iPE].y;
  for (i = 0; i < setup->nPE; i++) {
    if (i == iPE) continue;
    if (setup->pes[i].isBad) continue;
    x2 = setup->pes[i].x;
    y2 = setup->pes[i].y;
    if (x2 - x1 > 1) continue;
    if (x1 - x2 > 1) continue;
    if (y2 - y1 > 1) continue;
    if (y1 - y2 > 1) continue;
    if (setup->pes[iPE].start > setup->pes[i].end) break;
    if (setup->pes[iPE].end < setup->pes[i].start) break;
    setup->pes[iPE].isPE = amdmsTRUE;
    setup->pes[i].isPE = amdmsTRUE;
    if (setup->pes[i].nextPE == i) {
      /* there is no old chain of pixels at i */
      setup->pes[i].nextPE = iPE;
      setup->pes[i].prevPE = iPE;
      setup->pes[iPE].nextPE = i;
      setup->pes[iPE].prevPE = i;
    } else {
      /* there is an old chain of pixels at i */
      setup->pes[iPE].nextPE = setup->pes[i].nextPE;
      setup->pes[i].nextPE = iPE;
      setup->pes[iPE].prevPE = i;
      setup->pes[setup->pes[iPE].nextPE].prevPE = iPE;
    }
    return;
  }
  setup->pes[iPE].isBad = amdmsTRUE;
  amdmsDebug(__FILE__, __LINE__, "possible PE at pixel(%d, %d) not verified", x1, y1);
}

void amdmsRecalculateMeanVar(amdmsPARTICLE_EVENT_SETUP *setup,
			    amdmsDATA_FILTER_SETUP *filter,
			    int iPE,
			    amdmsDATA *meanPixels,
			    amdmsDATA *varPixels)
{
  int           i, j, k;
  int           x, y;
  int           start, end;
  int           count;
  int           hcount;
  amdmsPIXEL     mean, var;
  int           minX, maxX, minY, maxY;
  int           nx, ny;
  int           iPixel;
  
  i = iPE;
  nx = meanPixels->nx;
  ny = meanPixels->ny;
  start = setup->pes[i].start;
  end   = setup->pes[i].end;
  hcount = 1;
  minX = nx;
  maxX = -1;
  minY = ny;
  maxY = -1;
  for (j = setup->pes[i].nextPE; j != i; j = setup->pes[j].nextPE) {
    x = setup->pes[j].idx%nx;
    y = setup->pes[j].idx/nx;
    minX = MIN(minX, x);
    maxX = MAX(maxX, x);
    minY = MIN(minY, y);
    maxY = MAX(maxY, y);
    start = MIN(start, setup->pes[j].start);
    end = MAX(end, setup->pes[j].end);
    hcount++;
  }
  amdmsDebug(__FILE__, __LINE__, "detected PE between images %d and %d", start, end);
  for (j = iPE; hcount; j = setup->pes[j].nextPE) {
    iPixel = setup->pes[j].idx;
    setup->pes[j].start = start;
    setup->pes[j].end = end;
    count = 0;
    mean = 0.0;
    var = 0.0;
    for (k = 0; k < setup->nValues; k++) {
      if ((k >= start) && (k <= end)) continue;
      count++;
      mean += setup->pes[j].values[k];
    }
    mean /= (amdmsPIXEL)count;
    for (k = 0; k < setup->nValues; k++) {
      if ((k >= start) && (k <= end)) continue;
      var += (setup->pes[j].values[k] - mean)*(setup->pes[j].values[k] - mean);
    }
    var /= (amdmsPIXEL)(count - 1);
    amdmsDebug(__FILE__, __LINE__, "  pixel at (%d, %d) has now mean = %.4g, var = %.4g (old values %.4g, %.4g)", iPixel%nx, iPixel/nx, mean, var, meanPixels->data[iPixel], varPixels->data[iPixel]);
    meanPixels->data[iPixel] = mean;
    varPixels->data[iPixel] = var;
    setup->pes[j].isUpToDate = 1;
    hcount--;
  }
  amdmsInfo(__FILE__, __LINE__, "#defPE %d %d %d %d %d %d",
	   start + filter->ioiFrom, end + filter->ioiFrom,
	   minX - 10, minY - 10, maxX - minX + 20, maxY - minY + 20);
  setup->nSPE++;
}

void amdmsCalcMeanVar(amdmsPARTICLE_EVENT_SETUP *setup,
		     int iPE,
		     int from,
		     int to,
		     amdmsPIXEL *mean,
		     amdmsPIXEL *var)
{
  int     i;

  *mean = 0.0;
  for (i = from; i < to; i++) {
    *mean += setup->pes[iPE].values[i];
  }
  *mean /= (amdmsPIXEL)(to - from);
  *var = 0.0;
  for (i = from; i < to; i++) {
    *var += (setup->pes[iPE].values[i] - *mean)*(setup->pes[iPE].values[i] - *mean);
  }
  *var /= (amdmsPIXEL)(to - from - 1);
}

