/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle and compute the P2VM.
 */

#define _POSIX_SOURCE 1

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

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

/* Local functions */
static void             amdlibInitP2VM(amdlibP2VM_MATRIX *p2vm);
static amdlibCOMPL_STAT amdlibAllocateP2VM(amdlibP2VM_MATRIX *p2vm, 
                                           const int nx, const int nbTel,
                                           const int nbBases,
                                           const int nbChannels, 
                                           amdlibERROR_MSG errMsg);
static void             amdlibFreeP2VM(amdlibP2VM_MATRIX *p2vm);
void amdlibLinearize(int n, double *y, double *wy);
void amdlibBoxCarSmooth(int n, double *y, int w);
static double amdlibEstimateCWVariance(double *ikb, 
                                       double *ikg,
                                       double *v1,
                                       double *v2, 
                                       double P1b,
                                       double P2b,
                                       double P1g,
                                       double P2g,
                                       amdlibDOUBLE ron,  /* Hope ron did not change */
                                       int nx);    /* Width of the I channel  */

/* Public functions */
/**
 * Release memory allocated to store P2VM
 *
 * @param p2vm structure where P2VM is stored
 */
void amdlibReleaseP2VM(amdlibP2VM_MATRIX *p2vm)
{
    amdlibLogTrace("amdlibReleaseP2VM()");
    
    amdlibFreeP2VM(p2vm);
    memset(p2vm, '\0', sizeof(amdlibP2VM_MATRIX)); 
}
/**
 * A rather correct ron value for CW code
 */
#define amdlibTYPICAL_RON 10.0
/**
 * A value for bad ck or dks. 0 should do.
 */
#define amdlibInvalidCkDkValue 0.0
/**
 * Minimum accepted SNR for P2VM computation. Should NOT BE ZERO
 */
#define amdlibMIN_SNR_FOR_P2VM 10.0
/**
 * The wavelength of the center of the H-K phase jump in Low_JHK mode
 */
#define amdlibWAVELENGTH_HK_PHASE_JUMP 1942.0000

#define amdlibComputeP2VM_FREEALL() amdlibFree2DArrayDouble(interf_v);        \
    amdlibFree2DArrayDouble(interf_0); amdlibFree2DArrayDouble(interf_1);     \
    amdlibFree2DArrayDouble(photo_v); amdlibFree2DArrayDouble(photo_0);       \
    amdlibFree2DArrayDouble(photo_1); amdlibFree2DArrayDouble(mu_k_1);  \
    amdlibFree2DArrayDouble(mu_k_2); amdlibFree2DArrayDouble(ck);             \
    amdlibFree2DArrayDouble(dk);  amdlibFree2DArrayDouble(sigma2CW);\
    free(vect);free(vect2);free(mat);amdlibFree2DArrayDouble(gamma0);\
    amdlibFree2DArrayDouble(gamma0w);amdlibFree2DArrayDouble(phaseDerivative);\
    amdlibFree3DArrayDouble(unormalizedP2vmInternalCopy);
