/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle raw data. 
 *
 * The raw data is the data produced by the instrument during an exposure. This
 * data is stored as binary tables in a FIT file. 
 */
#define _POSIX_SOURCE 1
#define amdlibREMANENCE 0.00

/*
 * System Headers
 */
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "fitsio.h"
#include <time.h>

/*
 * Local Headers
 */
#include "amdlib.h"
#include "amdlibProtected.h"

/* List of keywords corresponding to instrument configuration */
static char *amdlibRequiredInsCfgKeywList[] =
{
    "HIERARCH ESO INS GRAT1 NAME",
    "HIERARCH ESO INS GRAT1 ORDER",
    "HIERARCH ESO INS GRAT1 WLEN",
    "HIERARCH ESO INS GRIS1 NAME",
    "HIERARCH ESO INS GRIS2 NAME",
    "HIERARCH ESO INS OPTI2 NAME",
    "HIERARCH ESO INS OPTI5 NAME",
    "HIERARCH ESO INS OPTI6 NAME",
    "HIERARCH ESO INS OPTI7 NAME",
    NULL
};

/*
 * Local functions
 */
static amdlibCOMPL_STAT amdlibReadTimeTag (fitsfile        *filePtr, 
                                           amdlibRAW_DATA  *rawData,
                                           int             firstFrame,
                                           int             nbFrames,
                                           amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibWriteTimeTag (fitsfile        *filePtr, 
                                            amdlibRAW_DATA  *rawData,
                                            amdlibERROR_MSG errMsg);
static void  amdlibFreeRawData(amdlibRAW_DATA *rawData);
static amdlibFRAME_TYPE amdlibFrameTypeStr2Id(char *frameTypeStr);
static amdlibCOMPL_STAT amdlibCheckDataCompatibility(
                                              amdlibDARK_DATA *dark,
                                              amdlibRAW_DATA  *rawData,
                                              amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibDiscardNullFrames(amdlibRAW_DATA  *rawData,
                                                amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibSubstractDark(amdlibDARK_DATA *dark,
                                            amdlibRAW_DATA  *rawData,
                                            amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibCalibrateImage(amdlibRAW_DATA *rawData,
                                             int            iFrame,
                                             amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibSubstractRemanence(amdlibRAW_DATA  *rawData,
                                                amdlibERROR_MSG errMsg);
/*
 * Public functions
 */
/**
 * Load all frames from raw data file.
 *
 * This function loads the raw data file and updates all the raw data structures
 * accordingly. This concerns information related to:
 *      \li the instrument configuration
 *      \li the spectral channel data
 *      \li the imaging detector 
 *      \li the imaging data
 *      \li the region data
 *      \li the variance data
 *
 * @param filename name of the FITS file containing raw data.
 * @param rawData structure where loaded information will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadRawData(const char      *filename,
                                   amdlibRAW_DATA  *rawData,
                                   amdlibERROR_MSG errMsg)
{
    amdlibLogTrace("amdlibLoadRawData()");
    
    return (amdlibLoadRawFrames(filename, rawData, amdlibFIRST_FRAME, 
                                amdlibALL_FRAMES, errMsg));
}

/**
 *
 * This function loads, as amdlibLoadRawData() function does, the raw data file,
 * but it does not allocated memory to store region data and, of course, does
 * not them. This function is usefull to get information such the number of
 * frames, the number of region or the frame type without loading all the file.
 *
 * @param filename name of the FITS file containing raw data.
 * @param rawData structure where raw data header information will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadRawDataHdr(const char      *filename,
                                      amdlibRAW_DATA  *rawData,
                                      amdlibERROR_MSG errMsg)
{
    struct stat     statBuf;
    char            fitsioMsg[256];
    fitsfile        *filePtr;
    int             status = 0;
    char            keywName[64];
    int             i;
    double          dit;
    int             keysExist = 0;
    int             moreKeys = 0;
    amdlibKEYW_LINE record;

    amdlibLogTrace("amdlibLoadRawDataHdr()");
    
    /* If raw data structure is not initailized, do it */
    if (rawData->thisPtr != rawData)
    {
        amdlibInitRawData(rawData);
    }

    /* First free previous allocated memory */
    amdlibFreeRawData(rawData);

    /* Check the file exists */
    if (stat(filename, &statBuf) != 0)
    {
        amdlibSetErrMsg("File '%.80s' does not exist", filename);
        return amdlibFAILURE;
    }

    /* Open FITS file */
    if (fits_open_file(&filePtr, filename, READONLY, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    /* True if FITS file only contains detector data; i.e. does not contains
     * ICS sub-system nor wave data binary table*/
    rawData->detDataOnly = amdlibTRUE;

    /* Main header : get all keywords */

    /* Get the number of keywords in HDU */
    keysExist=0;
    moreKeys=0;
    if (fits_get_hdrspace(filePtr, &keysExist, &moreKeys,
                          &status) != 0)
    {
        status = 0;
    }

    /* For each keyword */
    amdlibClearInsCfg(&rawData->insCfg);
    for (i = 1; i <= keysExist; i++)
    {
        /* Read keyword line i */
        if (fits_read_record(filePtr, i, record, &status) != 0)
        {
            status = 0;
        }
        else
        {
            /* If it is not a simple comment */
            if (strstr(record, "COMMENT ") == NULL)
            {
                /* Store keyword */
                if (amdlibAddInsCfgKeyword(&rawData->insCfg,
                                           record, errMsg) == amdlibFAILURE)
                {
                    return amdlibFAILURE;
                }
            }

            /* If there is at least one instrument keyword, it is assumed that
             * ICS keywords have been merged to FITS header */
            if (strstr(record, "HIERARCH ESO INS") != NULL)
            {
                rawData->detDataOnly = amdlibFALSE;
            }
        }
    }

    /* Check the mandatory keywords are defined in header */
    if (rawData->detDataOnly == amdlibFALSE)
    {
        i = 0;
        while (amdlibRequiredInsCfgKeywList[i] != 0)
        {
            amdlibKEYWORD keyword;
            strcpy(keyword.name, amdlibRequiredInsCfgKeywList[i]);
            if (fits_read_key (filePtr, TSTRING, (char*)keyword.name,
                               keyword.value, NULL, &status) != 0)
            {
                amdlibGetFitsError(amdlibRequiredInsCfgKeywList[i])
                fits_close_file(filePtr, &status);
                return amdlibFAILURE;
            }
            i++;
        }
    }

    /* Main header : get P2VM id */
    strcpy (keywName, "HIERARCH ESO OCS P2VM ID");
    if (fits_read_key (filePtr, TINT, keywName, &rawData->p2vmId,
                       NULL, &status) != 0) 
    {
        /* No reference to P2VM */
        rawData->p2vmId = 0;
        status = 0;
    }

    /* Main header : get DIT */
    sprintf (keywName, "HIERARCH ESO DET DIT");
    if (fits_read_key (filePtr, TDOUBLE, (char*)keywName,
                       &dit, NULL, &status) != 0)
    {
        dit = 0.0;
        status = 0;
    }
    rawData->expTime = dit;

    /* Determine the frame type */
    if (rawData->detDataOnly == amdlibFALSE)
    {
        /* Main header : get frame type */
        char            frameTypeStr[64];
        strcpy (keywName, "HIERARCH ESO OCS DET FRAM TYPE");
        memset (frameTypeStr, '\0', sizeof(frameTypeStr));
        if (fits_read_key (filePtr, TSTRING, keywName, frameTypeStr,
                           NULL, &status) != 0) 
        {
            status = 0;
        }

        /* If OCS.DET.FRAM.TYPE keyword is specified, use it */
        if (strlen(frameTypeStr) != 0)
        {
            rawData->frameType = amdlibFrameTypeStr2Id(frameTypeStr);
        }
        else
        {
            /* Patch missing SKY type in OCS.DET.FRAM.TYPE. look DPR.TYPE instead */
            strcpy (keywName, "HIERARCH ESO DPR TYPE");
            if (fits_read_key (filePtr, TSTRING, keywName, frameTypeStr,
                               NULL, &status) != 0) 
            {
                status = 0;
            }
            if (strlen(frameTypeStr) != 0)
            {
                if (strcmp(frameTypeStr, "SKY") == 0)
                {
                    rawData->frameType = amdlibSKY_FRAME;
                }
            }
            else
            {
                amdlibSetErrMsg("Obsolete input data - "
                                "no frame type specified in '%.80s'", filename);
                rawData->frameType = amdlibUNKNOWN_FRAME;
            }
        }
    }
    else
    {
        rawData->frameType = amdlibUNKNOWN_FRAME;
    }

    /* Read 'AMBER wave data' */
    if (rawData->detDataOnly == amdlibFALSE)
    {
        if (amdlibReadWaveData(filePtr,
                               &rawData->waveData, errMsg) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    else
    {
        memset(&rawData->waveData, '\0', sizeof(amdlibWAVEDATA));
    }

    /* Read 'imaging detector header' */
    if (amdlibReadImagingDetectorHdr(filePtr, &rawData->imagingDetector,
                                    errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Set the number of spectral rows and column according to
     * the number of regions
     */
    rawData->nbRegions = rawData->imagingDetector.nbRegions;
    switch(rawData->nbRegions)
    {
        case 1:
            rawData->nbRows=1;
            rawData->nbCols=1;
            break;
        case 4:
            rawData->nbRows=1;
            rawData->nbCols=4;
            break;
        case 8:
            rawData->nbRows=2;
            rawData->nbCols=4;
            break;
        case 12:
            rawData->nbRows=3;
            rawData->nbCols=4;
            break;
        case 5:
            rawData->nbRows=1;
            rawData->nbCols=5;
            break;
        case 10:
            rawData->nbRows=2;
            rawData->nbCols=5;
            break;
        case 15:
            rawData->nbRows=3;
            rawData->nbCols=5;
            break;
        default:
            fits_close_file(filePtr, &status);
            amdlibSetErrMsg("Inconsistent number of regions '%d' ",
                            rawData->nbRegions);
            return amdlibFAILURE;

    }

    /* Read 'region informations' in imaging detector table */
    if (amdlibReadRegionInfo(filePtr, &rawData->region, rawData->nbRegions,
                            rawData->imagingDetector.maxTel,
                            errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Read 'imaging data header' */
    if (amdlibReadImagingDataHdr(filePtr, &rawData->imagingData,
                                errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Set the number of frames */
    rawData->nbFrames = rawData->region[0].dimAxis[2];

    /* Data is not yet loaded, and there is not calibrated */
    rawData->dataLoaded = amdlibFALSE;
    rawData->dataCalibrated = amdlibFALSE;

    /* Close the FITS file */
    if (fits_close_file(filePtr, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }
    return amdlibSUCCESS;
}

/**
 * Load N frames from raw data file.
 *
 * This function loads, as amdlibLoadRawData() function does, the raw data file,
 * but only the requested frames. The amdlibFIRST_FRAME macro can be used to
 * specify the first frame of the files, as well as the amdlibALL_FRAMES macro
 * to specify all remaining frames from the first specified frame to the last
 * one of the file.
 *
 * @param filename name of the FITS file containing raw data.
 * @param rawData structure where loaded information will be stored
 * @param firstFrame index of the first frame to load 
 * @param nbFrames number of frames to load 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadRawFrames(const char      *filename,
                                     amdlibRAW_DATA  *rawData,
                                     int             firstFrame,
                                     int             nbFrames,
                                     amdlibERROR_MSG errMsg)
{
    char            fitsioMsg[256];
    fitsfile        *filePtr;
    int             status = 0;
    int i;

    amdlibLogTrace("amdlibLoadRawFrames()");
    
    /* First load header of raw file */
    if (amdlibLoadRawDataHdr(filename, rawData, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Re-open FITS file */
    if (fits_open_file(&filePtr, filename, READONLY, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    /* Check if all frames have to be read */
    if (nbFrames == amdlibALL_FRAMES)
    {
        nbFrames = rawData->nbFrames - firstFrame + 1;
    }

    /* Read 'region data' in imaging data table */
    if (amdlibReadRegionData(filePtr, rawData->region,
                            rawData->nbRegions,
                            firstFrame, nbFrames,
                            errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    /* Set the number of frames */
    rawData->nbFrames = nbFrames;

    /* Read 'array geometry'  */
    if (amdlibReadArrayGeometry(filePtr, &rawData->arrayGeometry,
                                errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    
    /* Read 'time data' in imaging data table */
    if (amdlibReadTimeTag(filePtr, rawData,
                          firstFrame, nbFrames,
                          errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    
    /* Data is now loaded, but not yet calibrated */
    rawData->dataLoaded = amdlibTRUE;
    rawData->dataCalibrated = amdlibFALSE;

    /* Close the FITS file */
    if (fits_close_file(filePtr, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    if (amdlibAllocateRegions(&rawData->variance, 
                              rawData->nbRegions) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for variance map");
        return amdlibFAILURE;
    }
    /* Copy all region information */
    for (i = 0; i < rawData->nbRegions; i++)
    {
        int regionSize;
        /* Copy region information to variance */
        memcpy(&rawData->variance[i], &rawData->region[i], 
               sizeof(amdlibREGION));
        /* Allocate memory for variance */
        regionSize = rawData->region[i].dimAxis[0] * 
            rawData->region[i].dimAxis[1] * 
            rawData->region[i].dimAxis[2];
        rawData->variance[i].data  = calloc(regionSize, sizeof(*(rawData->variance[i].data)));
        if (rawData->variance[i].data == NULL)
        {
            amdlibSetErrMsg("Could not allocate memory for data of variance #%d",
                            i);
            return amdlibFAILURE;
        }
    }
    

    return amdlibSUCCESS;
}

/**
 * Release memory allocated to store raw data and reset the structure members to
 * zero.
 *
 * @param rawData structure where raw data is stored
 */
void amdlibReleaseRawData(amdlibRAW_DATA *rawData)
{
    amdlibLogTrace("amdlibReleaseRawData()");
    
    amdlibFreeRawData(rawData);
    memset (rawData, '\0', sizeof(amdlibRAW_DATA));
}

#define amdlibSTART_MASK_COL 5
/**
 * Calculate and remove the global bias, deduced from the masked pixels.
 * The bias varies with time, and is especially high after a time when the camera
 * is idle. Normally the AMBER camera should be ALWAYS acquiring frames.
 * It can be estimated grossly as the median value of the 
 * masked pixels, until a real bias measurement is provided.
 * The bias being due (mostly) to the temperature drift of the on-chip converters,
 * is dependent on the pixels's position. 
 * 
 * This function should normally not be used, if the bias does not change between the
 * dark and the observation, because the dark's rms is the sum of the dark photon noise
 * plus readout noise. Substracting dark and adding this dark noise to the sigma2 is all
 * we need. However, the bias is changing very rapidly in the first 20 or 50 frames at
 * the time of writing.  So perhaps estimating the bias frame by frame
 * and removing does a good job in the case where there are less than, say, 50 frames.
 *
 * @param rawData raw data. 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibRemoveGlobalBias(
                              amdlibRAW_DATA        *rawData,
                              amdlibERROR_MSG       errMsg)
{
    amdlibDOUBLE globalBias;
    int   iRow, iCol;
    int   iFrame;
    int   iRegion,darkRegion, darkRegionSize;
    int   regionHeight, regionWidth, regionSize;
    int   iX, iY;
    int nbGoodPixels;
    amdlibDOUBLE* work;
    double rms;
    amdlibDOUBLE **rawPtr, **variancePtr;
    amdlibDOUBLE **badPixels;

    amdlibLogTrace("amdlibRemoveGlobalBias()");
    
    for (iRow = 0; iRow < rawData->nbRows; iRow++)
      {
	/* Take first region of each row corresponding to the shielded area */
	darkRegion = iRow * rawData->nbCols;
	darkRegionSize = rawData->region[darkRegion].dimAxis[0] *
	  rawData->region[darkRegion].dimAxis[1];
	/* Take the part of the bad pixel map corresponding to the current 
	 * region */
	badPixels = amdlibGetBadPixelMapRegion(
					       rawData->region[darkRegion].corner[0] - 1,
					       rawData->region[darkRegion].corner[1] - 1,
					       rawData->region[darkRegion].dimAxis[0],
					       rawData->region[darkRegion].dimAxis[1],
					       errMsg);
	if (badPixels == NULL)
	  {
	    return amdlibFAILURE;
	  }
	    
	for (iFrame = 0; iFrame < rawData->region[darkRegion].dimAxis[2]; iFrame++)
	  {
	    rawPtr = amdlibWrap2DArrayDouble
	      (&rawData->region[darkRegion].data[darkRegionSize*iFrame], 
	       rawData->region[darkRegion].dimAxis[0],
	       rawData->region[darkRegion].dimAxis[1],
	       errMsg);
	    if (rawPtr == NULL)
	      {
		return amdlibFAILURE;
	      }
	    /*initialise number of good pixels*/
	    nbGoodPixels = 0;
	    /* allocate the work area */
	    work=calloc(darkRegionSize,sizeof(double));
	    
	    /* copy the valid pixels (not bad, not in the amdlibSTART_MASK_COL first pixels)
	       in a work array to evaluate the global bias  */
	    for (iY = 0; iY <  rawData->region[darkRegion].dimAxis[1]; iY++)
	      {
		/* Skip the first amdlibSTART_MASK_COL pixels of each line. These
		 * pixels are too noisy, and are not significant when computing
		 * bias. 
		 */
		/* skip START_MASK_COL pixels */
		for (iX = amdlibSTART_MASK_COL ; 
		     iX < rawData->region[darkRegion].dimAxis[0]; 
		     iX++)
		  {
		    if ((badPixels[iY][iX] == amdlibGOOD_PIXEL_FLAG))
		      {
			work[nbGoodPixels++] = rawPtr[iY][iX];
		      }
		  }
	      }
	    /* evaluate bias in some fashion... */
/* 	    globalBias = amdlibQuickSelectDble(work,nbGoodPixels); */
	    globalBias = amdlibAvgValues(nbGoodPixels,work);
	    /* evaluate grossly detector noise, to be set in the "noise" section, in case
	     * there is no dark measurement to get a pixel-by-pixel value. Noise is estimated
	     * on the masked area as the bias */
	    rms = amdlibRmsValues(nbGoodPixels,work);
	    free(work);
	    amdlibFree2DArrayDoubleWrapping(rawPtr);
	    /* substract this value directly from ALL REGIONS of same Row of Raw data. 
	       No use to remap bad pixels here */
	    for (iCol = 0; iCol < rawData->nbCols; iCol++)
	      {
		iRegion = iRow * rawData->nbCols + iCol;
		regionWidth = rawData->region[iRegion].dimAxis[0];
		regionHeight = rawData->region[iRegion].dimAxis[1];
		regionSize = regionWidth * regionHeight;
		rawPtr = amdlibWrap2DArrayDouble(
						 &rawData->region[iRegion].data[regionSize*iFrame], 
						 rawData->region[iRegion].dimAxis[0],
						 rawData->region[iRegion].dimAxis[1],
						 errMsg);
		if (rawPtr == NULL)
		  {
		    amdlibFree2DArrayDouble(badPixels);
		    return amdlibFAILURE;
		  }
		variancePtr = amdlibWrap2DArrayDouble(
						 &rawData->variance[iRegion].data[regionSize*iFrame], 
						 rawData->variance[iRegion].dimAxis[0],
						 rawData->variance[iRegion].dimAxis[1],
						 errMsg);
		if (variancePtr == NULL)
		  {
		    amdlibFree2DArrayDouble(badPixels);
		    amdlibFree2DArrayDoubleWrapping(rawPtr);
		    return amdlibFAILURE;
		  }
		for (iY = 0; iY < regionHeight; iY++)
		  {
		    for (iX = 0; iX < regionWidth; iX++)
		      {
                          /* remove offset */
                          rawPtr[iY][iX] -= globalBias;
                          /* store estimated sigma2det in variance */
                          variancePtr[iY][iX] = rms*rms;
		      }
		  }
		amdlibFree2DArrayDoubleWrapping(rawPtr);
		amdlibFree2DArrayDoubleWrapping(variancePtr);
	      }
	  }
	amdlibFree2DArrayDouble(badPixels);
      }
    return amdlibSUCCESS;
}


/**
 * Calibrate raw data; i.e. compensate for detector effect 
 *
 * This function applies cosmetic corrections on raw data produced by the AMBER
 * instrument. This consists for each pixel to :
 *      \li discard null frames
 *      \li substract the dark (compensation for detector effects),
 *      \li divide the result by flat field,
 *      \li multiply the result by the gain to get photoelectron counts,
 *      \li substract remanence.
 *
 * @param dark to be substracted to raw data
 * @param rawData structure where raw data is stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned. */
amdlibCOMPL_STAT amdlibCalibrateRawData(amdlibDARK_DATA *dark,
                                        amdlibRAW_DATA  *rawData,
                                        amdlibERROR_MSG errMsg)
{
    int   iFrame;
    int   iRow, iCol, iRegion;

    amdlibLogTrace("amdlibCalibrateRawData()");
    
    /* Check raw data structure contains data */
    if (rawData->dataLoaded == amdlibFALSE)
    {
        amdlibSetErrMsg("The raw data structure does not "
                        "contain data. Check call to amdlibLoadRawData()");
        return amdlibFAILURE;
    }

    /* Check raw data has been already calibrated */
    if (rawData->dataCalibrated == amdlibTRUE)
    {
        amdlibSetErrMsg("The data has been already calibrated");
        return amdlibFAILURE;
    }

    /* Check compatibity of raw data and dark map */
    if (amdlibCheckDataCompatibility(dark, rawData, 
                                     errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }
    
    /* Fix eventual start pix 0 to 1 in region header */
    for (iRow = 0; iRow < rawData->nbRows; iRow++)
    {
        for (iCol = 0; iCol < rawData->nbCols; iCol++)
        {
            iRegion = iRow * rawData->nbCols + iCol;
            
            if (rawData->region[iRegion].corner[0] == 0) 
            {
                rawData->region[iRegion].corner[0]  = 1;
            }
            if (rawData->region[iRegion].corner[1] == 0) 
            {
                rawData->region[iRegion].corner[1]  = 1;
            }
            if (rawData->variance[iRegion].corner[0] == 0) 
            {
                rawData->variance[iRegion].corner[0]  = 1;
            }
            if (rawData->variance[iRegion].corner[1] == 0) 
            {
                rawData->variance[iRegion].corner[1]  = 1;
            }
        }
    }

    /* Check for NULL Frames, discard iFFskip first frames */
    if (amdlibDiscardNullFrames(rawData, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

/*     pixel bias does not exist yet. Here is where it should be removed: */
/*     /\* Subtract Pixel Bias Map directly on raw data *\/ */
/*     if (amdlibSubtractPixelBias(pixelBias, rawData, errMsg) != amdlibSUCCESS) */
/*     { */
/*         return amdlibFAILURE; */
/*     } */


    /* Calculate and Remove Global Bias (until we have cold shutter
     * bias maps) Additionnally this writes an estimate of the image
     * variance in the rawdata->variance section, (in ADU^2) which is
     * to be replaced by the more correct value given by the dark when
     * or if a dark is used. */

    if (!(amdlibGetUserPref(amdlibNO_BIAS).set==amdlibTRUE))
    {
        if (amdlibRemoveGlobalBias(rawData, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }

    /* Substract Dark Map . Dark is in ADU, removed before converting to e*/
    if (dark!=NULL)
      {
          if (amdlibSubstractDark(dark, rawData, errMsg) != amdlibSUCCESS)
          {
              return amdlibFAILURE;
          }
      }

    /* For each frame */
    for (iFrame = 0; iFrame < rawData->region[0].dimAxis[2]; iFrame++)
      {
        /* Calculate the calibrated image */
        if (amdlibCalibrateImage(rawData, iFrame, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }

    /* Substract remanence */
    if (amdlibREMANENCE != 0)
    {
        if (amdlibSubstractRemanence(rawData, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }

    /* Set 'calibrated' flag to TRUE */
    rawData->dataCalibrated = amdlibTRUE;

    return amdlibSUCCESS;
}


/** 
 * Check whether detector configurations for both raw data are the same or not. 
 *
 * This function returns true if the detector configurations (number of rows and
 * columns, and dimensions of each region) corresponding to the raw data are the
 * same. Otherwise, it returns false.
 *
 * @param rawData1 raw data containing first detector configuration
 * @param rawData2 raw data containing second detector configuration
 *
 * @return
 * true (amdlibTRUE) if configurations are the same, and false (amdlibFALSE)
 * otherwise.
 */
amdlibBOOLEAN amdlibIsSameDetCfg(amdlibRAW_DATA *rawData1,
                                 amdlibRAW_DATA *rawData2)
{
    amdlibBOOLEAN sameDetCfg;
    int iRow, iCol;
    int iRegion;

    amdlibLogTrace("amdlibIsSameDetCfg()");
    
    /* If raw data structure is not initailized, return FALSE */
    if (rawData1->thisPtr != rawData1)
    {
        return amdlibFALSE;
    }
    if (rawData2->thisPtr != rawData2)
    {
        return amdlibFALSE;
    }

    /* Compare the detector configurations */
    sameDetCfg = amdlibTRUE;
    if ((rawData1->nbRows != rawData2->nbRows) ||
        (rawData1->nbCols != rawData2->nbCols))
    {
        sameDetCfg = amdlibFALSE;
    }
    else
    {
        for (iRow=0; iRow < rawData1->nbRows ; iRow++)
        {
            for (iCol = 0; iCol < rawData1->nbCols; iCol++)
            {
                iRegion = iRow *  rawData1->nbCols + iCol;
                if ((rawData1->region[iRegion].corner[0] !=
                     rawData2->region[iRegion].corner[0]) ||
                    (rawData1->region[iRegion].corner[1] !=
                     rawData2->region[iRegion].corner[1]) ||
                    (rawData1->region[iRegion].dimAxis[0] !=
                     rawData2->region[iRegion].dimAxis[0]) ||
                    (rawData1->region[iRegion].dimAxis[1] !=
                     rawData2->region[iRegion].dimAxis[1]))
                {
                    sameDetCfg = amdlibFALSE;
                }
            }
        }
    }
    return sameDetCfg;
}

/*
 * Protected functions
 */
/**
 * Initialize raw data structure.
 *
 * @param rawData pointer to raw data structure
 */
void amdlibInitRawData(amdlibRAW_DATA *rawData)
{
    amdlibLogTrace("amdlibInitRawData()");
    
    /* Initialize data structure */
    memset (rawData, '\0', sizeof(amdlibRAW_DATA));
    rawData->thisPtr = rawData;
}

/**
 * Copy the source file into the destination file. This version
 * permits to copy from a fits to a fits.gz and vice-versa.
 *
 * @param srcFilename name of the source file.
 * @param dstFilename name of the destination file.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibCopyRawDataFile(const char      *srcFilename,
                                       const char      *dstFilename,
                                       amdlibERROR_MSG errMsg)
{
    char         fitsioMsg[256];
    fitsfile     *srcPtr,*dstPtr;
    int          status = 0;
    char *dst;
    dst=calloc(strlen(dstFilename)+2,sizeof(char));

    amdlibLogTrace("amdlibCopyRawDataFile()");
    /* Open FITS file */
    if (fits_open_file(&srcPtr, srcFilename, READONLY, &status) != 0)
    {
        amdlibReturnFitsError(srcFilename);
    }
    /*Prefix dstFilename with a ! to overwrite it*/
    sprintf(dst,"!%s",dstFilename);
    if (fits_create_file(&dstPtr, dst, &status) != 0)
    {
        amdlibReturnFitsError(dstFilename);
    }

    if (fits_copy_file(srcPtr, dstPtr, (int)1, (int)1,(int)1, &status) != 0)
    {
        amdlibSetErrMsg("Unable to copy Raw data file");
        return amdlibFAILURE;
    }
    /* Write & Close the FITS files */
    if (fits_close_file(srcPtr, &status) != 0)
    {
        amdlibReturnFitsError(srcFilename);
    }
    if (fits_close_file(dstPtr, &status) != 0)
    {
        amdlibReturnFitsError(dstFilename);
    }
    return amdlibSUCCESS;

}

/**
 * Store/Replace the detector data in the given file. 
 *
 * This function stores the detector data contained in the raw data structure
 * into the specified file. The specified file must be an existing raw data
 * file. This function just replaces the detector data contained in the
 * IMAGING_DATA binary table. 
 * 
 * @warning 
 * It does not change or update the information of the different headers, and
 * does not check correctness of these informations compared to the raw data.
 * Therefore, this function has to be only used to store, in the original file
 * or in a copy of the original file, the raw data which has been processed
 * using for example amdlibCalibrateRawData() function.
 * 
 * @param filename name of the FITS file containing raw data.
 * @param rawData structure where raw data is stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibStoreRawData(const char      *filename,
                                    amdlibRAW_DATA  *rawData,
                                    amdlibERROR_MSG errMsg)
{
    struct stat  statBuf;
    char         fitsioMsg[256];
    fitsfile     *filePtr;
    int          status = 0;

    amdlibLogTrace("amdlibStoreRawData()");
    
    /* Check the file exists */
    if (stat(filename, &statBuf) != 0)
    {
        amdlibSetErrMsg("File '%.80s' does not exist", filename);
        return amdlibFAILURE;
    }

    /* If raw data structure is not initailized, do it */
    if (rawData->thisPtr != rawData)
    {
        amdlibSetErrMsg("Raw data is not initialized");
        return amdlibFAILURE;
    }

    /* Open FITS file */
    if (fits_open_file(&filePtr, filename, READWRITE, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    /* Write 'region data' in imaging data table */
    if (amdlibWriteRegionData(filePtr, rawData->region,
                            rawData->nbRegions,
                            errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Write 'time tag' in imaging data table */
    if (amdlibWriteTimeTag(filePtr, rawData, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Write the FITS file */
    if (fits_close_file(filePtr, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    return amdlibSUCCESS;
}

/**
 * Save raw data into a FITS file as 2-dimensional images. 
 *
 * This function creates glued images from data of the detector subwindows and
 * stores the resulting images into a normal FITS file.
 *
 * @param filename name of the FITS file where 2D images will be stored.
 * @param rawData structure where raw data is stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSaveRawDataToFits(const char      *filename,
                                         amdlibRAW_DATA  *rawData,
                                         amdlibERROR_MSG errMsg)
{
    char         fitsioMsg[256];
    fitsfile     *filePtr;
    int          status = 0;
    int          i, iRegion;
    int          iRow, iCol;
    int          regionHeight, regionWidth, regionSize;
    int          iY, iX, iFrame;
    amdlibDOUBLE        *image=NULL;

    int          bitpix = FLOAT_IMG;
    long         naxis = 3;  /* 2-dimensional image */
    long         naxes[3] = {0, 0, 0};

    amdlibLogTrace("amdlibSaveRawDataToFits()");
    
    /* If raw data structure is not initialized, do it */
    if (rawData->thisPtr != rawData)
    {
        amdlibSetErrMsg("Raw data not initialized");
        return amdlibFAILURE;
    }

    /* Check raw data structure contains data */
    if (rawData->dataLoaded == amdlibFALSE)
    {
        amdlibSetErrMsg("The raw data structure does not "
                        "contain data. Check call to amdlibLoadRawData()");
        return amdlibFAILURE;
    }

    /* Delete old file if it already exists */
    remove(filename);

    /* Create FITS file */
    if (fits_create_file(&filePtr, filename, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    /* Determine the size of the glued image */
    for (iCol = 0; iCol < rawData->nbCols; iCol++)
    {
        iRegion = iCol;
        naxes[0] += rawData->region[iRegion].dimAxis[0];
    }
    for (iRow = 0; iRow < rawData->nbRows; iRow++)
    {
        iRegion = iRow *  rawData->nbCols;
        naxes[1] += rawData->region[iRegion].dimAxis[1];
    }
    naxes[2] = rawData->region[0].dimAxis[2];

    /* Allocate memory */
    image = calloc(naxes[0]*naxes[1]*naxes[2],sizeof(amdlibDOUBLE));
    if (image == NULL)
    {
        amdlibSetErrMsg("Could not allocate memory for glued image");
        return amdlibFAILURE;
    }

    /* Create glued image */
    i=0;
    for (iFrame = 0; iFrame < rawData->region[0].dimAxis[2]; iFrame++)
    {
        for (iRow = 0; iRow < rawData->nbRows; iRow++)
        {
            iRegion = iRow * rawData->nbCols;
            regionHeight = rawData->region[iRegion].dimAxis[1];
            for (iY = 0; iY < regionHeight; iY++)
            {
                for (iCol = 0; iCol < rawData->nbCols; iCol++)
                {
                    iRegion = iRow *  rawData->nbCols + iCol;
                    regionWidth = rawData->region[iRegion].dimAxis[0];
                    regionSize = regionWidth * regionHeight;
                    for (iX = 0; iX < regionWidth; iX++)
                    {
                        image[i++] =
                        rawData->region[iRegion].data[iFrame*regionSize +
                                                      iY*regionWidth + iX];
                    }
                }
            }
        }
    }

    /* Write the required keywords for the primary array image.     */
    if (fits_create_img(filePtr,  bitpix, naxis, naxes, &status) != 0)
    {
        free(image);
        amdlibGetFitsError(filename);
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Copy array containing instrument configuration into file */
    for (i=0; i < rawData->insCfg.nbKeywords; i++)
    {
        amdlibKEYW_LINE keywLine;
        if (strstr(rawData->insCfg.keywords[i].name, "HIERARCH ESO") != NULL)
        {
            sprintf((char*)keywLine, "%s=%s/%s", 
                    rawData->insCfg.keywords[i].name,
                    rawData->insCfg.keywords[i].value,
                    rawData->insCfg.keywords[i].comment);
            if (fits_write_record(filePtr, keywLine, &status) != 0)
            {
                amdlibGetFitsError(rawData->insCfg.keywords[i].name);
                fits_close_file(filePtr, &status);
                return amdlibFAILURE;
            }
        }
    }

    /* write the array of unsigned integers to the FITS file */
    if (fits_write_img(filePtr, TDOUBLE, 1, naxes[0]*naxes[1]*naxes[2], image,
                       &status) != 0)
    {
        free(image);
        amdlibGetFitsError(filename);
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Release previously allocated memory */
    free(image);

    /* close file */
    if (fits_close_file(filePtr, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }
    return amdlibSUCCESS;
}

/**
 * Duplicate raw data structure. 
 *
 * This function allocates memory for destination data structure and copies the
 * data from source into it. 
 *
 * @param srcRawData source raw data structure.
 * @param dstRawData destination  raw data structure.
 * @param errMsg error description message returned if function fails.
  *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibDuplicateRawData(amdlibRAW_DATA  *srcRawData,
                                        amdlibRAW_DATA  *dstRawData,
                                        amdlibERROR_MSG errMsg)
{
    int i;
    
    amdlibLogTrace("amdlibDuplicateRawData()");
    
    /* Initialize destination data structure */
    if (dstRawData->thisPtr != dstRawData)
    {
        amdlibInitRawData(dstRawData);
    }
    
    /* Copy all raw data information */
    memcpy(dstRawData, srcRawData, sizeof(amdlibRAW_DATA));
      
    /* Allocates memory for regions */
    dstRawData->region = NULL;
    if (amdlibAllocateRegions(&dstRawData->region, 
                              dstRawData->nbRegions) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for regions");
        return amdlibFAILURE;
    }
    dstRawData->variance = NULL;
    if (amdlibAllocateRegions(&dstRawData->variance, 
                              dstRawData->nbRegions) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for variance map");
        return amdlibFAILURE;
    }

    /* Copy all regions information and data */
    for (i = 0; i < dstRawData->nbRegions; i++)
    {
        int regionSize;
        /* Copy region information */
        memcpy(&dstRawData->region[i], &srcRawData->region[i], 
               sizeof(amdlibREGION));
        /* Copy variance information */
        memcpy(&dstRawData->variance[i], &srcRawData->variance[i], 
               sizeof(amdlibREGION));

        /* Allocate memory for region and variance */
        regionSize = srcRawData->region[i].dimAxis[0] * 
            srcRawData->region[i].dimAxis[1] * 
            srcRawData->region[i].dimAxis[2];
        dstRawData->region[i].data = calloc(regionSize, sizeof(*(dstRawData->region[i].data)));
        dstRawData->variance[i].data  = calloc(regionSize, sizeof(*(dstRawData->variance[i].data)));
        if (dstRawData->region[i].data == NULL)
        {
            amdlibSetErrMsg("Could not allocate memory for data of region #%d",
                            i);
            return amdlibFAILURE;
        }
        if (dstRawData->variance[i].data == NULL)
        {
            amdlibSetErrMsg("Could not allocate memory for data of variance #%d",
                            i);
            return amdlibFAILURE;
        }
        /* Copy region data */
        memcpy(dstRawData->region[i].data, srcRawData->region[i].data, 
               regionSize * sizeof(*(srcRawData->region[i].data)));
        /* Copy variance data */
        memcpy(dstRawData->variance[i].data, srcRawData->variance[i].data, 
               regionSize * sizeof(*(srcRawData->variance[i].data)));
    }
    
    /* Copy time tag and computed rms for all frames */
    dstRawData->timeTag = calloc(dstRawData->nbFrames, sizeof(double));
    if (dstRawData->timeTag == NULL)
    {
        amdlibSetErrMsg("Could not allocate memory for time tag");
        return amdlibFAILURE;
    }
    for (i = 0; i < dstRawData->nbFrames; i++)
    {
        dstRawData->timeTag[i] = srcRawData->timeTag[i];
    }

    return amdlibSUCCESS;
}

/**
 * Sum and pack data of a channel 
 *
 * This function sums and packs (i.e. rows are merged together) data for the
 * given channel along X, Y and Z axis, according to the specified sum flag. The
 * result is stored in the array given as parameter. This array contains
 * pointers where each pointer points to the memory where result for frame i
 * has to be stored; i.e. result[i] contains pointer to memory allocated for
 * frame i.
 * 
 * @warning
 * The calling function has to allocate memory for the result.
 *
 * @param rawData raw data structure.
 * @param sumX flag indicating whether data has to be summed along X-axis or not
 * @param sumY flag indicating whether data has to be summed along Y-axis or not
 * @param sumZ flag indicating whether data has to be summed along Z-axis or not
 * @param channel channel number; could be amdlibPHOTO1_CHANNEL,
 * amdlibPHOTO2_CHANNEL, amdlibPHOTO3_CHANNEL or amdlibINTERF_CHANNEL
 * @param result array where result will be stored 
 * @param errMsg error description message returned if function fails.
  *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSumAndPackData(amdlibRAW_DATA  *rawData,
                                      amdlibBOOLEAN    sumX,
                                      amdlibBOOLEAN    sumY,
                                      amdlibBOOLEAN    sumZ,
                                      int              channel,
                                      amdlibDOUBLE   **result,
                                      amdlibDOUBLE   **sigma2Result,
                                      amdlibERROR_MSG errMsg)
{
    int channelIndex;
    int dimX; /* resulting column width, ie number of pixels */
    int dimY; /* resulting column height, ie number of spectral channels */
    int dimZ; /* resulting column depth, ie number of frames */
    int iRow; /* index on rawData rows */
    int iRegion; /* index on regions */
    int currentY;
    int firstYInRow;
    int sX, sY, sZ;
    int iX, iY, iZ;
    amdlibDOUBLE ***rawPtr,***variancePtr; /* wrapping on rawData regions data */
    amdlibDOUBLE **resPtr,**sigma2ResPtr; /* wrapping on each result data frame */
    amdlibDOUBLE **badPixels;

    amdlibLogTrace("amdlibSumAndPackData()");
    
    /* Find channel index */
    switch (channel)
    {
        case amdlibPHOTO1_CHANNEL :
        {
            channelIndex = 1;
            break;
        }
        case amdlibPHOTO2_CHANNEL :
        {
            channelIndex = 2;
            break;
        }
        case amdlibPHOTO3_CHANNEL :
        {
            channelIndex = 4;
            break;
        }
        case amdlibINTERF_CHANNEL :
        {
            channelIndex = 3;
            break;
        }
        default :
        {
            amdlibSetErrMsg("Channel reference '%d' does not exist", channel);
            return amdlibFAILURE;
        }
    }
    
    /* Find dimensions according to sumX, sumY and sumZ booleans */
    if (sumX == amdlibTRUE)
    {
        dimX = 1;
    }
    else
    {
        dimX = rawData->region[channelIndex].dimAxis[0]; 
    }
    if (sumY == amdlibTRUE)
    {
        dimY = 1;
    }
    else
    {
        dimY = 0;
        for (iRow = 0; iRow < rawData->nbRows; iRow ++)
        {
            dimY += 
                rawData->region[iRow*rawData->nbCols + channelIndex].dimAxis[1];
        }
    }
    if (sumZ == amdlibTRUE)
    {
        dimZ = 1;
    }
    else
    {
        dimZ = rawData->region[channelIndex].dimAxis[2];
    }
    /*  Reset result array */
    for (iZ = 0; iZ < dimZ; iZ ++)
    {
        if (result[iZ] == NULL)
        {
            amdlibSetErrMsg("Null pointer");
            return amdlibFAILURE;
        }
        else
        {
            memset(result[iZ], '\0', dimX * dimY * sizeof(**result));
        }
    }
    /*  Reset sigma2Result array */
    for (iZ = 0; iZ < dimZ; iZ ++)
    {
        if (sigma2Result[iZ] == NULL)
        {
            amdlibSetErrMsg("Null pointer");
            return amdlibFAILURE;
        }
        else
        {
            memset(sigma2Result[iZ], '\0', dimX * dimY * sizeof(**sigma2Result));
        }
    }
    
    /* Loop on rows so as to pack data */
    currentY = 0;
    for (iRow = 0; iRow < rawData->nbRows; iRow ++)
    {
        iRegion = iRow*rawData->nbCols + channelIndex;
        badPixels = amdlibGetBadPixelMapRegion(
                                    rawData->region[iRegion].corner[0] - 1,
                                    rawData->region[iRegion].corner[1] - 1,
                                    rawData->region[iRegion].dimAxis[0],
                                    rawData->region[iRegion].dimAxis[1],
                                    errMsg);
        if (badPixels == NULL)
        {
            return amdlibFAILURE;
        }
        rawPtr = amdlibWrap3DArrayDouble(rawData->region[iRegion].data, 
                                        rawData->region[iRegion].dimAxis[0],
                                        rawData->region[iRegion].dimAxis[1],
                                        rawData->region[iRegion].dimAxis[2],
                                        errMsg);
        if (rawPtr == NULL)
        {
            amdlibFree2DArrayDouble(badPixels);
            return amdlibFAILURE;
        }
        variancePtr = amdlibWrap3DArrayDouble(rawData->variance[iRegion].data, 
                                        rawData->variance[iRegion].dimAxis[0],
                                        rawData->variance[iRegion].dimAxis[1],
                                        rawData->variance[iRegion].dimAxis[2],
                                        errMsg);
        if (variancePtr == NULL)
        {
            amdlibFree2DArrayDouble(badPixels);
            amdlibFree3DArrayDoubleWrapping(rawPtr);        
            return amdlibFAILURE;
        }
        sX = 0;
        sY = 0;
        sZ = 0;
        firstYInRow = currentY;
        for (iZ = 0; iZ < rawData->region[iRegion].dimAxis[2]; iZ ++)
        {
            if (sumZ == amdlibFALSE)
            {
                sZ = iZ;
            }
            resPtr = amdlibWrap2DArrayDouble(result[sZ], dimX, dimY, errMsg);
            if (resPtr == NULL)
            {
                return amdlibFAILURE;
            }
            sigma2ResPtr = amdlibWrap2DArrayDouble(sigma2Result[sZ], dimX, dimY, errMsg);
            if (sigma2ResPtr == NULL)
            {
                amdlibFree2DArrayDoubleWrapping(resPtr);
                return amdlibFAILURE;
            }

            currentY = firstYInRow;
            for (iY = 0; iY < rawData->region[iRegion].dimAxis[1]; iY ++)
            {
                if (sumY == amdlibFALSE)
                {
                    sY = currentY;
                }
                for (iX = 0; iX < rawData->region[iRegion].dimAxis[0]; iX ++)
                {
                    if (sumX == amdlibFALSE)
                    {
                        sX = iX;
                    }
                    if (badPixels[iY][iX] == amdlibGOOD_PIXEL_FLAG)
                    {
                        resPtr[sY][sX] += rawPtr[iZ][iY][iX];
                        sigma2ResPtr[sY][sX] += variancePtr[iZ][iY][iX];
                    }
                }
                currentY ++;
            }
            amdlibFree2DArrayDoubleWrapping(resPtr);
            amdlibFree2DArrayDoubleWrapping(sigma2ResPtr);
        }
        amdlibFree2DArrayDouble(badPixels);
        amdlibFree3DArrayDoubleWrapping(rawPtr);        
        amdlibFree3DArrayDoubleWrapping(variancePtr);        
    }
    
    /* If data has been summed along Z-axis, it means
     * that we do an average of all frames.
     * We have to modify the variance accordingly 
     * (retrieve the readout noise) */
    if (sumZ == amdlibTRUE)
    {
        resPtr = amdlibWrap2DArrayDouble(result[0], dimX, dimY, errMsg);
        if (resPtr == NULL)
        {
            return amdlibFAILURE;
        }
        sigma2ResPtr = amdlibWrap2DArrayDouble(sigma2Result[0], dimX, dimY, errMsg);
        if (sigma2ResPtr == NULL)
        {
            amdlibFree2DArrayDoubleWrapping(resPtr);
            return amdlibFAILURE;
        }
        for (iY = 0; iY < dimY; iY++)
        {
            for (iX = 0; iX < dimX; iX ++)
            {
                /* sum of N ron */
                sigma2ResPtr[iY][iX] -= resPtr[iY][iX];
                /* mean ron (=true ron if a dark was used) */
                sigma2ResPtr[iY][iX] /= rawData->region[channelIndex].dimAxis[2];
                /*mean value of pixel*/
                resPtr[iY][iX] /= rawData->region[channelIndex].dimAxis[2];
                /*sigma2 of mean value */
                sigma2ResPtr[iY][iX] = 
                (resPtr[iY][iX]+sigma2ResPtr[iY][iX])
                /rawData->region[channelIndex].dimAxis[2];
            }
        }
        amdlibFree2DArrayDoubleWrapping(resPtr);
        amdlibFree2DArrayDoubleWrapping(sigma2ResPtr);
    }

    return amdlibSUCCESS;
}


/*
 * Local functions
 */
/**
 * Get time tags for the list of frames. 
 *
 * This function retrieves the time tags from the given FITS file for the
 * specified frames.
 * 
 * @param filePtr pointer to the FITS file containing raw data.
 * @param rawData structure where raw data is stored
 * @param firstFrame index of the first frame to load 
 * @param nbFrames number of frames to load 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibReadTimeTag (fitsfile        *filePtr, 
                                    amdlibRAW_DATA  *rawData,
                                    int             firstFrame,
                                    int             nbFrames,
                                    amdlibERROR_MSG errMsg)
{
    int        status = 0;
    int        anynull = 0;
    char       fitsioMsg[256];
    int        colnum;
    
    amdlibLogTrace("amdlibReadTimeTag()");
    
    /* Reset error message */
    memset(errMsg, '\0', sizeof(amdlibERROR_MSG));

    /* Allocate memory to store time tag list */
    rawData->timeTag = calloc(nbFrames, sizeof(*(rawData->timeTag)));
    if (rawData->timeTag == NULL)
    {
        amdlibSetErrMsg("Could not allocate memory for time tag list");
        return amdlibFAILURE;
    }

   /* Go to the IMAGING_DATA binary table */
    if (fits_movnam_hdu(filePtr, BINARY_TBL, 
                        "IMAGING_DATA", 0, &status) != 0)
    {
        amdlibReturnFitsError("IMAGING_DATA");
    }

    if(fits_get_colnum (filePtr, CASEINSEN, "TIME", &colnum, &status))
    {
        amdlibReturnFitsError("TIME");
    }

    if(fits_read_col (filePtr, TDOUBLE, colnum, firstFrame, 1, nbFrames,
                      NULL, rawData->timeTag, &anynull, &status) != 0)
    {
        amdlibReturnFitsError("TIME");
    }
    return amdlibSUCCESS;
}

/**
 * Set time tags for all frames. 
 *
 * This function write the time tags stored in the raw data structure into the
 * FITS file.
 * 
 * @param filePtr pointer to the FITS file containing raw data.
 * @param rawData structure where raw data is stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteTimeTag (fitsfile        *filePtr, 
                                     amdlibRAW_DATA  *rawData,
                                     amdlibERROR_MSG errMsg)
{
    int        status = 0;
    char       fitsioMsg[256];
    int        colnum;

    amdlibLogTrace("amdlibWriteTimeTag()");
    
    /* Reset error message */
    memset(errMsg, '\0', sizeof(amdlibERROR_MSG));

    /* Go to the IMAGING_DATA binary table */
    if (fits_movnam_hdu(filePtr, BINARY_TBL, "IMAGING_DATA", 0, &status) != 0)
    {
        amdlibReturnFitsError("IMAGING_DATA");
    }

    if(fits_get_colnum (filePtr, CASEINSEN, "TIME", &colnum, &status))
    {
        amdlibReturnFitsError("TIME");
    }
    /* Check data pointer */
    if (rawData->timeTag == NULL)
    {        
        amdlibSetErrMsg("The pointer to the time data is invalid"); 
        return amdlibFAILURE;
    }

    /* Write the time data */
    if (fits_write_col(filePtr, TDOUBLE, colnum, 1, 1, 
                       rawData->nbFrames, rawData->timeTag, &status) != 0)
    {
        amdlibReturnFitsError("TIME");
    }
    return amdlibSUCCESS;
}

 

/**
 * Free memory allocated memory of raw data structure.
 *
 * This function frees previously allocated memory (if any) where raw data has
 * been stored. 
 * 
 * @param rawData structure where raw data is stored
 */
void amdlibFreeRawData(amdlibRAW_DATA *rawData)
{
    amdlibLogTrace("amdlibFreeRawData()");
    
    amdlibFreeRegions(&rawData->region, rawData->nbRegions);
    amdlibFreeRegions(&rawData->variance,  rawData->nbRegions);
    amdlibReleaseOiArray(&rawData->arrayGeometry);
    if (rawData->timeTag != NULL)
    {
        free(rawData->timeTag);
        rawData->timeTag = NULL;
    }
}

/**
 * Convert string frame type to Id.
 *
 * This function converts the string corresponding the the frame as defined by
 * OCS.DET.FRAM.TYPE keyword to the frame type Id as defined by the
 * amdlibFRAME_TYPE enumerate.
 *
 * @param frameTypeStr frame type as string 
 *
 * @return
 * Frame type Id.
 */
amdlibFRAME_TYPE amdlibFrameTypeStr2Id(char *frameTypeStr)
{
    amdlibLogTrace("amdlibFrameTypeStr2Id()");
    
    if (strcmp(frameTypeStr, "DARK") == 0)
    {
        return amdlibDARK_FRAME;
    }
    if ((strcmp(frameTypeStr, "TEL1") == 0) || 
        (strcmp(frameTypeStr, "TEL1SC") == 0))
    {
        return amdlibTEL1_FRAME;
    }
    if ((strcmp(frameTypeStr, "TEL2") == 0) ||
        (strcmp(frameTypeStr, "TEL2SC") == 0))
    {
        return amdlibTEL2_FRAME;
    }
    if ((strcmp(frameTypeStr, "TEL3") == 0) ||
        (strcmp(frameTypeStr, "TEL3SC") == 0))
    {
        return amdlibTEL3_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL12") == 0)
    {
        return amdlibTEL12_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL12Q") == 0)
    {
        return amdlibTEL12Q_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL23") == 0)
    {
        return amdlibTEL23_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL23Q") == 0)
    {
        return amdlibTEL23Q_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL13") == 0)
    {
        return amdlibTEL13_FRAME;
    }
    if (strcmp(frameTypeStr, "TEL13Q") == 0)
    {
        return amdlibTEL13Q_FRAME;
    }
    if (strcmp(frameTypeStr, "SKY") == 0)
    {
        return amdlibSKY_FRAME;
    }
    return amdlibUNKNOWN_FRAME;
}

/**
 * Check dark map and data compatibity
 *
 * This function checks if data from the dark map and data from the raw 
 * data structure are compatible, i.e. if each field stored in both structures
 * is instanciated twice by the same value.
 *
 * @param dark map.
 * @param rawData raw data. 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibCheckDataCompatibility(amdlibDARK_DATA *dark,
                                              amdlibRAW_DATA        *rawData,
                                              amdlibERROR_MSG       errMsg)
{
    int iRegion;
    
    amdlibLogTrace("amdlibCheckDataCompatibility()");
    
    if (dark != NULL)
    {
        if (dark->nbRegions != rawData->nbRegions)
        {
            amdlibSetErrMsg("The number of regions in raw data (%d) and in "
                            "dark data (%d) are incompatible",
                            rawData->nbRegions, dark->nbRegions);
            return amdlibFAILURE;
        }

        if (dark->expTime != rawData->expTime)
        {
            /* Use a Warning this time */
            amdlibLogWarning("The expTime in raw data (%f) and in dark "
                             "data (%f) are incompatible",
                             rawData->expTime, dark->expTime);
        }

        if (dark->nbRows != rawData->nbRows)
        {
            amdlibSetErrMsg("The number of rows in raw data (%d) and in "
                            "dark data (%d) are incompatible",
                            rawData->nbRows, dark->nbRows);
            return amdlibFAILURE;
        }

        if (dark->nbCols != rawData->nbCols)
        {
            amdlibSetErrMsg("The number of columns in raw data (%d) and in "
                            "dark data (%d) are incompatible",
                            rawData->nbCols, dark->nbCols);
            return amdlibFAILURE;
        }

        /* For each region */
        for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
        {
            /* Check the region position */
            if (dark->region[iRegion].corner[0] !=
                rawData->region[iRegion].corner[0])
            {
                amdlibSetErrMsg("The position in X direction of the region %d "
                                "in raw data (%d) and in dark data (%d) "
                                "are incompatible",
                                iRegion, rawData->region[iRegion].corner[0],
                                dark->region[iRegion].corner[0]);
                return amdlibFAILURE;
            }

            if (dark->region[iRegion].corner[1] !=
                rawData->region[iRegion].corner[1])
            {
                amdlibSetErrMsg("The position in X direction of the region %d "
                                "in raw data (%d) and in dark data (%d) "
                                "are incompatible",
                                iRegion, rawData->region[iRegion].corner[1],
                                dark->region[iRegion].corner[1]);
                return amdlibFAILURE;
            }

            /* Check the region size */
            if (dark->region[iRegion].dimAxis[0] !=
                rawData->region[iRegion].dimAxis[0])
            {
                amdlibSetErrMsg("The number of pixels  in X direction of the "
                                "region %d in raw data (%d) and in dark "
                                "data (%d are incompatible",
                                iRegion, rawData->region[iRegion].dimAxis[0],
                                dark->region[iRegion].dimAxis[0]);
                return amdlibFAILURE;
            }
            if (dark->region[iRegion].dimAxis[1] !=
                rawData->region[iRegion].dimAxis[1])
            {
                amdlibSetErrMsg("The number of pixels in X direction of the "
                                "region %d in raw data (%d) and in dark "
                                "data (%d) are incompatible",
                                iRegion, rawData->region[iRegion].dimAxis[1],
                                dark->region[iRegion].dimAxis[1]);
                return amdlibFAILURE;
            }
        }
    }
    return amdlibSUCCESS;
}

/**
 * Delete null frames 
 *
 * This function treats the problem of null frames present in the raw data being
 * calibrated. These null frames are not taken into account, they are shifted at
 * the end of the list of frames and ignored.
 *
 * @param rawData raw data. 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibDiscardNullFrames(amdlibRAW_DATA        *rawData,
                                         amdlibERROR_MSG       errMsg)
{
    int nbFrames;
    int nbNullFrames=0;
    int iFrame, iFirstFrame;
    int onbFrames;
    int oFrame;
    int iRegion;
    int regionSize;
    int iFFskip=0;

#define amdlibFIRST_FRAME_IF_SKIP 0
#define amdlibMIN_FRAMES_FOR_DROP 200
    
    amdlibLogTrace("amdlibDiscardNullFrames()");
    
    if (amdlibGetUserPref(amdlibDROP).set==amdlibTRUE)
        iFFskip=amdlibGetUserPref(amdlibDROP).value;

     
    nbFrames = rawData->region[0].dimAxis[2];
    iFirstFrame=nbNullFrames=iFFskip;
    for (iFrame = iFirstFrame; iFrame < nbFrames ; iFrame++)
    {
        if (rawData->timeTag[iFrame] == 0.0)
        {
            nbNullFrames++;
        }
    }

    /* Compact all avoided frames and update structure header accordingly*/
    if (nbNullFrames!=0) 
    {
        /* Report on Null Frame Number. Compute a percentage. Warning issued at 20% */
      if (((double)nbNullFrames-(double)iFirstFrame)/(double)nbFrames>0.2) amdlibLogWarning("more than %.1f%% frames tagged with null DIT removed.",100.0*((double)nbNullFrames-(double)iFirstFrame)/(double)nbFrames);

        onbFrames = nbFrames-iFirstFrame;
        /* Copy back each non-null frame values and infos after the last good */
        for (iFrame = iFirstFrame, oFrame = 0; iFrame < nbFrames; iFrame++)
        {
            if (rawData->timeTag[iFrame] == 0.0)
            {
                onbFrames--;
            }
            else
            {
                if (iFrame != oFrame)
                {
                    for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
                    {
                        regionSize = rawData->region[iRegion].dimAxis[0] *
                                     rawData->region[iRegion].dimAxis[1];
                        memcpy(
                          rawData->region[iRegion].data + oFrame*regionSize,
                          rawData->region[iRegion].data + iFrame*regionSize,
                          regionSize*sizeof(*(rawData->region[iRegion].data)));
                        memcpy(
                          rawData->variance[iRegion].data + oFrame*regionSize,
                          rawData->variance[iRegion].data + iFrame*regionSize,
                          regionSize*sizeof(*(rawData->variance[iRegion].data)));
                    }
                    rawData->timeTag[oFrame] = rawData->timeTag[iFrame];
                }
                oFrame++;
            }
        }
        if (onbFrames == 0)
        {
            amdlibSetErrMsg("Only Null Frames found !");
            return amdlibFAILURE;
        }

        /* Zero end of data */
        for (iFrame = onbFrames; iFrame < nbFrames; iFrame++)
        {
            for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
            {
                regionSize = rawData->region[iRegion].dimAxis[0] *
                rawData->region[iRegion].dimAxis[1];
                memset(
                       rawData->region[iRegion].data + iFrame*regionSize,
                       '\0', regionSize*sizeof(*(rawData->region[iRegion].data)));
                memset(
                       rawData->variance[iRegion].data + iFrame*regionSize,
                       '\0', regionSize*sizeof(*(rawData->variance[iRegion].data)));
            }
        }

        nbFrames = onbFrames;
        /* Update nb frame info where it hurts... */
        rawData->nbFrames = nbFrames;
        for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
        {
            rawData->region[iRegion].dimAxis[2] = nbFrames;
            rawData->variance[iRegion].dimAxis[2] = nbFrames;
        }
    }
    
    return amdlibSUCCESS;
}

/**
 * Substracts dark
 *
 * This function substracts dark 
 * on raw data, and add variance information in the structure.
 *
 * @param dark dark map.
 * @param rawData raw data. 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSubstractDark(amdlibDARK_DATA *dark,
                                     amdlibRAW_DATA  *rawData,
                                     amdlibERROR_MSG errMsg)
{
    int iRegion;
    int regionSize;
    int iFrame;
    int iX, iY;
    int   startPixelX, startPixelY;
    int   regionWidth, regionHeight;
    amdlibDOUBLE **badPixels;
    amdlibDOUBLE ***rawPtr;
    amdlibDOUBLE ***variancePtr;
    amdlibDOUBLE **darkPtr;    
    amdlibDOUBLE **sigma2DarkPtr;    
    
    amdlibLogTrace("amdlibSubstractDark()");

    /* Do not take risks of a bad external call */
    if (dark!=NULL)
      {
    
	for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
	  {
            startPixelX  = rawData->region[iRegion].corner[0] - 1;
            regionWidth = rawData->region[iRegion].dimAxis[0];
            startPixelY  = rawData->region[iRegion].corner[1] - 1;
            regionHeight = rawData->region[iRegion].dimAxis[1];

            /* Get bad pixels and flat-field for this region */
            badPixels = amdlibGetBadPixelMapRegion(startPixelX, startPixelY,
                                                   regionWidth, regionHeight,
                                                   errMsg);
	    regionSize = regionWidth * regionHeight;
	    rawPtr = 
	      amdlibWrap3DArrayDouble(rawData->region[iRegion].data, 
				      regionWidth,
				      regionHeight,
				      rawData->region[iRegion].dimAxis[2],
				      errMsg);
	    variancePtr = 
	      amdlibWrap3DArrayDouble(rawData->variance[iRegion].data, 
				      regionWidth,
				      regionHeight,
				      rawData->variance[iRegion].dimAxis[2],
				      errMsg);
	    darkPtr = 
	      amdlibWrap2DArrayDouble(dark->region[iRegion].data, 
				      regionWidth,
				      regionHeight,
				      errMsg);
	    sigma2DarkPtr = 
	      amdlibWrap2DArrayDouble(dark->noise[iRegion].data, 
				      regionWidth,
				      regionHeight,
				      errMsg);
            if (badPixels == NULL)
            {
                return amdlibFAILURE;
            }
	    if (rawPtr == NULL) 
	      {
		return amdlibFAILURE;
	      }
	    if (variancePtr == NULL) 
	      {
		amdlibFree3DArrayDoubleWrapping(rawPtr);
		return amdlibFAILURE;
	      }
	    if (darkPtr == NULL)
	      {
		amdlibFree3DArrayDoubleWrapping(rawPtr);
		amdlibFree3DArrayDoubleWrapping(variancePtr);
		return amdlibFAILURE;
	      }
	    if (sigma2DarkPtr == NULL)
	      {
		amdlibFree3DArrayDoubleWrapping(rawPtr);
		amdlibFree3DArrayDoubleWrapping(variancePtr);
		amdlibFree2DArrayDoubleWrapping(darkPtr);
		return amdlibFAILURE;
	      }

	    for (iFrame = 0; iFrame < rawData->region[iRegion].dimAxis[2]; 
		 iFrame++)
	      {
		for (iY = 0; iY < regionHeight ; iY++)
		  {
		    for (iX = 0; iX < regionWidth; iX++)
		      {
			if (badPixels[iY][iX] == amdlibGOOD_PIXEL_FLAG)
			  {
			    rawPtr[iFrame][iY][iX] -= darkPtr[iY][iX]; 
			    /* When dark is used, use the (better)
                             * individual noise given by the darks'
                             * noise section. */
			    variancePtr[iFrame][iY][iX] = sigma2DarkPtr[iY][iX]; 
			  }
		      }
		  }
	      }
            amdlibFree2DArrayDouble(badPixels);
	    amdlibFree3DArrayDoubleWrapping(rawPtr);
	    amdlibFree2DArrayDoubleWrapping(darkPtr);
	    amdlibFree3DArrayDoubleWrapping(variancePtr);
	    amdlibFree2DArrayDoubleWrapping(sigma2DarkPtr);
	  }
      }
    else
      {
	amdlibLogWarning("Calling procedure amdlibSubstractDark() with a null dark");
      }
    return amdlibSUCCESS;
}

/**
 * Calibrate image 
 *
 * This function calibrates the specified image. 
 *
 * @param rawData raw data. 
 * @param iFrame index of the frame 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibCalibrateImage(amdlibRAW_DATA        *rawData,
                                      int                   iFrame,
                                      amdlibERROR_MSG       errMsg)
{
    int   iRow, iCol;
    int   iRegion;
    int   iX, iY;
    int   startPixelX, startPixelY;
    int   regionWidth, regionHeight;
    int   regionSize;
    amdlibDOUBLE **badPixels;
    amdlibDOUBLE **flatField;
    amdlibDOUBLE **rawPtr;
    amdlibDOUBLE **variancePtr;
    amdlibDOUBLE ffmValue;
    amdlibDOUBLE gain;
    amdlibDOUBLE lastGoodPixelValue=0.0,lastGoodPixelVariance=0.0;

    amdlibLogTrace("amdlibCalibrateImage()");
    
    for (iRow = 0; iRow < rawData->nbRows; iRow++)
    {
        for (iCol = 0; iCol < rawData->nbCols; iCol++)
        {
            iRegion = iRow * rawData->nbCols + iCol;
            gain=rawData->region[iRegion].gain;
            startPixelX  = rawData->region[iRegion].corner[0] - 1;
            regionWidth = rawData->region[iRegion].dimAxis[0];
            startPixelY  = rawData->region[iRegion].corner[1] - 1;
            regionHeight = rawData->region[iRegion].dimAxis[1];

            /* Get bad pixels and flat-field for this region */
            badPixels = amdlibGetBadPixelMapRegion(startPixelX, startPixelY,
                                                   regionWidth, regionHeight,
                                                   errMsg);
            if (badPixels == NULL)
            {
                return amdlibFAILURE;
            }
            /* lastGoodPixelValue tries to be a realistic pixel value to put at 
             * a bad pixel location, in case this pixel is used anyway in a
             * further treatment. It is reset to 0 at the beginning of 
             * a region (since regions have different fluxes), when 
             * lastGoodPixelVariance which must be 
             * representative of at least the typical ron is not reset.*/
            lastGoodPixelValue=0.0;
            flatField = amdlibGetFlatFieldMapRegion(startPixelX, startPixelY,
                                                    regionWidth, regionHeight,
                                                    errMsg);
            if (flatField == NULL)
            {
                amdlibFree2DArrayDouble(badPixels);                
                return amdlibFAILURE;            
            }
            regionSize = regionWidth * regionHeight;
            rawPtr = amdlibWrap2DArrayDouble(
                             &rawData->region[iRegion].data[regionSize*iFrame], 
                             rawData->region[iRegion].dimAxis[0],
                             rawData->region[iRegion].dimAxis[1],
                             errMsg);
            if (rawPtr == NULL)
            {
                amdlibFree2DArrayDouble(badPixels);
                amdlibFree2DArrayDouble(flatField);
                return amdlibFAILURE;
            }

            variancePtr = amdlibWrap2DArrayDouble(
                             &rawData->variance[iRegion].data[regionSize*iFrame], 
                             rawData->variance[iRegion].dimAxis[0],
                             rawData->variance[iRegion].dimAxis[1],
                             errMsg);
            if (variancePtr == NULL)
            {
                amdlibFree2DArrayDouble(badPixels);
                amdlibFree2DArrayDouble(flatField);
                amdlibFree2DArrayDoubleWrapping(rawPtr);
                return amdlibFAILURE;
            }
            for (iY = 0; iY < regionHeight; iY++)
            {
                for (iX = 0; iX < regionWidth; iX++)
                {
                    /* Flat is not corrected in shielded area */
                    if (iCol != 0)
                    {
                        ffmValue = flatField[iY][iX];
                    }
                    else
                    {
                        ffmValue = 1.0;
                    }

                    if (badPixels[iY][iX] == amdlibGOOD_PIXEL_FLAG)
                    {
                        /* test whether 'saturated' flag should be set */
                        if (rawPtr[iY][iX] > amdlibDETECTOR_SATURATION) 
                        {
                            rawData->dataIsSaturated = amdlibTRUE;
                        }
                        /* normalize image by gain and flat. Here we are in ADU, so be careful,
                         * the statistics are only for photoelectrons e-.
                         */
                        rawPtr[iY][iX] *= (gain/ffmValue); /*now: in e-*/
                        lastGoodPixelValue=rawPtr[iY][iX];
                        /*variance is sigma2det in adu at the moment,
                         * convert in e- */
                        variancePtr[iY][iX] *= (gain*gain); /*RON^2 in e- */
                        /* add image to get variance=pix+sigma2det */
                        variancePtr[iY][iX] += rawPtr[iY][iX];
                        lastGoodPixelVariance=variancePtr[iY][iX];
                    }
                    else
                    {
                        rawPtr[iY][iX] = lastGoodPixelValue;
                        variancePtr[iY][iX] = lastGoodPixelVariance; 
                    }
                }
            }
            amdlibFree2DArrayDouble(badPixels);
            amdlibFree2DArrayDoubleWrapping(rawPtr);
            amdlibFree2DArrayDoubleWrapping(variancePtr);
            amdlibFree2DArrayDouble(flatField);
        }
    } 
    return amdlibSUCCESS;
}

/**
 * Substract remanence
 *
 * This function substracts remanence on all frame. The remanence is computed
 * using amdlibREMANENCE value.
 *
 * @param rawData raw data. 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSubstractRemanence(amdlibRAW_DATA  *rawData,
                                         amdlibERROR_MSG errMsg)
{
    int   iRegion;
    int   iFrame;
    int   iX, iY;
    amdlibDOUBLE **memoryOfPreviousImage;
    amdlibDOUBLE ***rawPtr;
    amdlibDOUBLE cal;
    
    amdlibLogTrace("amdlibSubstractRemanence()");
    amdlibLogWarningDetail("Substracting a remanence of %f",amdlibREMANENCE);
    for (iRegion = 0; iRegion < rawData->nbRegions; iRegion++)
    {
        /* Allocate Previous Image area */
        memoryOfPreviousImage = amdlibAlloc2DArrayDouble(
                                        rawData->region[iRegion].dimAxis[0],
                                        rawData->region[iRegion].dimAxis[1],
                                        errMsg);
        if (memoryOfPreviousImage == NULL)
        {
            return amdlibFAILURE;
        }
        rawPtr = amdlibWrap3DArrayDouble(rawData->region[iRegion].data, 
                                        rawData->region[iRegion].dimAxis[0],
                                        rawData->region[iRegion].dimAxis[1],
                                        rawData->region[iRegion].dimAxis[2],
                                        errMsg);
        if (rawPtr == NULL)
        {
            amdlibFree2DArrayDouble(memoryOfPreviousImage);
            return amdlibFAILURE;
        }
        for (iFrame = 0; iFrame < rawData->region[iRegion].dimAxis[2]; iFrame++)
        {
            for (iY = 0; iY < rawData->region[iRegion].dimAxis[1]; iY++)
            {
                for (iX = 0; iX < rawData->region[iRegion].dimAxis[0]; iX++)
                {
                    cal = rawPtr[iFrame][iY][iX] - amdlibREMANENCE * 
                                                memoryOfPreviousImage[iY][iX];
                    memoryOfPreviousImage[iY][iX] = rawPtr[iFrame][iY][iX];
                    rawPtr[iFrame][iY][iX] = cal;
                }
            }
        }
        /*Free Previous Image area */
        amdlibFree2DArrayDouble(memoryOfPreviousImage);
        amdlibFree3DArrayDoubleWrapping(rawPtr);
    }
    /* Make Invalid the First frame (remanence unknown and uncompensated) */
    rawData->timeTag[0]=0.0;
    return amdlibSUCCESS;
}
/*___oOo___*/