/**
 * Computes the P2VM
 *
 * This function computes the P2VM. 
 *
 * @param p2vmData input data for P2VM computation.
 * @param p2vmType P2VM type : amdlibP2VM_2T or amdlibP2VM_3T
 * @param waveData data structure containing spectral dispersion table 
 * @param p2vm pointer to structure where resulting P2VM will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibComputeP2VM(amdlibP2VM_INPUT_DATA *p2vmData,
                                   amdlibP2VM_TYPE       p2vmType,
                                   amdlibWAVEDATA        *waveData,
                                   amdlibP2VM_MATRIX     *p2vm,
                                   amdlibERROR_MSG       errMsg)
{
    int i;
    int j;
    int k;
    int l;
    int baseline;
    int nx;
    int nl, nlValid, nlByBand[amdlibNB_BANDS];
    int image;
    int nbTel;
    int t;
    int nbBases;
    double meanFlux[amdlibNB_TEL][amdlibNB_BANDS];
    amdlibDOUBLE **badPixels;
    amdlibDOUBLE **flatField;

    amdlibSCIENCE_DATA *scienceData;
    int band;
    double nbVis[amdlibNBASELINE];
    double nbVisInBand[amdlibNBASELINE][amdlibNB_BANDS];

    /* In the following comments, the number of shutter closed
     * apply in the 3 telescope case*/
    double **interf_v = NULL;
    /* This matrix contains the IC for every tel. sEparately (2 shutters
     * closed) */
    double **interf_0 = NULL;
    /* This matrix contains the IC for two tel. combinated (1 shutter
     * closed) */
    double **interf_1 = NULL;
    /* This matrix contains the IC for two tel. combinated, w/ phase
     * shift (1 shutter closed) */
    double** photo_v = NULL;
    /* This matrix contains the PC for every tel. separately (2 shutters closed) */
    double **photo_0 = NULL;
    /* This matrix contains the 3 PCs for 2 tel. combinated (1 shutter closed) */
    double **photo_1 = NULL;
    /* This matrix contains the 3 PCs for 2 tel. combinated w/ phase shift (1 shutter closed) */
    /* The first index indicates the tel., the second the baseline! */
    double **mu_k_1 = NULL;
    double **mu_k_2 = NULL;
    double **sigma2CW = NULL;
    double **ck = NULL;
    double **dk = NULL;
    double cosgamm0, singamm0, sumck, sumdk, sumck2, sumckdk;
    double **gamma0 = NULL;
    double **gamma0w = NULL;
    double *mat = NULL;
    double *vect = NULL,*vect2 = NULL;
    char keywValue[amdlibKEYW_VAL_LEN+1];
    int isLowJHK=0;
    double wlenShift;
    int boxcar;
    double fwhm;
    double **phaseDerivative=NULL;
    double ***unormalizedP2vmInternalCopy=NULL;

    amdlibLogTrace("amdlibComputeP2VM()");
    
    /* Init the number of telescopes and baselines according to P2VM type */
    if (p2vmType == amdlibP2VM_2T)
    {
        nbTel  = 2;
        nbBases = 1;
    }
    else if (p2vmType == amdlibP2VM_3T)
    {
        nbTel  = 3;
        nbBases = 3;
    }
    else
    {
        amdlibSetErrMsg("Invalid P2VM type %d "
                        "(see amdlibP2VM_TYPE)", (int)p2vmType);
        return amdlibFAILURE;
    }

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

    /* Check the science data have been stored and calibrated */
    for (image = 0; image < 9; image++)
    {
        int frameType = image;

        /* Skip check of the unused frames for 2T */
        if ((nbTel == 2) &&
            ((frameType == amdlibTEL3_FRAME) ||
             (frameType == amdlibTEL23_FRAME) ||
             (frameType == amdlibTEL13_FRAME) ||
             (frameType == amdlibTEL23Q_FRAME) ||
             (frameType == amdlibTEL13Q_FRAME)))
        {
            continue;
        }

        /* Check the corresponding science data has been loaded */
        if (p2vmData->dataLoaded[image] == amdlibFALSE)
        {
            amdlibSetErrMsg("Missing data file corresponding "
                            "to the image %d (see amdlibFRAME_TYPE)", frameType);
            return amdlibFAILURE;
        }
    }

    /* If P2VM data structure is not initialized, do it */
    if (p2vm->thisPtr != p2vm)
    {
        amdlibInitP2VM(p2vm);
    }

    /* Set the number of pixels in interferometric channel, the number of
     * spectral channels and the start pixel */
    nx = p2vmData->scienceData[0].col[amdlibINTERF_CHANNEL].nbPixels;
    nl = p2vm->nbChannels = p2vmData->scienceData[0].nbChannels;
    p2vm->firstChannel = p2vmData->scienceData[0].channelNo[0];

    /* Allocate memory  */ 
    if (amdlibAllocateP2VM(p2vm, nx, nbTel, nbBases, 
                           nl, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Store P2VM type */
    p2vm->type = p2vmType;
    p2vm->nbFrames = p2vmData->nbFrames;

    /* Clear list of P2VM keywords */
    amdlibClearInsCfg(&p2vm->insCfg);

    /* Copy array containing instrument configuration into P2VM structure, and
     * MJD-OBS keyword which is required for archiving */
    scienceData = &p2vmData->scienceData[1]; 
    for (i=0; i < scienceData->insCfg.nbKeywords; i++)
    {
        if ((strstr(scienceData->insCfg.keywords[i].name, "MJD-OBS") != NULL) ||
	        (strstr(scienceData->insCfg.keywords[i].name, "DATE-OBS") !=NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, "EXPTIME") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, "ORIGIN") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, "INSTRUME") != NULL)||
            (strstr(scienceData->insCfg.keywords[i].name, "UTC") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, "OBSERVER") != NULL)||
            (strstr(scienceData->insCfg.keywords[i].name, "PI-COI") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, 
                    "HIERARCH ESO INS") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, 
                    "HIERARCH ESO OBS") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, 
                    "HIERARCH ESO OCS") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, 
                    "HIERARCH ESO TPL") != NULL) ||
            (strstr(scienceData->insCfg.keywords[i].name, 
                    "HIERARCH ESO DET") != NULL))
        {
            /* Excluded keyword : shutter and MCS which change during P2VM
             * calibration */
            if ((strstr(scienceData->insCfg.keywords[i].name,
                        "HIERARCH ESO INS SHUT") == NULL) &&
                (strstr(scienceData->insCfg.keywords[i].name,
                        "HIERARCH ESO INS OPTI4") == NULL))
            {
                if (amdlibSetInsCfgKeyword(
                                        &p2vm->insCfg, 
                                        scienceData->insCfg.keywords[i].name,
                                        scienceData->insCfg.keywords[i].value,
                                        scienceData->insCfg.keywords[i].comment,
                                        errMsg) != amdlibSUCCESS)
                {
                    return amdlibFAILURE;
                }

            }
        }
    }
    
    /* Get instrument configuration to test Low_JHK mode*/
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    if (amdlibGetInsCfgKeyword(&scienceData->insCfg, 
                               "HIERARCH ESO OCS OBS SPECCONF",
                               keywValue, errMsg) != amdlibSUCCESS)
    {
/*         return amdlibFAILURE; */
    }
    
    if (strstr(keywValue, "Low_JHK ") != NULL) isLowJHK=1;
    
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    sprintf(keywValue, " %f", waveData->photoOffset[0]);
    if (amdlibSetInsCfgKeyword(&p2vm->insCfg, "HIERARCH ESO QC P1 OFFSETY",
                               keywValue, "", errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    sprintf(keywValue, " %f", waveData->photoOffset[1]);
    if (amdlibSetInsCfgKeyword(&p2vm->insCfg, "HIERARCH ESO QC P2 OFFSETY",
                               keywValue, "", errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    sprintf(keywValue, " %f", waveData->photoOffset[2]);
    if (amdlibSetInsCfgKeyword(&p2vm->insCfg, "HIERARCH ESO QC P3 OFFSETY",
                               keywValue, "", errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Copy bad pixel map corresponding to the interferometric channel */
    badPixels = amdlibGetBadPixelMapRegion(
                p2vmData->scienceData[0].col[amdlibINTERF_CHANNEL].startPixel-1,
                p2vm->firstChannel, 
                nx, p2vm->nbChannels, errMsg); 
    if (badPixels != NULL)
    {
        for (l = 0; l < nl; l++)   
        {
            for (i=0; i < nx; i++)   
            {
                p2vm->badPixelsPt[l][i] = (unsigned char) badPixels[l][i];
            }
        }
    }
    else
    {
        for (l = 0; l < nl; l++)
        {
            for (i = 0; i < nx; i++)
            {
                p2vm->badPixelsPt[l][i] = amdlibGOOD_PIXEL_FLAG;
            }
        }
    }
    amdlibFree2DArrayDouble(badPixels);
    
    /* Copy flat field map corresponding to the interferometric channel */
    flatField = amdlibGetFlatFieldMapRegion(
                p2vmData->scienceData[0].col[amdlibINTERF_CHANNEL].startPixel-1,
                p2vm->firstChannel, 
                nx, p2vm->nbChannels, errMsg); 
    if (flatField != NULL)
    {
        for (l=0; l < nl; l++)   
        {
            for (i=0; i < nx; i++)   
            {
                p2vm->flatFieldPt[l][i] = flatField[l][i];
            }
        }
    }
    else
    {
 
        for (l=0; l < nl; l++)   
        {
            for (i=0; i < nx; i++)   
            {   
                p2vm->flatFieldPt[l][i] = 1.0;
            }
        }
    }
    amdlibFree2DArrayDouble(flatField);

    /* Eventually shift wavelengths with user value */
    if (amdlibGetUserPref(amdlibSHIFT_WLENTABLE).set==amdlibTRUE)
    {
        /* If a spectral shift of the wlen table has been requested, do it. */
        amdlibDOUBLE *temp;
        temp=calloc(amdlibNB_SPECTRAL_CHANNELS,sizeof(double));
        /* Copy array containing wavelengths into P2VM structure */
        for (l = 0; l < amdlibNB_SPECTRAL_CHANNELS; l++)
        {
            temp[l] = waveData->wlen[l];
        }
        wlenShift=amdlibGetUserPref(amdlibSHIFT_WLENTABLE).value;
        if (amdlibShift(amdlibNB_SPECTRAL_CHANNELS,temp,wlenShift,waveData->wlen,errMsg) == amdlibFAILURE)
        {
            amdlibLogError("while shifting wavelength table by %f",wlenShift);
            free(temp);
            return amdlibFAILURE;
        }
        free(temp);
    }
    /* Copy array (eventually shifted) containing wavelengths
     * into P2VM structure */
    for (l = 0; l < nl; l++)
    {
        p2vm->wlen[l] = waveData->wlen[p2vmData->scienceData[0].channelNo[l]];
    }

    
    /***** Check photometry, and flag spectral where there is no enough flux */
    /* First check SNR of flux coming from each telescope */
    for (l=0; l < nl; l++)   
    {
        if (p2vmData->scienceData[0].frame[0].snrPhoto1 
            < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric channel %d of frame %d "
                            "too low. Should be greater than %.1f", 
                            p2vmData->scienceData[0].frame[0].snrPhoto1, 
                            1, 1, amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }
        if (p2vmData->scienceData[1].frame[0].snrPhoto2 
            < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric channel %d of frame %d "
                            "too low. Should be greater than %.1f",
                            p2vmData->scienceData[1].frame[0].snrPhoto2, 
                            1, 2, amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }

        if (nbTel == 3) 
        {
            if (p2vmData->scienceData[2].frame[0].snrPhoto3 
                < amdlibMIN_SNR_FOR_P2VM)
            {
            amdlibSetErrMsg("SNR (%.1f) on photometric channel %d of frame %d "
                            "too low. Should be greater than %.1f",
                            p2vmData->scienceData[2].frame[0].snrPhoto3, 
                            1, 3, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
        }
    }

    /* Then retrieve flux coming from each telescope; flux is retrieved from
     * amdlibTEL1_FRAME, amdlibTEL2_FRAME and amdlibTEL3_FRAME frames */
    for (l = 0; l < nl; l++)   
    {
        p2vm->photometryPt[0][0][l] = 
            p2vmData->scienceData[amdlibTEL1_FRAME].frame[0].photo1[l];
        p2vm->photometryPt[0][1][l] = 
            p2vmData->scienceData[amdlibTEL2_FRAME].frame[0].photo2[l];
        if (nbTel == 3) 
        {
            p2vm->photometryPt[0][2][l] = 
                p2vmData->scienceData[amdlibTEL3_FRAME].frame[0].photo3[l];
        }
        else
        {
            p2vm->photometryPt[0][2][l] = 0.0;
        }
    }

    /* Flag all channels with lambda = 0 (happens in JHK mode)*/
    for (l = 0; l < nl; l++)   
    {
        if (p2vm->wlen[l] <= 0.0)
        {
            p2vm->flag[l] = amdlibFALSE;
        }
        else
        {
            p2vm->flag[l] = amdlibTRUE;
        }
    }

    /* Then, compute mean flux (for each band) coming from each telescope */
    for (t = 0; t < amdlibNB_TEL; t++)
    {
        meanFlux[t][0] = 0.0;
        meanFlux[t][1] = 0.0;
        meanFlux[t][2] = 0.0;
    }
    for (t = 0; t < amdlibNB_BANDS; t++)
    {
        nlByBand[t] = 0;
    }
    for (l = 0; l < nl; l++)   
    {
        amdlibBAND theBand;
        theBand = amdlibGetBand(p2vm->wlen[l]);
        if (theBand != amdlibUNKNOWN_BAND) 
        {
            for (t = 0; t < nbTel; t++)
            {
                if (p2vm->photometryPt[0][t][l] > 0)
                {
                    meanFlux[t][theBand] += p2vm->photometryPt[0][t][l];
                    nlByBand[theBand] += 1;
                }
            }
        }
    }
    for (t = 0; t < nbTel; t++)
    {
        for (band = 0; band < amdlibNB_BANDS; band++)
        {
            if (nlByBand[band] != 0)
            {
                meanFlux[t][band] /= nlByBand[band];
            }
        }
    }

    /* Check flux for each spectral channel and flag accordingly. Channel with
     * flux less than 10% of the mean flux is marked as bad. */
    for (l = 0; l < nl; l++)   
    {
        amdlibBAND theBand;
        theBand = amdlibGetBand(p2vm->wlen[l]);
        if (theBand != -1) 
        {
            p2vm->flag[l] = amdlibTRUE; 
            for (t = 0; t < nbTel; t++)
            {
                if (p2vm->photometryPt[0][t][l] < (0.1 * meanFlux[t][theBand]))
                {
                    p2vm->flag[l] = amdlibFALSE;
                }
            }
        }
        else
        {
            p2vm->flag[l] = amdlibFALSE;
        }
    }

    /* Get the number of valid channels */
    nlValid = 0;
    for (l = 0; l < nl; l++)   
    {
        if (p2vm->flag[l] == amdlibTRUE)
        {
            nlValid++;
        }
    }
    if (nlValid == 0)
    {
        amdlibSetErrMsg("No flux detected on photometric channels") ;
        return amdlibFAILURE;
    }

    /* Check SNR of flux coming from each telescope for each interferogram */
    for (l = 0; l < nl; l++)   
    {
        amdlibFRAME_SCIENCE_DATA *frame;
        /* The first index of photo_0/1 refers to the baseline, the
         * second to the telescope. */
        /* Baseline 1-2 */
        frame = &p2vmData->scienceData[amdlibTEL12_FRAME].frame[0];
        if (frame->snrPhoto1 < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric "
                            "channel %d of frame %d too low. Should be greater "
                            "than %.1f", frame->snrPhoto1, 1, amdlibTEL12_FRAME,
                            amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }
        if (frame->snrPhoto2 < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric "
                            "channel %d of frame %d too low. Should be greater "
                            "than %.1f", frame->snrPhoto2, 2, amdlibTEL12_FRAME,
                            amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }
        /* Baseline 1-2 with delay*/
        frame = &p2vmData->scienceData[amdlibTEL12Q_FRAME].frame[0];
        if (frame->snrPhoto1 < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric "
                            "channel %d of frame %d too low. Should be greater "
                            "than %.1f", frame->snrPhoto1, 1, amdlibTEL12Q_FRAME, 
                            amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }
        if (frame->snrPhoto2 < amdlibMIN_SNR_FOR_P2VM)
        {
            amdlibSetErrMsg("SNR (%.1f) on photometric "
                            "channel %d of frame %d too low. Should be greater "
                            "than %.1f", frame->snrPhoto2, 2, amdlibTEL12Q_FRAME, 
                            amdlibMIN_SNR_FOR_P2VM);
            return amdlibFAILURE;
        }

        if (nbTel == 3)
        {
            /* Baseline 2-3 */
            frame = &p2vmData->scienceData[amdlibTEL23_FRAME].frame[0];
            if (frame->snrPhoto2 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto2, 2,
                                amdlibTEL23_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
            if (frame->snrPhoto3 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto3, 3,
                                amdlibTEL23_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }

            /* Baseline 2-3 with delay*/
            frame = &p2vmData->scienceData[amdlibTEL23Q_FRAME].frame[0];
            if (frame->snrPhoto2 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto2, 2,
                                amdlibTEL23Q_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
            if (frame->snrPhoto3 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto3, 3,
                                amdlibTEL23Q_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }

            /* Baseline 3-1 */
            frame = &p2vmData->scienceData[amdlibTEL13_FRAME].frame[0];
            if (frame->snrPhoto3 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto3, 3,
                                amdlibTEL13_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
            if (frame->snrPhoto1 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto1, 1,
                                amdlibTEL13_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }

            /* Baseline 3-1 with delay*/
            frame = &p2vmData->scienceData[amdlibTEL13Q_FRAME].frame[0];
            if (frame->snrPhoto3 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto3, 3,
                                amdlibTEL13Q_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
            if (frame->snrPhoto1 < amdlibMIN_SNR_FOR_P2VM)
            {
                amdlibSetErrMsg("SNR (%.1f) on photometric "
                                "channel %d of frame %d too low. "
                                "Should be greater than %.1f", frame->snrPhoto1, 1,
                                amdlibTEL13Q_FRAME, amdlibMIN_SNR_FOR_P2VM);
                return amdlibFAILURE;
            }
        }
    }

    /* Retrieve flux coming from each telescope for each interferogram */
    for (l = 0; l < nl; l++)   
    {
        amdlibFRAME_SCIENCE_DATA *frame;
        /* The first index of photo_0/1 refers to the baseline, the
         * second to the telescope. */
        /* Baseline 1-2 */
        frame = &p2vmData->scienceData[amdlibTEL12_FRAME].frame[0];
        p2vm->photometryPt[1][0][l] = frame->photo1[l];
        p2vm->photometryPt[1][1][l] = frame->photo2[l];
        for (j = 0; j < nx; j++)
        {
            p2vm->photometryPt[1][2][l] += frame->intf[l*nx+j];
        }
        /* Baseline 1-2 with delay*/
        frame = &p2vmData->scienceData[amdlibTEL12Q_FRAME].frame[0];
        p2vm->photometryPt[2][0][l] = frame->photo1[l];
        p2vm->photometryPt[2][1][l] = frame->photo2[l];
        for (j = 0; j < nx; j++)
        {
            p2vm->photometryPt[2][2][l] += frame->intf[l*nx+j];
        }

        if (nbTel == 3)
        {
            /* Baseline 2-3 */
            frame = &p2vmData->scienceData[amdlibTEL23_FRAME].frame[0];
            p2vm->photometryPt[3][0][l] = frame->photo2[l];
            p2vm->photometryPt[3][1][l] = frame->photo3[l];
            for (j = 0; j < nx; j++)
            {
                p2vm->photometryPt[3][2][l] += frame->intf[l*nx+j];
            }
            /* Baseline 2-3 with delay*/
            frame = &p2vmData->scienceData[amdlibTEL23Q_FRAME].frame[0];
            p2vm->photometryPt[4][0][l] = frame->photo2[l];
            p2vm->photometryPt[4][1][l] = frame->photo3[l];
            for (j = 0; j < nx; j++)
            {
                p2vm->photometryPt[4][2][l] += frame->intf[l*nx+j];
            }
            /* Baseline 3-1 */
            frame = &p2vmData->scienceData[amdlibTEL13_FRAME].frame[0];
            p2vm->photometryPt[5][0][l] = frame->photo1[l];
            p2vm->photometryPt[5][1][l] = frame->photo3[l];
            for (j = 0; j < nx; j++)
            {
                p2vm->photometryPt[5][2][l] += frame->intf[l*nx+j];
            }
            /* Baseline 3-1 with delay*/
            frame = &p2vmData->scienceData[amdlibTEL13Q_FRAME].frame[0];
            p2vm->photometryPt[6][0][l] = frame->photo1[l];
            p2vm->photometryPt[6][1][l] = frame->photo3[l];
            for (j = 0; j < nx; j++)
            {
                p2vm->photometryPt[6][2][l] += frame->intf[l*nx+j];
            }
        }
    }

    scienceData = p2vmData->scienceData;

    for (baseline = 0; baseline < nbBases; baseline++)
    {
        p2vm->insVis[baseline] = 0;
        p2vm->insVisErr[baseline] = 0;
        nbVis[baseline] = 0.0;
        for (band = 0; band < amdlibNB_BANDS; band++)
        {
            p2vm->insVisInBand[baseline][band] = 0;
            p2vm->insVisErrInBand[baseline][band] = 0;
            nbVisInBand[baseline][band] = 0.0;
        }
    }

    interf_v = amdlibAlloc2DArrayDouble(nx, nbTel, errMsg);
    if (interf_v == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    interf_0 = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (interf_0 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    interf_1 = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (interf_1 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    photo_v  = amdlibAlloc2DArrayDouble(nbTel, nbTel, errMsg);
    if (photo_v == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;    
    }

    /* The first index of photo_0/1 refers to the baseline, the
     * second to the telescope. These are NOT contiguous arrays!
     * Must be equivalent to  photo_0[nbTel][nbBases]
     * (are contiguous only in 3tel/3baseline case)*/
    photo_0 = amdlibAlloc2DArrayDouble(nbBases, nbTel, errMsg);
    if (photo_0 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    photo_1 = amdlibAlloc2DArrayDouble(nbBases, nbTel, errMsg);
    if (photo_1 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    mu_k_1 = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (mu_k_1 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    mu_k_2 = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (mu_k_2 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    ck = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (ck == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    dk = amdlibAlloc2DArrayDouble(nx, nbBases, errMsg);
    if (dk == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    sigma2CW = amdlibAlloc2DArrayDouble(nbBases, nl, errMsg);
    if (sigma2CW == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    mat=calloc(nbTel*nbTel, sizeof(double));
    vect=calloc(nbTel,sizeof(double));
    vect2=calloc(nbTel,sizeof(double));
    gamma0=amdlibAlloc2DArrayDouble(nl, nbBases, errMsg);
    if (gamma0 == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    gamma0w=amdlibAlloc2DArrayDouble(nl, nbBases, errMsg);
    if (gamma0w == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    phaseDerivative=amdlibAlloc2DArrayDouble(nl, nbBases, errMsg);
    if (phaseDerivative == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }
    unormalizedP2vmInternalCopy=amdlibAlloc3DArrayDouble(2*nbBases,nx,nl,errMsg);
    if (unormalizedP2vmInternalCopy == NULL)
    {
        amdlibComputeP2VM_FREEALL();
        return amdlibFAILURE;
    }

    /* clean start with default values for all elements in the p2vm */
    /* l = spectral channels */
    for (l = 0; l < nl; l++)   
    {
        /* Set vk and sumVk to 0 */
        for (t = 0; t < nbTel; t++)
        {
            for (j = 0; j < nx; j++)
            {
                p2vm->vkPt[t][l][j] = 0;
            }
        }
        p2vm->sumVkPt[0][l] = 0.0;
        if (nbTel == 3)
        {
            p2vm->sumVkPt[1][l] = 0.0;
            p2vm->sumVkPt[2][l] = 0.0;
        }
        /* Set ck to 1 and dk to invalid */
        for (baseline = 0; baseline < nbBases; baseline++)
        {
            for (j = 0; j < nx; j++)
            {
                p2vm->matrixPt[l][j][baseline] = amdlibInvalidCkDkValue;
                p2vm->matrixPt[l][j][baseline+nbBases] = amdlibInvalidCkDkValue;
                unormalizedP2vmInternalCopy[l][j][baseline] = amdlibInvalidCkDkValue;
                unormalizedP2vmInternalCopy[l][j][baseline+nbBases] = amdlibInvalidCkDkValue;
            }
            p2vm->phasePt[baseline][l] = 0.0;
            gamma0[baseline][l]=0.0;
            gamma0w[baseline][l]=0.0;
        }
    }

    /* l = spectral channels */
    for (l = 0; l < nl; l++)   
    { /* begin for-loop for each wavelength */
        /* In the following comments, the number of shutter closed
         * apply in the 3 telescope case*/

        /* Get photometry info, and copy them into P2VM data structure */
        for (t=0; t<nbTel; t++)
        {
            photo_v[t][0] = scienceData[t].frame[0].photo1[l];
            photo_v[t][1] = scienceData[t].frame[0].photo2[l];
           if (nbTel == 3) photo_v[t][2] = scienceData[t].frame[0].photo3[l];
        }
        for (baseline = 0; baseline < nbBases; baseline++)
        {
            /* The second index of photo_0/1 refers to the baseline, the
             * first to the telescope. */
            photo_0[0][baseline] = 
                scienceData[baseline + 3].frame[0].photo1[l];
            photo_0[1][baseline] = 
                scienceData[baseline + 3].frame[0].photo2[l];
            if (nbTel == 3)
            {
                photo_0[2][baseline] = 
                    scienceData[baseline + 3].frame[0].photo3[l];
            }
            photo_1[0][baseline] = 
                scienceData[baseline + 6].frame[0].photo1[l];
            photo_1[1][baseline] = 
                scienceData[baseline + 6].frame[0].photo2[l];
            if (nbTel == 3)
            {
                photo_1[2][baseline] = 
                    scienceData[baseline + 6].frame[0].photo3[l];
            }
            for (j = 0; j < nx; j++)
            {
                interf_0[baseline][j] =
                    scienceData[baseline + 3].frame[0].intf[l * nx + j];
                interf_1[baseline][j] =
                    scienceData[baseline + 6].frame[0].intf[l * nx + j];
            }
        }
        for (t = 0; t < nbTel; t++)
        {
            for (j = 0; j < nx; j++)
            {
                interf_v[t][j] = scienceData[t].frame[0].intf[l * nx + j];
            }
        }
        if (nbTel == 3) 
        {
            mat[0]=photo_v[0][0];
            mat[1]=photo_v[0][1];
            mat[2]=photo_v[0][2];
            mat[3]=photo_v[1][0];
            mat[4]=photo_v[1][1];
            mat[5]=photo_v[1][2];
            mat[6]=photo_v[2][0];
            mat[7]=photo_v[2][1];
            mat[8]=photo_v[2][2];
        } else {
            mat[0]=photo_v[0][0];
            mat[1]=photo_v[0][1];
            mat[2]=photo_v[1][0];
            mat[3]=photo_v[1][1];
        }
        amdlibInvertMatrix(mat, nbTel);

        /* The Following will differentiate then between flagged and 
         * not flagged wavelengths: 
         */
        if (p2vm->flag[l] == amdlibTRUE)
        {
            /* Computation of the vk coefficients for this wavelength (for all
             * telescopes), taking into account the possible Ghosts.*/
            /* Ghost removal scheme: find the true vki as
             * I1 = v1k*P11 + v2k*P12 + v3k*P13
             * I2 = v1k*P21 + v2k*P22 + v3k*P23
             * I3 = v1k*P31 + v2k*P32 + v3k*P33
             * by inverting the matrix, etc.
             */
            for (j = 0; j < nx; j++)
            {
                double v1,v2,v3;
                if (p2vm->badPixelsPt[l][j] == amdlibGOOD_PIXEL_FLAG)
                {
                    if (nbTel == 3)
                    {
                        vect[0]=interf_v[0][j];
                        vect[1]=interf_v[1][j];
                        vect[2]=interf_v[2][j];
                        if (amdlibProductMatrix(mat, vect, vect2, nbTel, nbTel,1) != amdlibSUCCESS)
                        {
                            amdlibSetErrMsg("Product matrix error.");
                            return amdlibFAILURE;
                        }
                        v1=vect2[0];
                        v2=vect2[1];
                        v3=vect2[2];
                        p2vm->vkPt[0][l][j] = v1;
                        p2vm->vkPt[1][l][j] = v2;
                        p2vm->vkPt[2][l][j] = v3;
                    } else {
                        vect[0]=interf_v[0][j];
                        vect[1]=interf_v[1][j];
                        if (amdlibProductMatrix(mat, vect, vect2, nbTel, nbTel,1) != amdlibSUCCESS)
                        {
                            amdlibSetErrMsg("Product matrix error.");
                            return amdlibFAILURE;
                        }
                        v1=vect2[0];
                        v2=vect2[1];
                        p2vm->vkPt[0][l][j] = v1;
                        p2vm->vkPt[1][l][j] = v2;
                    }
                } else  {
                    for (t = 0; t < nbTel; t++)
                    {
                        p2vm->vkPt[t][l][j] = 0.0;
                    }
                }
            }
            /* Compute theoretical SNR on (yet to come) instrumental
             * visibilities here. Note SNR will really be computed at end */
            /* baseline 1-2 */
            sigma2CW[l][0] = amdlibEstimateCWVariance(
                             &interf_0[0][0],
                             &interf_1[0][0],
                             &p2vm->vkPt[0][l][0],
                             &p2vm->vkPt[1][l][0],
                             photo_0[0][0],
                             photo_0[1][0],
                             photo_1[0][0],
                             photo_1[1][0],
                             amdlibTYPICAL_RON,
                             nx);
            if (nbTel == 3)
            {
                /* Baseline 2-3 */
                sigma2CW[l][1] = amdlibEstimateCWVariance(
                                 &interf_0[1][0],
                                 &interf_1[1][0],
                                 &p2vm->vkPt[1][l][0],
                                 &p2vm->vkPt[2][l][0],
                                 photo_0[1][1],
                                 photo_0[2][1],
                                 photo_1[1][1],
                                 photo_1[2][1],
                                 amdlibTYPICAL_RON,
                                 nx);
                /* Baseline 3-1 */
                sigma2CW[l][2] = amdlibEstimateCWVariance(
                                 &interf_0[2][0],
                                 &interf_1[2][0],
                                 &p2vm->vkPt[2][l][0],
                                 &p2vm->vkPt[0][l][0],
                                 photo_0[2][2],
                                 photo_0[0][2],
                                 photo_1[2][2],
                                 photo_1[0][2],
                                 amdlibTYPICAL_RON,
                                 nx);
            }
            /* Computation of the mu coefficients: */
            for (j = 0; j < nx; j++)  /* j is the spatial extent parameter */
            {
                if (p2vm->badPixelsPt[l][j] == amdlibGOOD_PIXEL_FLAG)
                {
                    if (nbTel == 3)
                    {
                    /* baseline 1-2 */
                        mu_k_1[0][j] = interf_0[0][j] -
                        photo_0[0][0] * p2vm->vkPt[0][l][j] -
                        photo_0[1][0] * p2vm->vkPt[1][l][j] -
                        photo_0[2][0] * p2vm->vkPt[2][l][j];
                        mu_k_2[0][j] = interf_1[0][j] -
                        photo_1[0][0] * p2vm->vkPt[0][l][j] -
                        photo_1[1][0] * p2vm->vkPt[1][l][j] -
                        photo_1[2][0] * p2vm->vkPt[2][l][j];
                        /* baseline 2-3 */
                        mu_k_1[1][j] = interf_0[1][j] -
                        photo_0[0][1] * p2vm->vkPt[0][l][j] -
                        photo_0[1][1] * p2vm->vkPt[1][l][j] -
                        photo_0[2][1] * p2vm->vkPt[2][l][j];
                        mu_k_2[1][j] = interf_1[1][j] -
                        photo_1[0][1] * p2vm->vkPt[0][l][j] -
                        photo_1[1][1] * p2vm->vkPt[1][l][j] -
                        photo_1[2][1] * p2vm->vkPt[2][l][j];
                        /* baseline 3-1 */
                        mu_k_1[2][j] = interf_0[2][j] -
                        photo_0[0][2] * p2vm->vkPt[0][l][j] -
                        photo_0[1][2] * p2vm->vkPt[1][l][j] -
                        photo_0[2][2] * p2vm->vkPt[2][l][j];
                        mu_k_2[2][j] = interf_1[2][j] -
                        photo_1[0][2] * p2vm->vkPt[0][l][j] -
                        photo_1[1][2] * p2vm->vkPt[1][l][j] -
                        photo_1[2][2] * p2vm->vkPt[2][l][j];
                    }
                    else
                    {
                    /* baseline 1-2 : we do not have the ghost information on 3rd beam alas!*/
                        mu_k_1[0][j] = interf_0[0][j] -
                        photo_0[0][0] * p2vm->vkPt[0][l][j] -
                        photo_0[1][0] * p2vm->vkPt[1][l][j];
                        mu_k_2[0][j] = interf_1[0][j] -
                        photo_1[0][0] * p2vm->vkPt[0][l][j] -
                        photo_1[1][0] * p2vm->vkPt[1][l][j];
                    }
                }
                else
                {
                    for (baseline = 0; baseline < nbBases; baseline++)
                    {
                        mu_k_1[baseline][j] = 0.0;
                        mu_k_2[baseline][j] = 0.0;
                    }
                }
            }
            
            /* Computation of sumv1v2 (for all baselines). Bad Pix Vks are 
             * already at 0, so sumvk is OK : */
            p2vm->sumVkPt[0][l] = 0.0;
            if (nbTel == 3)
            {
                p2vm->sumVkPt[1][l] = 0.0;
                p2vm->sumVkPt[2][l] = 0.0;
            }
            for (j = 0; j < nx; j++)
            {
                /* Baseline 1-2 */
                p2vm->sumVkPt[0][l] += p2vm->vkPt[0][l][j] * 
                                       p2vm->vkPt[1][l][j];

                if (nbTel == 3)
                {
                    /* Baseline 2-3 */
                    p2vm->sumVkPt[1][l] += p2vm->vkPt[1][l][j] * 
                                           p2vm->vkPt[2][l][j];
                    /* Baseline 3-1 */
                    p2vm->sumVkPt[2][l] += p2vm->vkPt[2][l][j] * 
                                           p2vm->vkPt[0][l][j];
                }
            }

            /* Computation of cks and first use of dk (to be modified after)
             * bad pixel's ck and dk are at 0 since mu_k_1 and 2 are 0 */
            for (j = 0; j < nx; j++)
            {
                /* Baseline 1-2 */
                ck[0][j] = 0.5 * mu_k_1[0][j] / 
                amdlibSignedSqrt(photo_0[0][0] * photo_0[1][0] * p2vm->sumVkPt[0][l]);                        
                if (nbTel == 3)
                {
                    /* Baseline 2-3 */
                    ck[1][j] = 0.5 * mu_k_1[1][j] /
                    amdlibSignedSqrt(photo_0[1][1] * photo_0[2][1] * p2vm->sumVkPt[1][l]);
                    /* Baseline 3-1 */
                    ck[2][j] = 0.5 * mu_k_1[2][j] /
                    amdlibSignedSqrt(photo_0[2][2] * photo_0[0][2] * p2vm->sumVkPt[2][l]);
                }
                /* Baseline 1-2 */
                dk[0][j] = 0.5 * mu_k_2[0][j] / 
                amdlibSignedSqrt(photo_1[0][0] * photo_1[1][0] * p2vm->sumVkPt[0][l]);
                if (nbTel == 3)
                {
                    /* Baseline 2-3 */
                    dk[1][j] = 0.5 * mu_k_2[1][j] /
                    amdlibSignedSqrt(photo_1[1][1] * photo_1[2][1] * p2vm->sumVkPt[1][l]);
                    /* Baseline 3-1 */
                    dk[2][j] = 0.5 * mu_k_2[2][j] /
                    amdlibSignedSqrt(photo_1[2][2] * photo_1[0][2] * p2vm->sumVkPt[2][l]);
                }
            }

            /* Estimate the correct gamma0 and compute the dks*/
            for (baseline = 0; baseline < nbBases; baseline++)
            {
                sumck2 = 0;
                sumckdk = 0;
                for (j = 0; j < nx; j++) 
                {
                    sumck2 += amdlibPow2(ck[baseline][j]);
                }
                for (j = 0; j < nx; j++) 
                {
                    sumckdk += ck[baseline][j] * dk[baseline][j];
                }
                cosgamm0 = sumckdk / sumck2;
                /* If gamma0 is invalid (may be NaN) */
                if ((cosgamm0==cosgamm0) && cosgamm0 >= -1.0 && cosgamm0 <= 1.0 ) 
                {
                    gamma0[baseline][l]=acos(cosgamm0);
                    gamma0w[baseline][l]=1.0;
                }
                for (j = 0; j < nx; j++)
                {
                    p2vm->matrixPt[l][j][baseline] = ck[baseline][j];
                    p2vm->matrixPt[l][j][baseline+nbBases] = dk[baseline][j];
                    unormalizedP2vmInternalCopy[l][j][baseline] = ck[baseline][j];
                    unormalizedP2vmInternalCopy[l][j][baseline+nbBases] = dk[baseline][j];
                }
            }
        }
    }   /* End for-loop for each wavelength */

    if (amdlibGetUserPref(amdlibGAUSSSMOOTH_P2VM_PHASE).set==amdlibTRUE)
    {
        if (isLowJHK==0)
        {
            fwhm=amdlibGetUserPref(amdlibGAUSSSMOOTH_P2VM_PHASE).value;
            amdlibGaussSmooth(nl, gamma0[0], fwhm);
            if (nbTel == 3)
            {
                amdlibGaussSmooth(nl, gamma0[1], fwhm);
                amdlibGaussSmooth(nl, gamma0[2], fwhm);
            }
        }
    }
    else if (amdlibGetUserPref(amdlibBOXCARSMOOTH_P2VM_PHASE).set==amdlibTRUE)
    {
        if (isLowJHK==0)
        {
            boxcar=floor(amdlibGetUserPref(amdlibBOXCARSMOOTH_P2VM_PHASE).value);
            /* Boxcar Smoothing of the gamma0 values (HR and MR only)*/
            if (boxcar>2 && boxcar<nl/10 ) {
                 amdlibBoxCarSmooth(nl, gamma0[0], boxcar);
                if (nbTel == 3)
                {
                    amdlibBoxCarSmooth(nl, gamma0[1], boxcar);
                    amdlibBoxCarSmooth(nl, gamma0[2], boxcar);
                }
            } 
            else
            {
                amdlibLogWarning("Unappropriate Boxcar Smoothing width, ignored.");
            }
        }
    } 
    else if (amdlibGetUserPref(amdlibLINEARIZE_P2VM_PHASE).set==amdlibTRUE)
    {
        if (isLowJHK==0)
        {
            /* fit a 1st deg function in the gamma0 values in HR and MR mode*/
            amdlibLinearize(nl, gamma0[0],gamma0w[0]);
            if (nbTel == 3)
            {
                amdlibLinearize(nl, gamma0[1],gamma0w[1]);
                amdlibLinearize(nl, gamma0[2],gamma0w[2]);
            }
        }
    }
    /* recompute ck and dk after changes of vlaues of the phase (linear, smooth, etc) */
    for (l = 0; l < nl; l++)   
    {
        for (baseline = 0; baseline < nbBases; baseline++)
        {
           if (gamma0w[baseline][l]==1) 
            {
                cosgamm0 = cos(gamma0[baseline][l]);
                singamm0 = amdlibSignedSqrt(1.0-(cosgamm0 * cosgamm0));
                sumck=0;
                sumdk=0;
                for (j = 0; j < nx; j++) 
                {
                    ck[baseline][j]=p2vm->matrixPt[l][j][baseline];
                    dk[baseline][j]=p2vm->matrixPt[l][j][baseline+nbBases];
                    sumck+=amdlibPow2(ck[baseline][j]);
                    dk[baseline][j] = -ck[baseline][j]*cosgamm0/singamm0+dk[baseline][j]/singamm0; /*rotate dk*/
                    sumdk+=amdlibPow2(dk[baseline][j]);
                }

                /* If we want to Normalize the P2VM to V=1 */
                /* First save unomrlized values in the internal copy:*/
                for (j = 0; j < nx; j++) 
                {
                    unormalizedP2vmInternalCopy[l][j][baseline]=ck[baseline][j];
                    unormalizedP2vmInternalCopy[l][j][baseline+nbBases]=dk[baseline][j];
                }
                /* then apply normalisation if any */
                if (amdlibGetUserPref(amdlibNORMALIZE_P2VM).set==amdlibTRUE)
                {
                    sumck=amdlibSignedSqrt(2*sumck);
                    sumdk=amdlibSignedSqrt(2*sumdk);
                }
                else
                {
                    sumck=1.0;
                    sumdk=1.0;
                }
                for (j = 0; j < nx; j++) 
                {
                    ck[baseline][j] /= sumck;
                    dk[baseline][j] /= sumdk;
                    p2vm->matrixPt[l][j][baseline]=ck[baseline][j];
                    p2vm->matrixPt[l][j][baseline+nbBases]=dk[baseline][j];
                }
                p2vm->phasePt[baseline][l] = gamma0[baseline][l];
            }
        }
    }
    /*in LR mode, tag regions where the bands surperpose :*/
    /* it suffices to look where the derivative of gamma0*lambda (aka the piezo displacement) has big drops */
    if (isLowJHK==1 && amdlibGetUserPref(amdlibZAP_JHK_DISCONTINUTIES).set==amdlibTRUE)
    {
        double median[3];
        int weight;
        for (baseline = 0; baseline < nbBases; baseline++) 
        {
            for (l = 1; l < nl; l++)   
            {
                phaseDerivative[baseline][l]=fabs(p2vm->phasePt[baseline][l]*p2vm->wlen[l]-p2vm->phasePt[baseline][l-1]*p2vm->wlen[l-1])/2.0l/M_PI;
            }
            median[baseline]=amdlibQuickSelectDble(&phaseDerivative[baseline][1],nl-1);
        }
        for (l = 1; l < nl; l++)   
        {
            weight=0;
            for (baseline = 0; baseline < nbBases; baseline++) 
            {
                if (phaseDerivative[baseline][l]>8*median[baseline])
                {
                    weight++;
                }
            } /* to be valid, displacement must be confirmed on 2 or more baselines for 3t, 
               * 1 baseline for 2T */
            if (((double)(weight)/(double)(nbBases))>0.5) 
            {
                p2vm->flag[l] = amdlibFALSE;
                for (k = 0; k <  nbBases; k++)
                {
                    for (j = 0; j < nx; j++)
                    {
                        p2vm->vkPt[k][l][j] = 0;
                        p2vm->matrixPt[l][j][k] = amdlibInvalidCkDkValue;
                        p2vm->matrixPt[l][j][k+nbBases] = amdlibInvalidCkDkValue;
                    }
                    p2vm->sumVkPt[k][l] = 0.0;
                    p2vm->phasePt[k][l] = 0.0;
                    gamma0[k][l]=0.0;
                    gamma0w[k][l]=0.0;
                }
            }
        }
        /*Optionally, compute Offset as difference between 1st wl of K band and a fixed value.*/
        if (amdlibGetUserPref(amdlibAUTO_SHIFT_JHK).set==amdlibTRUE)
        {
            double offset;
            l=0;
            /* jump eventual flagged end of K Band */
            while (p2vm->flag[l]==amdlibFALSE && l< nl)
            {
                l++;
            }
            /* find jump if any */
            while (p2vm->flag[l]==amdlibTRUE && l< nl)
            {
                l++;
            }
            if (l<nl)
            {
                offset=(p2vm->wlen[l-1]-amdlibWAVELENGTH_HK_PHASE_JUMP);
                /* perform this shift */
                for (l = 0; l < nl; l++)
                {
                    p2vm->wlen[l]-=offset;
                }
                amdlibLogInfo("Performed shift of %lf nm to align wavelength table on H-K discontinuity.",offset);
            } 
            else
            {
                amdlibLogInfo("H-K discontinuity not found, requested automatic wavelength shift not performed, sorry.");
            }
        }
    }

    /* Compute fringe contrasts on unormalized values */
    for (l = 0; l < nl; l++)   
    {
        for (baseline = 0; baseline < nbBases; baseline++)
        {
            if (gamma0w[baseline][l]==1) 
            {
                double vis = 0.0;
                for (j = 0; j < nx; j++)
                {
                    ck[baseline][j]=unormalizedP2vmInternalCopy[l][j][baseline];
                    dk[baseline][j]=unormalizedP2vmInternalCopy[l][j][baseline+nbBases];
                    vis += ck[baseline][j] * ck[baseline][j] +
                    dk[baseline][j] * dk[baseline][j];
                }
                if ((sigma2CW[l][baseline] * vis) != 0)
                {
                    /* Filter out value with Err/Vis > 0.2%) */
                    if ((sigma2CW[l][baseline] / amdlibSignedSqrt(vis)) < 0.002)
                    {
                        p2vm->insVis[baseline] += amdlibSignedSqrt(vis);
                        p2vm->insVisErr[baseline] += 
                        1 / (4 * sigma2CW[l][baseline] * vis);
                        nbVis[baseline] += 1;
                        amdlibBAND theBand;
                        theBand = amdlibGetBand(p2vm->wlen[l]);
                        if (theBand != -1)
                        {
                            p2vm->insVisInBand[baseline][theBand] += 
                            amdlibSignedSqrt(vis);
                            p2vm->insVisErrInBand[baseline][theBand] += 
                            1 / (4 * sigma2CW[l][baseline] * vis);
                            nbVisInBand[baseline][theBand] += 1;
                        }
                    }
                }
            }
        }
    }
    /* final estimate of fringe contrast per base, per band.*/
    for (baseline = 0; baseline < nbBases; baseline++)
    {
        if (nbVis[baseline] != 0)
        {
            p2vm->insVis[baseline] = p2vm->insVis[baseline] / nbVis[baseline];
            p2vm->insVisErr[baseline] = 1/p2vm->insVisErr[baseline];
        }
        else
        {
            p2vm->insVis[baseline] = 0.0;
            p2vm->insVisErr[baseline] = 0.0;
        }
        for (band = 0; band < amdlibNB_BANDS; band++)
        {
            if (nbVisInBand[baseline][band] != 0)
            {
                p2vm->insVisInBand[baseline][band] = 
                    p2vm->insVisInBand[baseline][band] / 
                    nbVisInBand[baseline][band];
                p2vm->insVisErrInBand[baseline][band] = 
                    1/p2vm->insVisErrInBand[baseline][band];
            }
            else
            {
                p2vm->insVisInBand[baseline][band] = 0.0;
                p2vm->insVisErrInBand[baseline][band] = 0.0;
            }
        }
    }
    
    amdlibComputeP2VM_FREEALL();

    /* Set the P2VM Id */
    p2vm->id = p2vmData->p2vmId;

    return amdlibSUCCESS;
}

/**
 * Check whether the given channel is valid in P2VM or not. 
 *
 * This function check that the given channel belows to P2VM and is valid; i.e.
 * the corresponding flag is true. If it is a valid channel; this function
 * returns the correponding index in P2VM matrix. 
 *
 * @param p2vm pointer to the P2VM 
 * @param channelNo spectral channel number 
 * @param iChannel spectral channel index in P2VM.
 *
 * @return
 * spectral channel validity flag.
 */
amdlibBOOLEAN amdlibIsValidChannel(amdlibP2VM_MATRIX *p2vm,
                                   int channelNo,
                                   int *iChannel)
{
    /* Compute spectral channel index in P2VM */
    int iP2vmChannel = channelNo - p2vm->firstChannel;
    if ((iP2vmChannel < 0) ||
        (iP2vmChannel >= p2vm->nbChannels) || 
        (p2vm->flag[iP2vmChannel] == amdlibFALSE) ||  
        (p2vm->wlen[iP2vmChannel] <= 0.0))
    {
        *iChannel = -1;
        return amdlibFALSE;
    }
    else
    {
        *iChannel = iP2vmChannel;
        return amdlibTRUE;
    }
}

/**
 * Check compatibility between the science data and P2VM,
 *
 * This function checks compatibility between the science data and P2VM, and
 * returns true if the P2VM can be used to process science data with (in this
 * case) the percentage of spectral channels covered by P2VM.
 *
 * @param scienceData structure where science data is stored
 * @param p2vm structure where P2VM is stored
 * @param percentage percentage of covered channels used as threshold.
 *
 * @return
 * True if P2VM can be used, and false otherwise.
 */
amdlibBOOLEAN amdlibIsP2VMUsable(amdlibSCIENCE_DATA *scienceData,
                                 amdlibP2VM_MATRIX  *p2vm,
                                 amdlibDOUBLE              *percentage)
{
    amdlibBOOLEAN isUsable;
    int l;
    int nbCovSpecChannels;         /* Number of channels covered by P2VM */
    int nbTel;
    isUsable = amdlibTRUE;

    amdlibLogTrace("amdlibIsP2VMUsable()");
    
    /* If P2VM data structure is not initailized, do it */
    if (p2vm->thisPtr != p2vm)
    {
        amdlibInitP2VM(p2vm);
    }

    /* Check the number of telescopes and baselines */
    nbTel = scienceData->nbCols-1;
    if (nbTel == 1)
    {
        isUsable = amdlibFALSE;
        *percentage = 0.0;
    }
    else if ((nbTel == 3) && (p2vm->type == amdlibP2VM_2T))
    {
        isUsable = amdlibFALSE;
        *percentage = 0.0;
    }
    /* Check interferometric channel width */
    else if (p2vm->nx != scienceData->col[amdlibINTERF_CHANNEL].nbPixels)
    {
        isUsable = amdlibFALSE;
        *percentage = 0.0;
    }
    else
    {
        /* Get total number of spectral channels */
        nbCovSpecChannels = 0;
        /* For all channels of the science data */
        for (l = 0; l < scienceData->nbChannels; l++)
        {
            /* If channel belongs to P2VM */
            if ((scienceData->channelNo[l] >= p2vm->firstChannel) &&
                (scienceData->channelNo[l] <= (p2vm->firstChannel + 
                                               p2vm->nbChannels)))
            {
                /* Increase number of covered channels */
                nbCovSpecChannels++;
            }
        }

        /* The percentage of spectral channels covered by P2VM */
        if (nbCovSpecChannels != 0)
        {
            isUsable = amdlibTRUE;
            *percentage = 100.0 * 
            (amdlibDOUBLE)nbCovSpecChannels/(amdlibDOUBLE)scienceData->nbChannels;
        }
        else
        {
            isUsable = amdlibFALSE;
            *percentage = 0.0;
        }
        isUsable = amdlibTRUE;
    }
    return (isUsable);
}

/**
 * Save P2VM into a file. 
 *
 * This function creates FITS file containing P2VM stored in a binary table.
 *
 * @param filename name of the FITS file where 2D images will be stored.
 * @param p2vm structure where P2VM is stored
 * @param accuracy P2VM accuracy (amdlibP2VM_STD_ACC or amdlibP2VM_HIGH_ACC)
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSaveP2VM(const char          *filename,
                                amdlibP2VM_MATRIX   *p2vm,
                                amdlibP2VM_ACCURACY accuracy,
                                amdlibERROR_MSG     errMsg)
{
    char        *ttype[] = {"EFF_WAVE", "MATRIX", "VK", "SUM_VK",
                            "BAD_PIXELS", "PHOTOMETRY", "FLAG",
                            "CALIB_PHASE", "FLAT_FIELD"};
    char        *tform[9];
    char        tform1[36];
    char        tform2[36];
    char        tform3[36];
    char        tform4[36];
    char        tform5[36];
    char        tform6[36];
    char        tform7[36];
    char        tform8[36];
    char        tform9[36];
    char        *tunit[] = {"m", "", "", "", "", "e-", "", "deg", ""};
    int         i, j, k;
    int         status = 0;
    fitsfile    *p2vmFile;
    char        fitsioMsg[256];
#ifndef ESO_CPL_PIPELINE_VARIANT
    struct stat statBuf;
#endif
    amdlibDOUBLE*      theTable;

    int         nbTel;
    int         nbBases;

    char        keywName[amdlibKEYW_NAME_LEN+1];
    char        keywValue[amdlibKEYW_VAL_LEN+1];
    double      obsDate;

    int         colNum;
    char        tDim[32];
    char        tDimVal[32];

    time_t      timeSecs;

    struct tm   *timeNow;
    char        strTime[256];

    int         nl, inl; /*number of wlen and initial number of wlen*/
    int         sp, ep; /*start, end pixel of subset written in fits file*/
    int         nx;
    char        *startTime;
    char        version[32];
    int         band, baseline;

    amdlibLogTrace("amdlibSaveP2VM()");

    /* Check the P2VM is initialised and computed */
    if (p2vm->thisPtr != p2vm)
    {
        amdlibSetErrMsg("P2VM is not initialised"); 
        return amdlibFAILURE;
    }
    if ((p2vm->type != amdlibP2VM_2T) && (p2vm->type != amdlibP2VM_3T))
    {
        amdlibSetErrMsg("P2VM has not been computed"); 
        return amdlibFAILURE;
    }

    /* Init the number of telescopes and baselines according to P2VM type */
    if (p2vm->type == amdlibP2VM_2T)
    {
        nbTel  = 2;
        nbBases = 1;
    }
    else
    {
        nbTel  = 3;
        nbBases = 3;
    }

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

#ifndef ESO_CPL_PIPELINE_VARIANT
    /* 
     * amdlib will create the file on its own
     */
    /* First remove previous P2VM file (if any) */
    if (stat(filename, &statBuf) == 0)
    {
        if (remove(filename) != 0)
        {
            amdlibSetErrMsg("Could not overwrite file %s", filename); 
            return amdlibFAILURE;
        }
    }

    /* Create new FITS file */
    if (fits_create_file(&p2vmFile, filename, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }
#else
    /* 
     * The ESO pipeline will create that file and prepare the header
     * with DMD compliant keywords. Hence amdlib shall just append to it
     */ 

    printf( "Opening File %s for appending...\n", filename  );

    /* Append to FITS file */
    if( fits_open_file(&p2vmFile, filename, READWRITE, &status) )
    {
        printf( "%s->fits_open_file cannot open in append mode\n", filename  );
        amdlibReturnFitsError(filename );
    }
    else
    {
        printf( "%s->fits_open_file append OK\n", filename);
    }
#endif   

    /* Reduce the size of the written P2VM in case spectral channels at start 
     * or end have a zero lambda (normally, only JHK mode). This defines a new 
     * setup of the camera when this library is used by the AMBER OS.
     */
    /* compute start and length of valid (lambda non-null) spectral window */
    inl = p2vm->nbChannels;
    sp = 0;
    while (sp < inl && p2vm->wlen[sp] <= 0.0)
    {
        sp++;
    }
    ep = inl - 1;
    while (ep >= 0 && p2vm->wlen[ep] <= 0.0)
    {
        ep--;
    }
    nl = ep + 1 - sp;
    nx = p2vm->nx;

    /***** Create the binary table */
    sprintf(tform1,"%dE", nl);
    sprintf(tform2,"%dD", nl*nx*2*nbBases);    /* Matrix */
    sprintf(tform3,"%dD", nl*nx*nbTel);        /* Vk */
    sprintf(tform4,"%dD", nl*nbBases);         /* Sum of Vk */
    sprintf(tform5,"%dB", nl*nx);              /* Bad pixels */
    sprintf(tform6,"%dD", nl*(2*nbBases+1)*3); /* Photometry */
    sprintf(tform7,"%dB", nl);                 /* Validity flag */
    sprintf(tform8,"%dD", nl*nbBases);         /* Phase */
    sprintf(tform9,"%dE", nl*nx);              /* FlatField */
    tform[0] = tform1;
    tform[1] = tform2;
    tform[2] = tform3;
    tform[3] = tform4;
    tform[4] = tform5;
    tform[5] = tform6;
    tform[6] = tform7;
    tform[7] = tform8;
    tform[8] = tform9;
    if (fits_create_tbl(p2vmFile, BINARY_TBL, 1, 9, ttype, 
                        tform, tunit, "P2VM", &status) != 0)
    {
        amdlibGetFitsError("P2VM");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /***** Write the new binary table */
    /* Wavelengths table, subset extraction is easy... */
    colNum = 1;
    amdlibDOUBLE wlenInM[nl];
    for (i = 0; i < nl; i++)
    {
        wlenInM[i] = p2vm->wlen[sp + i] * amdlibNM_TO_M;
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , nl,
                       wlenInM, &status) != 0)
    {
        amdlibGetFitsError("EFF_WAVE");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Matrix : convert double to amdlibDOUBLE (subset is just an index shift), then 
     * write */
    colNum = 2;
    /* Allocate amdlibDOUBLE table */
    theTable = calloc(p2vm->nbChannels * p2vm->nx * 6,
                        sizeof(*theTable));
    if (theTable == NULL)
    {
        fits_close_file(p2vmFile, &status);
        amdlibSetErrMsg("Could not allocate memory for temporary buffer");
        return amdlibFAILURE;
    }       
    for (i = 0; i < nl * nx * 2 * nbBases; i++)
    {
        theTable[i] = (amdlibDOUBLE)p2vm->matrix[2 * nbBases * nx * sp + i];
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , 
                       nl * nx * 2*nbBases, theTable, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("MATRIX");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d,%d)", 2 * nbBases, nx, nl);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Matrix'", &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* vk : convert double to amdlibDOUBLE, extracting subset, then write */
    colNum = 3;
    for (k = 0; k < nbTel; k++)
    {
        for (i = 0; i < nl; i++)
        {
            for (j = 0; j < nx ; j++)
            {
                theTable[k*nx*nl + i*nx + j] = (amdlibDOUBLE)p2vm->vkPt[k][i+sp][j];
            }
        }
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , 
                       nl *nx * nbTel, theTable, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("VK");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d,%d)", nx, nl, nbTel);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Vk'", &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* sumVk : convert double to amdlibDOUBLE, extract subset, then write */
    colNum = 4;
    for (j = 0; j < nbBases ; j++)
    {
        for (i = 0; i < nl; i++)
        {
            theTable[j*nl + i] = (amdlibDOUBLE)p2vm->sumVkPt[j][i+sp];
        }
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , 
                       nl * nbBases, theTable, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("SUM_VK");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d)", nl, nbBases);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Sum_of_Vk'", &status) != 0)
    {
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Bad pixel table. fortunately subset is just an index shift */
    colNum = 5;
    if (fits_write_col(p2vmFile, TBYTE, colNum, 1 ,1 , nl * nx,
                       &p2vm->badPixelsPt[0][sp], &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("BAD_PIXELS");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d)", nx, nl);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Bad_pixels'", &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Photometry : convert double to amdlibDOUBLE, extract subset, then write */
    colNum = 6;
    for (i = 0; i < nl; i++)
    {
        for (j = 0; j < (2 * nbBases + 1) * 3 ; j++)
        {
            theTable[j*nl+i] = (amdlibDOUBLE)  p2vm->photometry[j*inl+(i+sp)];
        }
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , 
                       nl * (2 * nbBases + 1) * 3, theTable, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("PHOTOMETRY");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d,%d)", nl, 3, 2*nbBases+1);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Photometry'", &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Validity flag, subset is a shift */
    colNum = 7;
    if (fits_write_col(p2vmFile, TBYTE, colNum, 1 ,1 , 
                       nl, &p2vm->flag[sp], &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("FLAG");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Phase : convert double to amdlibDOUBLE and radians to degrees, extract subset,
     * then write */
    colNum = 8;
    for (j = 0; j < nbBases ; j++)
    {
        for (i = 0; i < nl; i++)
        {
            theTable[j*nl + i] = (amdlibDOUBLE)p2vm->phasePt[j][i+sp] * 180.0 / M_PI;
        }
    }
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , 
                       nl * nbBases, theTable, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("CALIB_PHASE");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Free not used anymore theTable*/
    free(theTable);

    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d)", nl, nbBases);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'Phase'", &status)!=0)
    {
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Flat Field table. fortunately subset is just an index shift */
    colNum = 9;
    if (fits_write_col(p2vmFile, TDOUBLE, colNum, 1 ,1 , nl * nx,
                       &p2vm->flatFieldPt[0][sp], &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("FLAT_FIELD");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    sprintf(tDim, "TDIM%d", colNum);
    sprintf(tDimVal, "(%d,%d)", nx, nl);
    if (fits_write_key(p2vmFile, TSTRING, tDim, tDimVal,
                       "Dimensions of the array 'FlatField'", &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError(tDim);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Complete binary table header */
    if (fits_write_key(p2vmFile, TINT, "NWAVE", &nl,
                       "Number of spectral channels", &status) != 0)
    {
        amdlibGetFitsError("NWAVE");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(p2vmFile, TINT, "WINTERF", &p2vm->nx,
                       "Width of the interferometric channel", &status) != 0)
    {
        amdlibGetFitsError("WINTERF");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    /* Update start pixel */
    sp = (p2vm->firstChannel + 1) + sp;
    if (fits_write_key(p2vmFile, TINT, "STARTPIX", &sp,
                       "First pixel on detector on Y axis for this P2VM",
                       &status) != 0)
    {
        amdlibGetFitsError("STARTPIX");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Complete main header */
    if (fits_movabs_hdu(p2vmFile, 1, 0, &status) != 0)
    {
        amdlibGetFitsError("Main header");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Add DATE */
    timeSecs = time(NULL);
    timeNow = gmtime(&timeSecs);
    strftime(strTime, sizeof(strTime), "%Y-%m-%dT%H:%M:%S", timeNow);
    if (fits_update_key(p2vmFile, TSTRING, "DATE", strTime,
                        "Date this file was written", &status) != 0)
    {
        amdlibGetFitsError("DATE");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Add MJD-OBS and DATE-OBS */
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));
    if (amdlibGetInsCfgKeyword(&(p2vm->insCfg), "MJD-OBS", keywValue, 
                               errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }
    sscanf(keywValue, "%lf", &obsDate);
    /* Add 1 second to not have duplicated MJD-OBS */
    obsDate += 1.0/(24.0*3600.0);
    if (fits_update_key(p2vmFile, TDOUBLE, "MJD-OBS", &obsDate, 
                        "Observation start", &status))
    {
        amdlibGetFitsError("MJD-OBS");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    startTime = amdlibMJD2ISODate(obsDate);
    if (fits_update_key(p2vmFile, TSTRING, "DATE-OBS", startTime,
                        "Observing date", &status) != 0)
    {
        amdlibGetFitsError("DATE-OBS");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

#ifndef ESO_CPL_PIPELINE_VARIANT    
    /* Copy array containing instrument configuration into P2VM structure */
    for (i=0; i < p2vm->insCfg.nbKeywords; i++)
    {
        amdlibKEYW_LINE keywLine;
        if (((strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO INS") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO OBS") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO OCS") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO DET") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO QC P1 ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO QC P2 ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO QC P3 ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, 
                     "HIERARCH ESO TPL") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "ORIGIN  ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "EXPTIME ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "INSTRUME") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "PI-COI  ") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "OBSERVER") != NULL) ||
             (strstr(p2vm->insCfg.keywords[i].name, "UTC     ") != NULL)) &&
            (strstr(p2vm->insCfg.keywords[i].name, 
                    "HIERARCH ESO OCS P2VM") == NULL))
        {
            sprintf((char*)keywLine, "%s=%s/%s", p2vm->insCfg.keywords[i].name,
                    p2vm->insCfg.keywords[i].value, 
                    p2vm->insCfg.keywords[i].comment);
            if (fits_write_record(p2vmFile, keywLine, &status) != 0)
            {
                amdlibGetFitsError(p2vm->insCfg.keywords[i].name);
                fits_close_file(p2vmFile, &status);
                return amdlibFAILURE;
            }
        }
    }

#endif

    /* Add amdlib version */
    amdlibGetVersion(version);
    if (fits_write_key(p2vmFile, TSTRING, "HIERARCH ESO OCS DRS VERSION", 
                       version,
                       "Data Reduction SW version", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO OCS DRS VERSION");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(p2vmFile, TINT, "HIERARCH ESO OCS P2VM NTEL", &nbTel,
                       "Number of telescopes", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO OCS P2VM NTEL");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(p2vmFile, TINT, "HIERARCH ESO OCS P2VM NINTERF", 
                       &nbBases,
                       "Number of interferometric baselines", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO OCS P2VM NINTERF");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

#ifndef ESO_CPL_PIPELINE_VARIANT     
    /* Add DPR keywords */
    if (fits_write_key(p2vmFile, TSTRING, "HIERARCH ESO DPR CATG", "CALIB",
                       "Observation category", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO DPR CATG");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(p2vmFile, TSTRING, "HIERARCH ESO DPR TECH", 
                       "INTERFEROMETRY", "Observation technique", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO DPR TECH");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
#endif    
    if (fits_update_key(p2vmFile, TSTRING, "HIERARCH ESO DPR TYPE", "P2VM",
                        "Observation type", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO DPR TYPE");
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

    /* Add P2VM Id */
    strcpy (keywName, "HIERARCH ESO OCS P2VM ID");
    if (fits_update_key(p2vmFile, TINT, keywName, &p2vm->id,
                        "Unique P2VM id", &status) != 0)
    {
        amdlibGetFitsError(keywName);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }

#ifndef ESO_CPL_PIPELINE_VARIANT  /* TLICHA appears twice in pipline mode */      
    /* Add accuracy keyword */
    strcpy (keywName, "HIERARCH ESO OCS P2VM ACCURACY");
    switch (accuracy)
    {
        case amdlibP2VM_STD_ACC :
            strcpy (keywValue, "std");
            break;
        case amdlibP2VM_HIGH_ACC :
            strcpy (keywValue, "high");
            break;
        default :
            strcpy (keywValue, "unknown");
            break;
    }
    if (fits_write_key(p2vmFile, TSTRING, keywName, keywValue,
                       "P2VM accuracy", &status) != 0)
    {
        amdlibGetFitsError(keywName);
        fits_close_file(p2vmFile, &status);
        return amdlibFAILURE;
    }
#endif

    /* Add instrument visibility keywords */
    /* Check for 'nan' value, warn if found and force them to 0 */
    for (i=0; i<nbBases; i++)
    {
        if (isnan(p2vm->insVis[i]))  
        {
            amdlibLogWarning("Instrument visibility is NaN at "
                             "baseline %d", i);
            p2vm->insVis[i] = 0.0; 
        }
        if (isnan(p2vm->insVisErr[i]))  
        {                     
            amdlibLogWarning("Instrument visibility Error is NaN at "
                             "baseline %d", i);
            p2vm->insVisErr[i] = 0.0; 
        }
        for (band = 0; band < amdlibNB_BANDS; band++)
        {
            if (isnan(p2vm->insVisInBand[i][band]))  
            {
                amdlibLogWarning("Instrument visibility is NaN at "
                                 "baseline %d in band %c", i, 
                                 amdlibBandNumToStr(band));
                p2vm->insVisInBand[i][band] = 0.0; 
            }
            if (isnan(p2vm->insVisErrInBand[i][band]))  
            {                     
                amdlibLogWarning("Instrument visibility Error is NaN at "
                                 "baseline %d in band %c", i, 
                                 amdlibBandNumToStr(band));
                p2vm->insVisErrInBand[i][band] = 0.0; 
            }
        }
    }

    for (baseline = 0; baseline < nbBases; baseline++)
    {
        int baselineNum[amdlibNBASELINE] = {12, 23, 31};
        char comment[128];
        sprintf(keywName, "HIERARCH ESO QC P2VM VIS%d", baselineNum[baseline]);
        sprintf(comment, "Inst. visibility for the baseline %d",
                baselineNum[baseline]);
        if (fits_write_key(p2vmFile, TDOUBLE, keywName, 
                           &p2vm->insVis[baseline], comment, &status) != 0)
        {
            amdlibGetFitsError(keywName);
            fits_close_file(p2vmFile, &status);
            return amdlibFAILURE;
        }
        sprintf(keywName, "HIERARCH ESO QC P2VM ERRVIS%d",
                baselineNum[baseline]);
        sprintf(comment, "Inst. vis. error for the baseline %d",
                baselineNum[baseline]);
        if (fits_write_key(p2vmFile, TDOUBLE, keywName, 
                           &p2vm->insVisErr[baseline], comment, &status) != 0)
        {
            amdlibGetFitsError(keywName);
            fits_close_file(p2vmFile, &status);
            return amdlibFAILURE;
        }

        for (band = 0; band < amdlibNB_BANDS; band++)
        {
            char comment[128];
            sprintf(keywName, "HIERARCH ESO QC P2VM %c VIS%d", 
                    amdlibBandNumToStr(band), baselineNum[baseline]);
            sprintf(comment, "Inst. vis. for %d in %c",
                    baselineNum[baseline], amdlibBandNumToStr(band));
            if (fits_write_key(p2vmFile, TDOUBLE, keywName, 
                               &p2vm->insVisInBand[baseline][band], 
                               comment, &status) != 0)
            {
                amdlibGetFitsError(keywName);
                fits_close_file(p2vmFile, &status);
                return amdlibFAILURE;
            }
            sprintf(keywName, "HIERARCH ESO QC P2VM %c ERRVIS%d",
                    amdlibBandNumToStr(band), baselineNum[baseline]);
            sprintf(comment, "Inst. vis. error for %d in %c",
                    baselineNum[baseline], amdlibBandNumToStr(band));
            if (fits_write_key(p2vmFile, TDOUBLE, keywName, 
                               &p2vm->insVisErrInBand[baseline][band], 
                               comment, &status) != 0)
            {
                amdlibGetFitsError(keywName);
                fits_close_file(p2vmFile, &status);
                return amdlibFAILURE;
            }
        }
    }
    
    /* Close FITS file */
    if (fits_close_file(p2vmFile, &status)) 
    {
        amdlibReturnFitsError(filename);
    }

    return amdlibSUCCESS;
}

/**
 * Load P2VM from file. 
 *
 * This function loads the P2VM from file.
 *
 * @param filename name of the FITS file containing P2VM.
 * @param p2vm structure where P2VM will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadP2VM(const char        *filename,
                                amdlibP2VM_MATRIX *p2vm,
                                amdlibERROR_MSG   errMsg)
{
    int             i,j,k;
    int             status = 0;
    int             anynull = 0;
    fitsfile        *filePtr;
    char            fitsioMsg[256];
    struct stat     statBuf;

    amdlibDOUBLE           *theTable;

    int             nbTel;
    int             nbBases;

    char            keywName[amdlibKEYW_NAME_LEN+1];
    char            keywValue[amdlibKEYW_VAL_LEN+1];

    int             keysExist = 0;
    int             moreKeys = 0;
    int             nbKeywords = 0;
    amdlibKEYW_LINE record;

    amdlibBOOLEAN   convert;

    int index = 0; 

    amdlibLogTrace("amdlibLoadP2VM()");
    
    /* If P2VM data structure is not initailized, do it */
    if (p2vm->thisPtr != p2vm)
    {
        amdlibInitP2VM(p2vm);
    }

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

    /* 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);
    }

#ifndef ESO_CPL_PIPELINE_VARIANT /* TLICHA: for pipline prefer the DET NTEL, OCS P2VM NTEL cannot be found?!? */        
    /* Main header : get number of telescopes and baselines */
    if (fits_read_key(filePtr, TINT, "HIERARCH ESO OCS P2VM NTEL", &nbTel, 
                      NULL, &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TINT, "NTEL", &nbTel, NULL, &status) != 0)
        {
            amdlibGetFitsError("HIERARCH ESO OCS P2VM NTEL");
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
#else
    /* Main header : get number of telescopes and baselines */
    if (fits_read_key(filePtr, TINT, "HIERARCH ESO DET NTEL", &nbTel, 
                      NULL, &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TINT, "NTEL", &nbTel, NULL, &status) != 0)
        {
            amdlibGetFitsError("HIERARCH ESO DET NTEL");
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
#endif    
    

    if (fits_read_key(filePtr, TINT, "HIERARCH ESO OCS P2VM NINTERF", &nbBases, 
                      NULL , &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TINT, "NINTERF", &nbBases, 
                          NULL , &status) != 0)
        {
            amdlibGetFitsError("HIERARCH ESO OCS P2VM NINTERF");
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    

    if ((nbTel == 2) && (nbBases == 1))
    {
        p2vm->type = amdlibP2VM_2T;
    }
    else if ((nbTel == 3) && (nbBases == 3))
    {
        p2vm->type = amdlibP2VM_3T;
    }
    else
    {
        fits_close_file(filePtr, &status);
        amdlibSetErrMsg("Number of telescopes (%d) and number of "
                        "interferometric baselines (%d) are inconsistent",
                        nbTel, nbBases);
        return amdlibFAILURE;
    }

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

    /* Main header : get ndit */
    strcpy (keywName, "HIERARCH ESO DET NDIT");
    if (fits_read_key (filePtr, TINT, keywName, &p2vm->nbFrames,
                       NULL, &status) != 0) 
    {
        p2vm->id = 0;
        status = 0;
    }

    /* Main header : get accuracy */
    strcpy (keywName, "HIERARCH ESO OCS P2VM ACCURACY");
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    if (fits_read_key (filePtr, TSTRING, keywName, keywValue,
                       NULL, &status) != 0) 
    {
        p2vm->accuracy = amdlibP2VM_UNKNOWN_ACC;
        status = 0;
    }
    if (strcmp (keywValue, "std") == 0)
    {
        p2vm->accuracy = amdlibP2VM_STD_ACC;
    }
    else if (strcmp (keywValue, "high") == 0)
    {
        p2vm->accuracy = amdlibP2VM_HIGH_ACC;
    }
    else
    {
        p2vm->accuracy = amdlibP2VM_UNKNOWN_ACC;
    }
    /* Main header : get instrument visibilities */
    /* Check for 'nan' value. If 'nan' force them to 0 */
    for (i=0; i<nbBases; i++)
    {
        if (isnan(p2vm->insVis[i]))  
        {                     
            p2vm->insVis[i] = 0.0; 
        }
        if (isnan(p2vm->insVisErr[i]))  
        {                     
            p2vm->insVisErr[i] = 0.0; 
        }
    }

    strcpy (keywName, "HIERARCH ESO QC P2VM VIS12");
    if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[0], 
                      NULL, &status) != 0)
    {
        /* Try old keyword */
        status = 0;
        strcpy (keywName, "HIERARCH ESO OCS P2VM VIS12");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[0], 
                          NULL, &status) != 0)
        {
            p2vm->insVis[0] = -1;
            status = 0;
        }
    }
    strcpy (keywName, "HIERARCH ESO QC P2VM ERRVIS12");
    if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[0], 
                      NULL, &status) != 0)
    {
        /* Try old keyword */
        status = 0;
        strcpy (keywName, "HIERARCH ESO OCS P2VM ERRVIS12");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[0], 
                          NULL, &status) != 0)
        {
            p2vm->insVisErr[0] = 0.0;
            status = 0;
        }
    }
    if (nbBases == 3)
    {
        strcpy (keywName, "HIERARCH ESO QC P2VM VIS23");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[1], 
                          NULL, &status) != 0)
        {
            /* Try old keyword */
            status = 0;
            strcpy (keywName, "HIERARCH ESO OCS P2VM VIS23");
            if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[1], 
                              NULL, &status) != 0)
            {
                p2vm->insVis[1] = -1;
                status = 0;
            }
        }
        strcpy (keywName, "HIERARCH ESO QC P2VM ERRVIS23");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[1], 
                          NULL, &status) != 0)
        {
            /* Try old keyword */
            status = 0;
            strcpy (keywName, "HIERARCH ESO OCS P2VM ERRVIS23");
            if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[1], 
                              NULL, &status) != 0)
            {
                p2vm->insVisErr[1] = 0.0;
                status = 0;
            }
        }
        strcpy (keywName, "HIERARCH ESO QC P2VM VIS31");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[2], 
                          NULL, &status) != 0)
        {
            /* Try old keyword */
            status = 0;
            strcpy (keywName, "HIERARCH ESO OCS P2VM VIS31");
            if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVis[2], 
                              NULL, &status) != 0)
            {
                p2vm->insVis[2] = -1;
                status = 0;
            }
        }
        strcpy (keywName, "HIERARCH ESO QC P2VM ERRVIS31");
        if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[2], 
                          NULL, &status) != 0)
        {
            /* Try old keyword */
            status = 0;
            strcpy (keywName, "HIERARCH ESO OCS P2VM ERRVIS31");
            if (fits_read_key(filePtr, TDOUBLE, keywName, &p2vm->insVisErr[2], 
                              NULL, &status) != 0)
            {
                p2vm->insVisErr[2] = 0.0;
                status = 0;
            }
        }
    }
    else
    {
        p2vm->insVis[1] = 0;
        p2vm->insVis[2] = 0;
        p2vm->insVisErr[1] = 0;
        p2vm->insVisErr[2] = 0;
    }

    /* Main header : get instrument configuration */
    /* Clear list of P2VM keywords */
    amdlibClearInsCfg(&p2vm->insCfg);

    /* 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 */
    nbKeywords=0;
    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 or a primary keyword */
            if ((strstr(record, "MJD-OBS") != NULL) ||
                (strstr(record, "HIERARCH ESO") != NULL))
            {
                /* Store keyword */
                if (amdlibAddInsCfgKeyword(&p2vm->insCfg,
                                           record, errMsg) == amdlibFAILURE)
                {
                    return amdlibFAILURE;
                }
            }
        }
    }

    /* Go to the P2VM binary table */
    convert = amdlibTRUE;  /* Assume it is the new table */
    if (fits_movnam_hdu(filePtr, BINARY_TBL, "P2VM", 0, &status) != 0)
    {
        /* Try old colunm name */
        status = 0;
        if (fits_movnam_hdu(filePtr, BINARY_TBL, 
                            "P2VM_MATRIX", 0, &status) != 0)
        {
            amdlibGetFitsError("P2VM");
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        convert = amdlibFALSE;  /* It is the old table */
    }

    /* Get table dimensions from binary table header */
    if (fits_read_key(filePtr, TINT, "NWAVE", &p2vm->nbChannels,
                      NULL, &status) != 0)
    {
        amdlibGetFitsError("NWAVE");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_read_key(filePtr, TINT, "WINTERF", &p2vm->nx,
                      NULL, &status) != 0)
    {
        amdlibGetFitsError("WINTERF");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_read_key(filePtr, TINT, "STARTPIX", &p2vm->firstChannel,
                      NULL, &status) != 0)
    {
        amdlibGetFitsError("STARTPIX");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    p2vm->firstChannel --;

    /* If P2VM data structure is not initailized, do it */
    if (p2vm->thisPtr != p2vm)
    {
        amdlibInitP2VM(p2vm);
    }

    /* Allocate memory  */ 
    if (amdlibAllocateP2VM(p2vm, p2vm->nx, nbTel, nbBases,
                           p2vm->nbChannels, errMsg) != amdlibSUCCESS)
    {
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    /* allocate amdlibDOUBLE table */
    theTable=calloc(p2vm->nbChannels*p2vm->nx*6,sizeof(*theTable));
    if (theTable == NULL)
    {
        fits_close_file(filePtr, &status);
        amdlibSetErrMsg("Could not allocate memory for temporary buffer");
        return amdlibFAILURE;
    }       

    /***** Read the binary table */
    /* Wavelengths table */
    if (fits_read_col(filePtr, TDOUBLE, 1, 1 ,1 , p2vm->nbChannels, NULL,
                      p2vm->wlen, &anynull, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("EFF_WAVE");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (convert == amdlibTRUE)
    {
        /* Convert m to nm */
        for (i = 0; i < p2vm->nbChannels; i++)
        {
            p2vm->wlen[i] = p2vm->wlen[i] / amdlibNM_TO_M;
        }
    }

    /* Matrix : read, then convert amdlibDOUBLE to double */
    if (fits_read_col(filePtr, TDOUBLE, 2, 1 ,1 , p2vm->nbChannels * 
                      p2vm->nx * 2 * nbBases,  NULL, theTable, 
                      &anynull, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("MATRIX");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    index = 0;
    for (i = 0; i < p2vm->nbChannels; i++)
    {
        for (j = 0; j < p2vm->nx; j++)
        {
            for (k = 0; k < 2 * nbBases; k++)
            {
                p2vm->matrixPt[i][j][k] = (double)theTable[index];
                index ++;
            }
        }
    }

    /* vk : read, then convert amdlibDOUBLE to double */
    if (fits_read_col(filePtr, TDOUBLE, 3, 1 ,1 , p2vm->nbChannels *
                      p2vm->nx * nbTel,  NULL, theTable,
                      &anynull, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("VK");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    index = 0;
    for (i = 0; i < nbTel; i++)
    {
        for (j = 0; j < p2vm->nbChannels; j++)
        {
            for (k = 0; k < p2vm->nx; k++)
            {
                p2vm->vkPt[i][j][k] = (double)theTable[index];
                index ++;
            }
        }
    }
    /* sumVk : read, then convert amdlibDOUBLE to double */
    if (fits_read_col(filePtr, TDOUBLE, 4, 1 ,1 , p2vm->nbChannels * nbBases,
                      NULL, theTable, &anynull, &status) != 0)
    {
        free(theTable);
        amdlibGetFitsError("SUM_VK");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    index = 0;
    for (i = 0; i < nbBases; i++)
    {
        for(j = 0; j < p2vm->nbChannels; j++)
        {
            p2vm->sumVkPt[i][j] = (double)theTable[index];
            index++;
        }
    }
    
    /* Bad pixel table : If not found mark all pixels as good */
    if (fits_read_col(filePtr, TBYTE, 5, 1 ,1 , p2vm->nbChannels * p2vm->nx,
                      NULL, p2vm->badPixelsPt[0], &anynull, &status) != 0)
    {
        for (i = 0; i < p2vm->nbChannels; i++)
        {
            for (j = 0; j < p2vm->nx; j++)
            {
                p2vm->badPixelsPt[i][j] = amdlibGOOD_PIXEL_FLAG;
            }
        }
        status = 0;
    }

    /* Photometry table : If not found set photometry to 0 */
    memset(theTable , '\0',
           sizeof(double)*p2vm->nbChannels * (2 * nbBases + 1) * 3);
    if (fits_read_col(filePtr, TDOUBLE, 6, 1 ,1 , 
                      p2vm->nbChannels * (2 * nbBases + 1) * 3,
                      NULL, theTable, &anynull, &status) != 0)
    {
        status = 0;
        if (fits_read_col(filePtr, TDOUBLE, 6, 1 ,1 , 
                          p2vm->nbChannels * nbTel,
                          NULL, theTable, &anynull, &status) != 0)
        {
            memset(theTable , '\0',
                   sizeof(double)*p2vm->nbChannels * (2 * nbBases + 1) * 3);
            status = 0;
        }
    }
    index = 0;
    for (i = 0; i < 2 * nbBases + 1; i++)
    {
        for (j = 0; j < 3; j++)
        {
            for (k = 0; k < p2vm->nbChannels; k++)
            {
                p2vm->photometryPt[i][j][k] = (double)theTable[index];
                index++;
            }
        }
    }

    /* Validity flag table : If not found set all flag to true */
    if (fits_read_col(filePtr, TBYTE, 7, 1 ,1 , p2vm->nbChannels,
                      NULL, p2vm->flag, &anynull, &status) != 0)
    {
        for (i = 0; i < p2vm->nbChannels; i++)
        {
            p2vm->flag[i] = amdlibTRUE;
        }
        status = 0;
    }

    /* Phase : read, then convert amdlibDOUBLE to double
     * If not found set all phases to 0 */
    if (fits_read_col(filePtr, TDOUBLE, 8, 1 ,1 , p2vm->nbChannels * nbBases,
                      NULL, theTable, &anynull, &status) != 0)
    {
        for (i = 0; i < p2vm->nbChannels* nbBases; i++)
        {
            theTable[i] = 0.0;
        }
        status = 0;
    }
    index = 0;
    for (i = 0; i < nbBases; i++)
    {
        for (j = 0; j < p2vm->nbChannels; j++)
        {
            if (convert == amdlibTRUE)
            {
                p2vm->phasePt[i][j] = (double)theTable[index] * M_PI / 180.0;
            }
            else
            {
                p2vm->phasePt[i][j] = (double)theTable[index];
            }
            index ++;
        }
    }

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

    /* FlatField table : If not found mark all pixels as 1.0 */
    if (fits_read_col(filePtr, TDOUBLE, 9, 1 ,1 , 
                      p2vm->nbChannels * p2vm->nx,
                      NULL, p2vm->flatFieldPt[0], &anynull, &status) != 0)
    {
        for (i = 0; i < p2vm->nbChannels; i++)
        {
            for (j = 0; j < p2vm->nx; j++)
            {
                p2vm->flatFieldPt[i][j] = 1.0;
            }
        }
        status = 0;
    }

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

    return amdlibSUCCESS;
}

/**
 * Duplicate P2VM. 
 *
 * This function duplicates P2VM; i.e. it allocates memory for new P2VM, and
 * then copies the source P2VM into the new one. 
 *
 * @param srcP2vm source P2VM.
 * @param dstP2vm pointer to the new P2VM 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibDuplicateP2VM (amdlibP2VM_MATRIX *srcP2vm, 
                                      amdlibP2VM_MATRIX *dstP2vm, 
                                      amdlibERROR_MSG   errMsg)
{
    int nbTel;
    int nbBases;
    
    amdlibLogTrace("amdlibDuplicateP2VM()");
    
    /* If P2VM data structure is not initailized, do it */
    if (dstP2vm->thisPtr != dstP2vm)
    {
        amdlibInitP2VM(dstP2vm);
    }
    
    if (srcP2vm->type == amdlibP2VM_2T)
    {
        nbTel  = 2;
        nbBases = 1;
    }
    else if (srcP2vm->type == amdlibP2VM_3T)
    {
        nbTel  = 3;
        nbBases = 3;
    }
    else
    {
        amdlibSetErrMsg("Invalid P2VM type");
        amdlibReleaseP2VM(dstP2vm);
        return amdlibFAILURE;
    }
 
    if (amdlibAllocateP2VM(dstP2vm, srcP2vm->nx, nbTel, nbBases,
                           srcP2vm->nbChannels, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }
 
    if (amdlibCopyP2VM(srcP2vm, dstP2vm, errMsg) != amdlibSUCCESS)
    {
        amdlibReleaseP2VM(dstP2vm);
        return amdlibFAILURE;
    }

    return amdlibSUCCESS;
}

/**
 * Merge P2VM. 
 *
 * This function creates a new P2VM by merging two P2VM. It allocates memory for
 * the merged P2VM.
 *
 * @param firstP2vm first P2VM.
 * @param secondP2vm second P2VM.
 * @param mergedP2vm pointer to the new P2VM 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibMergeP2VM(amdlibP2VM_MATRIX *firstP2vm,
                                 amdlibP2VM_MATRIX *secondP2vm,
                                 amdlibP2VM_MATRIX *mergedP2vm,
                                 amdlibERROR_MSG   errMsg)
{
    amdlibP2VM_MATRIX *p2vm1;
    amdlibP2VM_MATRIX *p2vm2;
    amdlibP2VM_MATRIX *currentP2vm;
    int               nx;
    int               nbTels;
    int               nbBases;
    int               nbChannels;
    int               coveringPixBegin;
    int               coveringPixEnd;
    int               currentPixel;
    int               iPix;
    int               iPix2;
    int               i;
    int               j;
     
    amdlibLogTrace("amdlibMergeP2VM()");
    
    /* Ordering firstP2vm and secondP2vm relative to their start pixels */
    if (firstP2vm->firstChannel < secondP2vm->firstChannel)
    {
        p2vm1 = firstP2vm;
        p2vm2 = secondP2vm;
    }
    else
    {
        p2vm1 = secondP2vm;
        p2vm2 = firstP2vm;
    }
    
    /* Check there is no error in the given parametres - 
     * check p2vm1 and p2vm2 have the same configuration */
    if (p2vm1->id == p2vm2->id)
    {
        amdlibSetErrMsg("Wrong input data - same p2vm ids");
        return amdlibFAILURE;
    }

    if ((p2vm1->type == 1 && p2vm2->type != 1) 
        || (p2vm1->type != 1 && p2vm2->type == 1))
    {
        amdlibSetErrMsg("Wrong input data - different p2vm types");
        return amdlibFAILURE;        
    }
    if (p2vm1->accuracy != p2vm2->accuracy)
    {
        amdlibSetErrMsg("Wrong input data - different accuracies");
        return amdlibFAILURE;        
    }
    if (p2vm1->nx != p2vm2->nx)
    {
        amdlibSetErrMsg("Wrong input data - different number of pixels "
                        "in columns");
        return amdlibFAILURE;        
    }
    
    /* Check the compatibility between wave lengths */
    if ((p2vm1->firstChannel + p2vm1->nbChannels) > p2vm2->firstChannel)
    {
        amdlibSetErrMsg("Incompatible wave lengths");
        return amdlibFAILURE;
    }
      
    /* Allocate mergedP2vm */
    nx = p2vm1->nx;
    coveringPixBegin = p2vm2->firstChannel;
    if (p2vm1->firstChannel + p2vm1->nbChannels == p2vm2->firstChannel)
    {
        coveringPixEnd = p2vm2->firstChannel;
        nbChannels = p2vm1->nbChannels + p2vm2->nbChannels;
     }
    else
    {
        coveringPixEnd = p2vm1->firstChannel + p2vm1->nbChannels;
        nbChannels = p2vm1->nbChannels + p2vm2->nbChannels 
                         - (coveringPixEnd - coveringPixBegin + 1); 
     }
    if (p2vm1->type == amdlibP2VM_2T)
    {
        nbTels = 2;
        nbBases = 1;
    }
    else if (p2vm1->type == amdlibP2VM_3T)
    {
        nbTels = 3;
        nbBases = 3;
    }
    else
    {
        amdlibSetErrMsg("Invalid P2VM type");
        return amdlibFAILURE;
    }

    /* If mergedP2vm is not initialized, do it */
    if (mergedP2vm->thisPtr != mergedP2vm)
    {
        amdlibInitP2VM(mergedP2vm);
    }
    /* Allocate memory */
    if (amdlibAllocateP2VM(mergedP2vm, nx, nbTels, nbBases, 
                           nbChannels, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* Initialize id, inscfg, startPixel, type and accuracy */
    mergedP2vm->id = p2vm1->id + p2vm2->id;
    
    amdlibClearInsCfg(&mergedP2vm->insCfg);    
    for (i = 0; i < p2vm1->insCfg.nbKeywords; i++)
    {
        if (strstr(p2vm1->insCfg.keywords[i].name, 
                   "HIERARCH ESO OCS P2VM") == NULL)
        {
            if (amdlibSetInsCfgKeyword(&mergedP2vm->insCfg, 
                                       p2vm1->insCfg.keywords[i].name,
                                       p2vm1->insCfg.keywords[i].value,
                                       p2vm1->insCfg.keywords[i].comment,
                                       errMsg) != amdlibSUCCESS)
            {
                return amdlibFAILURE;
            }
        }
    }
    
    mergedP2vm->firstChannel = p2vm1->firstChannel;
    mergedP2vm->type = p2vm1->type;
    mergedP2vm->accuracy = p2vm1->accuracy;

    /* Initialize arrays */
    iPix2 = -1;
    for (currentPixel = 0; currentPixel < nbChannels; currentPixel++)
    {
        /* Choice of the p2vm values to be copied */
        if (currentPixel < coveringPixBegin - p2vm1->firstChannel)
        {
            currentP2vm = p2vm1;
            iPix = currentPixel;
        }
        else if (currentPixel < coveringPixEnd - p2vm1->firstChannel)
        {
            iPix2 ++;
            if (p2vm1->flag[currentPixel] == amdlibTRUE 
                && p2vm2->flag[iPix2] == amdlibTRUE)
            {
                amdlibSetErrMsg("Inconsistent data in "
                                "covered spectral channels zone");
                amdlibReleaseP2VM(mergedP2vm);
                return amdlibFAILURE;
            }
            else if (p2vm1->flag[currentPixel] == amdlibTRUE)
            {
                currentP2vm = p2vm1;
                iPix = currentPixel;
            }
            else
            {
                currentP2vm = p2vm2;
                iPix = iPix2;
            }
        }
        else
        {
            currentP2vm = p2vm2;
            iPix2 ++;
            iPix = iPix2;
        }
        
        /* Copying values */
        mergedP2vm->wlen[currentPixel] = currentP2vm->wlen[iPix];
        mergedP2vm->flag[currentPixel] = currentP2vm->flag[iPix];
        for(i = 0; i < nbBases; i++)
        {
            mergedP2vm->sumVkPt[i][currentPixel] = 
                currentP2vm->sumVkPt[i][iPix];
            mergedP2vm->phasePt[i][currentPixel] = 
                currentP2vm->phasePt[i][iPix];
        }
        for (i = 0; i < nx; i++)
        {
            mergedP2vm->badPixelsPt[currentPixel][i]
                = currentP2vm->badPixelsPt[iPix][i];
            mergedP2vm->flatFieldPt[currentPixel][i]
                = currentP2vm->flatFieldPt[iPix][i];
            
            for(j = 0; j < 2 * nbBases; j++)
            {
                mergedP2vm->matrixPt[currentPixel][i][j]
                    = currentP2vm->matrixPt[iPix][i][j];
            }
        }
        for (i = 0; i < nbTels; i++)
        {
            for (j = 0; j < nx; j++)
            {
                mergedP2vm->vkPt[i][currentPixel][j] 
                    = currentP2vm->vkPt[i][iPix][j];
            }
        }
        for (i = 0; i < 2 * nbBases + 1; i++)
        {
            for (j = 0; j < 3; j ++)
            {
                mergedP2vm->photometryPt[i][j][currentPixel]
                    = currentP2vm->photometryPt[i][j][iPix];
            }
        }
    }
          
    /* TODO tbx insVis et insVisErr */
    for (i = 0; i < amdlibNBASELINE; i++)
    {
        mergedP2vm->insVis[i] = p2vm1->insVis[i];
        mergedP2vm->insVisErr[i] = p2vm1->insVisErr[i];
    }
    
    return amdlibSUCCESS;
}

/**
 * Copy P2VM. 
 *
 * This function copies P2VM the source P2VM into the destination one. 
 *
 * @param srcP2vm source P2VM.
 * @param dstP2vm pointer to the destination P2VM 
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibCopyP2VM (amdlibP2VM_MATRIX *srcP2vm, 
                                 amdlibP2VM_MATRIX *dstP2vm, 
                                 amdlibERROR_MSG   errMsg)
{
    int nbTel;
    int nbBases;
    int i;

    amdlibLogTrace("amdlibCopyP2VM()");
    
    /* Init the number of telescopes and baselines according to P2VM type */
    if (srcP2vm->type == amdlibP2VM_2T)
    {
        nbTel  = 2;
        nbBases = 1;
    }
    else
    {
        nbTel  = 3;
        nbBases = 3;
    }

    /* Copy fixed size fields */
    for (i = 0; i < srcP2vm->insCfg.nbKeywords; i++)
    {
        if (amdlibSetInsCfgKeyword(&dstP2vm->insCfg, 
                                   srcP2vm->insCfg.keywords[i].name,
                                   srcP2vm->insCfg.keywords[i].value,
                                   srcP2vm->insCfg.keywords[i].comment,
                                   errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }
    dstP2vm->type = srcP2vm->type;
    dstP2vm->id = srcP2vm->id;
    dstP2vm->firstChannel = srcP2vm->firstChannel;
    dstP2vm->nx = srcP2vm->nx;
    dstP2vm->nbChannels = srcP2vm->nbChannels;
    for (i = 0; i < amdlibNBASELINE; i++)
    {
        dstP2vm->insVis[i] = srcP2vm->insVis[i];
        dstP2vm->insVisErr[i] = srcP2vm->insVisErr[i];
    }

    /* Copy fields allocated dynamically */
    memcpy (dstP2vm->wlen, srcP2vm->wlen, 
            srcP2vm->nbChannels * sizeof(amdlibDOUBLE)); 
    memcpy (dstP2vm->matrix, srcP2vm->matrix, 
            2*srcP2vm->nx * srcP2vm->nbChannels * nbBases * sizeof(double)); 
    memcpy (dstP2vm->vk, srcP2vm->vk, 
            srcP2vm->nx * srcP2vm->nbChannels * nbTel * sizeof(double)); 
    memcpy (dstP2vm->sumVk, srcP2vm->sumVk, 
            srcP2vm->nbChannels * nbBases * sizeof(double)); 
    memcpy (dstP2vm->photometry, srcP2vm->photometry, 
            srcP2vm->nbChannels * (2 * nbBases + 1) * 3 * sizeof(double)); 
    memcpy (dstP2vm->badPixels, srcP2vm->badPixels, 
            srcP2vm->nx * srcP2vm->nbChannels * sizeof(char)); 
    memcpy (dstP2vm->flatField, srcP2vm->flatField, 
            srcP2vm->nx * srcP2vm->nbChannels * sizeof(amdlibDOUBLE)); 
    memcpy (dstP2vm->flag, srcP2vm->flag, 
            srcP2vm->nbChannels * sizeof(char)); 
    memcpy (dstP2vm->phase, srcP2vm->phase, 
            srcP2vm->nbChannels * nbBases * sizeof(double));

    return amdlibSUCCESS;
}

/*
 * Local functions definitions
 */

/**
 * Initialize P2VM structure. 
 *
 * @param p2vm pointer to P2VM structure
 */
void amdlibInitP2VM(amdlibP2VM_MATRIX *p2vm)
{
    amdlibLogTrace("amdlibInitP2VM()");
    
    /* Initialize data structure */
    memset (p2vm, '\0', sizeof(amdlibP2VM_MATRIX));
    p2vm->thisPtr = p2vm;
}

/**
 * Allocate memory for storing P2VM. 
 *
 * This functionallocates memory, according to the number of pixels of the
 * interferometric column 'nx' and the number of spectral channels
 * 'nbChannels', for the arrays of the 'p2vm' data structure. If memory has
 * been previously allocated, it is first freed.
 *
 * @param p2vm pointer to P2VM structure
 * @param nx width of the interferometric channel.
 * @param nbTel number of telescopes.
 * @param nbBases number of baselines.
 * @param nbChannels number of spectral channels.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAllocateP2VM(amdlibP2VM_MATRIX *p2vm, const int nx,
                                    const int nbTel, const int nbBases,
                                    const int nbChannels,
                                    amdlibERROR_MSG errMsg)
{
    amdlibLogTrace("amdlibAllocateP2VM()");
    
    /* First free previous allocated memory */
    amdlibFreeP2VM(p2vm);

    /* Array containing the wavelengths */
    p2vm->wlen = (amdlibDOUBLE *)calloc(nbChannels, sizeof(amdlibDOUBLE));
    if (p2vm->wlen == NULL)
    {
        amdlibSetErrMsg("Could not allocate %ld bytes for wlen", 
                        (long)(nbChannels * sizeof(amdlibDOUBLE)));
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    } 

    /* Array containing the ck and dk coefficients */
    p2vm->matrixPt = 
        amdlibAlloc3DArrayDouble( 2 * nbBases, nx, nbChannels, errMsg);
    if (p2vm->matrixPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->matrix = p2vm->matrixPt[0][0];    

    /* Arrays containing the vk coefficients */
    p2vm->vkPt = amdlibAlloc3DArrayDouble(nx, nbChannels, nbTel, errMsg);
    if (p2vm->vkPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->vk = p2vm->vkPt[0][0];

    /* Arrays containing the sumVk */
    p2vm->sumVkPt = 
        amdlibAlloc2DArrayDouble(nbChannels, nbBases, errMsg);
    if (p2vm->sumVkPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->sumVk = p2vm->sumVkPt[0];

    /* Arrays containing the photometry */
    /* double photometry[2*nbase+1][3][nl] */
    p2vm->photometryPt = 
        amdlibAlloc3DArrayDouble(nbChannels, 3, 2 * nbBases + 1, errMsg);
    if (p2vm->photometryPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->photometry = p2vm->photometryPt[0][0];


    /* Arrays containing the bad pixel flags */
    p2vm->badPixelsPt = 
        amdlibAlloc2DArrayUnsignedChar(nx, nbChannels, errMsg);
    if (p2vm->badPixelsPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->badPixels = p2vm->badPixelsPt[0];

     /* Arrays containing the FlatField */
    p2vm->flatFieldPt = amdlibAlloc2DArrayDouble(nx, nbChannels, errMsg); 
    if (p2vm->flatFieldPt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->flatField = p2vm->flatFieldPt[0];

    /* Arrays containing the validity flags for spectral channels */
    p2vm->flag = (unsigned char *)calloc(nbChannels, sizeof(char));
    if (p2vm->flag == NULL)
    {
        amdlibSetErrMsg("Could not allocate %ld bytes for flag", 
                        (long)(nbChannels * sizeof(amdlibDOUBLE)));
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }

    /* Arrays containing the phase */
    p2vm->phasePt =
        amdlibAlloc2DArrayDouble(nbChannels, nbBases, errMsg);
    if (p2vm->phasePt == NULL)
    {
        amdlibFreeP2VM(p2vm);
        return amdlibFAILURE;
    }
    p2vm->phase = p2vm->phasePt[0];

    /* Set array size */
    p2vm->nx = nx;
    p2vm->nbChannels = nbChannels;

    /* Return the new pointer */
    return amdlibSUCCESS;
}

/**
 * Free memory allocated for P2VM. 
 *
 * This function frees memory previously allocated by amdlibAllocateP2VM()
 * function.
 * 
 * @param p2vm pointer to P2VM structure
 */
void amdlibFreeP2VM(amdlibP2VM_MATRIX *p2vm)
{
    amdlibLogTrace("amdlibFreeP2VM()");
    
    /* Free allocated memory */
    if (p2vm->wlen != NULL)
    {
        free(p2vm->wlen);
        p2vm->wlen = NULL;
    }
    if (p2vm->matrix != NULL)
    {
        amdlibFree3DArrayDouble(p2vm->matrixPt);
        p2vm->matrix = NULL;
    }
    if (p2vm->vk != NULL)
    {
        amdlibFree3DArrayDouble(p2vm->vkPt);
        p2vm->vk = NULL;
     }
    if (p2vm->sumVk != NULL)
    {
        amdlibFree2DArrayDouble(p2vm->sumVkPt);
        p2vm->sumVk = NULL;
    }
    if (p2vm->photometry != NULL)
    {
        amdlibFree3DArrayDouble(p2vm->photometryPt);
        p2vm->photometry = NULL;
    }
    if (p2vm->badPixels != NULL)
    {
        amdlibFree2DArrayUnsignedChar(p2vm->badPixelsPt);
        p2vm->badPixels = NULL;
    }
    if (p2vm->flatField != NULL)
    {
        amdlibFree2DArrayDouble(p2vm->flatFieldPt);
        p2vm->flatField = NULL;
    }
    if (p2vm->flag != NULL)
    {
        free(p2vm->flag);
        p2vm->flag = NULL;
    }
    if (p2vm->phase != NULL)
    {
        amdlibFree2DArrayDouble(p2vm->phasePt);
        p2vm->phase = NULL;
    }
    /* Reset array size */
    p2vm->nx = 0;
    p2vm->nbChannels = 0;
}

/**
 * Computes the variance of (the square of the) instrumental contrast.
 *
 * This function computes the variance of (the square of the) instrumental
 * contrast of the p2vm. Based on memo AMB-IGR-022
 *
 * @return
 * Variance of (the square of the) instrumental contrast
 */
double amdlibEstimateCWVariance(double *ikb, 
                                double *ikg,
                                double *v1,
                                double *v2, 
                                double P1b,
                                double P2b,
                                double P1g,
                                double P2g,
                                amdlibDOUBLE ron, /* hope ron did not change*/
                                int nx) /*width of the I channel */

{
    /* b (resp. g) last char in name refer to measurements without (resp. with) 
     * the lambda/4 phase shift*/
    double sig2_P1b=P1b;
    double sig2_P2b=P2b;
    double sig2_P1g=P1g;
    double sig2_P2g=P2g;
    double mean_mukb;
    double mean_mukg;
    double tkb; 
    double tkg;
    double v1k;
    double v2k; 
    double sumvk=0.0;
    double s1g, s2g, s1b, s2b;
    double E_mu4_beta, E_mu4_gamma, E_mu2_2_beta, E_mu2_2_gamma;  
    double sig2_muk2_beta,sig2_muk2_gamma;
    double sig2_muk2_p1p2_beta,sig2_muk2_p1p2_gamma;
    double sig2_CWk=0.0;
    int i;

    amdlibLogTrace("amdlibEstimateCWVariance()");
    
    /* Compute sumvk */
    for (i=0; i<nx ; i++)
    {
        sumvk+= v1[i]*v2[i];
    }

    /* In order to keep formula short enough, we substitute indexed variables
     * with static variables */
    for (i=0; i<nx ; i++)
    { 
        v1k=v1[i];
        v2k=v2[i];
        tkb=ikb[i];
        tkg=ikg[i];

        /* Standard sigma squared on photometry + detector noise. To be replaced
         * by a correct value for snr on each photometry channel (to be computed
         * in amdlibScienceData: for example, nx below may not be the number of
         * pixels used in the photometric channel.
         */
        mean_mukb =  tkb - v1k*P1b - v2k*P2b;
        mean_mukg =  tkg - v1k*P1g - v2k*P2g;

        s1b = -4.0*tkb*v2k*v2k*v2k*
            (P2b*P2b*P2b+3.0*P2b*P2b+P2b+3.0*P2b*nx*ron*ron)+
            tkb*tkb*tkb*tkb+6.0*tkb*tkb*tkb+7.0*tkb*tkb+tkb+
            6.0*(tkb*tkb+tkb)*ron*ron+
            3.0*ron*ron*ron*ron+
            4.0*v1k*v1k*v1k*
            (P1b*P1b*P1b+3.0*P1b*P1b+P1b+3.0*P1b*nx*ron*ron)*v2k*P2b+
            6.0*v1k*v1k*(P1b*P1b+P1b+nx*ron*ron)*
            v2k*v2k*(P2b*P2b+P2b+nx*ron*ron)+
            4.0*v1k*P1b*v2k*v2k*v2k*
            (P2b*P2b*P2b+3.0*P2b*P2b+P2b+3.0*P2b*nx*ron*ron);

        s2b = s1b-4.0*(tkb*tkb*tkb+3.0*tkb*tkb+tkb+3.0*tkb*ron*ron)*
            v1k*P1b-4.0*(tkb*tkb*tkb+3.0*tkb*tkb+tkb+3.0*tkb*ron*ron)*
            v2k*P2b+6.0*(tkb*tkb+tkb+ron*ron)*v1k*v1k*
            (P1b*P1b+P1b+nx*ron*ron)+6.0*(tkb*tkb+tkb+ron*ron)*
            v2k*v2k*(P2b*P2b+P2b+nx*ron*ron);

        E_mu4_beta = s2b-4.0*tkb*v1k*v1k*v1k*
            (P1b*P1b*P1b+3.0*P1b*P1b+P1b+3.0*P1b*nx*ron*ron)+
            v1k*v1k*v1k*v1k*
            (P1b*P1b*P1b*P1b+6.0*P1b*P1b*P1b+7.0*P1b*P1b+P1b+
             6.0*(P1b*P1b+P1b)*nx*ron*ron+(3.0*nx+2.0*nx*nx)*
             ron*ron*ron*ron)+
            v2k*v2k*v2k*v2k*
            (P2b*P2b*P2b*P2b+6.0*P2b*P2b*P2b+7.0*P2b*P2b+P2b+
             6.0*(P2b*P2b+P2b)*nx*ron*ron+(3.0*nx+2.0*nx*nx)*
             ron*ron*ron*ron)+
            12.0*(tkb*tkb+tkb+ron*ron)*
            v1k*P1b*v2k*P2b-12.0*tkb*v1k*v1k*(P1b*P1b+P1b+nx*ron*ron)*
            v2k*P2b-12.0*tkb*v1k*P1b*v2k*v2k*(P2b*P2b+P2b+nx*ron*ron);

        E_mu2_2_beta = tkb*tkb+tkb+ron*ron-
            2.0*tkb*v1k*P1b-2.0*tkb*v2k*P2b+v1k*v1k*
            (P1b*P1b+P1b+nx*ron*ron)+2.0*
            v1k*P1b*v2k*P2b+v2k*v2k*
            (P2b*P2b+P2b+nx*ron*ron);

        sig2_muk2_beta = E_mu4_beta -  E_mu2_2_beta;

        s1g = -4.0*tkg*v2k*v2k*v2k*
            (P2g*P2g*P2g+3.0*P2g*P2g+P2g+3.0*P2g*nx*ron*ron)+
            tkg*tkg*tkg*tkg+6.0*tkg*tkg*tkg+7.0*tkg*tkg+tkg+
            6.0*(tkg*tkg+tkg)*ron*ron+
            3.0*ron*ron*ron*ron+
            4.0*v1k*v1k*v1k*
            (P1g*P1g*P1g+3.0*P1g*P1g+P1g+3.0*P1g*nx*ron*ron)*v2k*P2g+
            6.0*v1k*v1k*(P1g*P1g+P1g+nx*ron*ron)*
            v2k*v2k*(P2g*P2g+P2g+nx*ron*ron)+
            4.0*v1k*P1g*v2k*v2k*v2k*
            (P2g*P2g*P2g+3.0*P2g*P2g+P2g+3.0*P2g*nx*ron*ron);

        s2g = s1g-4.0*(tkg*tkg*tkg+3.0*tkg*tkg+tkg+3.0*tkg*ron*ron)*
            v1k*P1g-4.0*(tkg*tkg*tkg+3.0*tkg*tkg+tkg+3.0*tkg*ron*ron)*
            v2k*P2g+6.0*(tkg*tkg+tkg+ron*ron)*v1k*v1k*
            (P1g*P1g+P1g+nx*ron*ron)+6.0*(tkg*tkg+tkg+ron*ron)*
            v2k*v2k*(P2g*P2g+P2g+nx*ron*ron);

        E_mu4_gamma = s2g-4.0*tkg*v1k*v1k*v1k*
            (P1g*P1g*P1g+3.0*P1g*P1g+P1g+3.0*P1g*nx*ron*ron)+
            v1k*v1k*v1k*v1k*
            (P1g*P1g*P1g*P1g+6.0*P1g*P1g*P1g+7.0*P1g*P1g+P1g+
             6.0*(P1g*P1g+P1g)*nx*ron*ron+(3.0*nx+2.0*nx*nx)*
             ron*ron*ron*ron)+
            v2k*v2k*v2k*v2k*
            (P2g*P2g*P2g*P2g+6.0*P2g*P2g*P2g+7.0*P2g*P2g+P2g+
             6.0*(P2g*P2g+P2g)*nx*ron*ron+(3.0*nx+2.0*nx*nx)*
             ron*ron*ron*ron)+
            12.0*(tkg*tkg+tkg+ron*ron)*
            v1k*P1g*v2k*P2g-12.0*tkg*v1k*v1k*(P1g*P1g+P1g+nx*ron*ron)*
            v2k*P2g-12.0*tkg*v1k*P1g*v2k*v2k*(P2g*P2g+P2g+nx*ron*ron);

        E_mu2_2_gamma = tkg*tkg+tkg+ron*ron-
            2.0*tkg*v1k*P1g-2.0*tkg*v2k*P2g+v1k*v1k*
            (P1g*P1g+P1g+nx*ron*ron)+2.0*
            v1k*P1g*v2k*P2g+v2k*v2k*
            (P2g*P2g+P2g+nx*ron*ron);

        sig2_muk2_gamma = E_mu4_gamma -  E_mu2_2_gamma;

        /* Lets use Papoulis formula*/
        sig2_muk2_p1p2_beta = sig2_muk2_beta/(P1b*P1b*P2b*P2b) + 
            (sig2_P1b/(P1b*P1b))*(pow(mean_mukb,4.0)/(P1b*P1b*P2b*P2b)) + 
            sig2_P2b/(P2b*P2b)*(pow(mean_mukb,4.0)/(P1b*P1b*P2b*P2b));
        sig2_muk2_p1p2_gamma = sig2_muk2_gamma/(P1g*P1g*P2g*P2g) + 
            (sig2_P1g/(P1g*P1g))*(pow(mean_mukg,4.0)/(P1g*P1g*P2g*P2g)) + 
            sig2_P2g/(P2g*P2g)*(pow(mean_mukg,4.0)/(P1g*P1g*P2g*P2g));

        /* Sum Errors on carrying wave */
        sig2_CWk += (1.0/amdlibPow2(16.0*sumvk))*
        (sig2_muk2_p1p2_beta+sig2_muk2_p1p2_gamma);
    }    

    return sig2_CWk;
}

/**
 * Display P2VM 
 *
 * @param p2vm pointer to P2VM structure
 */
void amdlibDisplayP2vm(amdlibP2VM_MATRIX *p2vm)
{
    int i, j, k;
    int nbTel, nbBases;
    
    if (p2vm->type == amdlibP2VM_2T)
    {
        nbTel  = 2;
        nbBases = 1;
    }
    else
    {
        nbTel  = 3;
        nbBases = 3;
    }
   
#if 0
    /* Results for insCfg */
    printf("insCfg :\n");
    printf("p2vm->insCfg.nbKeywords = %d\n", p2vm->insCfg.nbKeywords);
    for (i = 0; i < p2vm->insCfg.nbKeywords; i ++)
    {
        printf("i = %d, name = %s, value = %s, comment = %s\n", i,
               p2vm->insCfg.keywords[i].name, p2vm->insCfg.keywords[i].value, 
               p2vm->insCfg.keywords[i].comment);
    }
#endif

    /* Fixed sized values */
    printf("type = %d\n", p2vm->type);
    printf("accuracy = %d\n", p2vm->accuracy);
    printf("firstChannel = %d\n", p2vm->firstChannel);
    printf("nx = %d\n", p2vm->nx);
    printf("nbChannels = %d\n", p2vm->nbChannels);
   
#if 1
    /* wlen table */
    printf("wlen : \n");
    for (i = 0; i < p2vm->nbChannels; i++)
    {
        printf("wlen[%d] = %f, flag = %d\n", i, p2vm->wlen[i], p2vm->flag[i]);
    }
    /* matrix */
    printf("matrix : \n");
    for (i = 0; i < 2 * nbBases; i++)
    {
        for (j = 0; j < p2vm->nx; j++)
        {
            for (k = 0; k < p2vm->nbChannels; k ++)
            {
                printf("matrix[%d][%d][%d] = %f\n", i, j, k, 
                       p2vm->matrixPt[k][j][i]);
            }
        }
    }
#endif
    /* vk */
    printf("vk :\n");
    for (i = 0; i < p2vm->nx; i++)
    {
        for (j = 0; j < p2vm->nbChannels; j ++)
        {
            for (k = 0; k < nbTel; k ++)
            {
                printf("vk[%d][%d][%d] = %f\n", i, j, k, p2vm->vkPt[k][j][i]);
            }
        }
    }
    /* sumvk */
    printf("sumVk :\n");
    for (i = 0; i < p2vm->nbChannels; i ++)
    {
        for (j = 0; j < nbBases; j ++)
        {
            printf("sumVk[%d][%d] = %f\n", i, j, p2vm->sumVkPt[j][i]);
        }
    }
#if 1
    /* bpm */
    printf("bpm :\n");
    for (i = 0; i < p2vm->nbChannels; i ++)
    {
        for (j = 0; j < p2vm->nx; j ++)
        {
            printf("badPixels[%d][%d] = %d\n", i, j, p2vm->badPixelsPt[i][j]);
        }
    }
    /* ffm */
    printf("ffm :\n");
    for (i = 0; i < p2vm->nbChannels; i ++)
    {
        for (j = 0; j < p2vm->nx; j ++)
        {
            printf("flatField[%d][%d] = %f\n", i, j, p2vm->flatFieldPt[i][j]);
        }
    }
    /* photometry */
    printf("photometry :\n");
    for (i = 0; i < p2vm->nbChannels; i++)
    {
        for (j = 0; j < 3; j ++)
        {
            for (k = 0; k < 2*nbBases+1; k ++)
            {
                printf("photometry[%d][%d][%d] = %f\n", i, j, k, 
                       p2vm->photometryPt[k][j][i]);
            }
        }
    }

    /* phase */
    printf("Phase :\n");
    for (i = 0; i < p2vm->nbChannels; i ++)
    {
        for (j = 0; j < nbBases; j ++)
        {
            printf("phase[%d][%d] = %f\n", i, j, p2vm->phasePt[j][i]);
        }
    }
#endif
}
/*___oOo__*/
