/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Markus Gaug  02/2004 <mailto:markus@ifae.es>
!
!   Copyright: MAGIC Software Development, 2000-2004
!
!
\* ======================================================================== */
//////////////////////////////////////////////////////////////////////////////
//
//   MCalibrationChargeCalc
//
//   Task to calculate the calibration conversion factors and quantum efficiencies 
//   from the fit results to the summed FADC slice distributions delivered by 
//   MCalibrationChargeCam, MCalibrationChargePix, MCalibrationChargeBlindPix and 
//   MCalibrationChargePINDiode, calculated and filled by MHCalibrationChargeCam, 
//   MHCalibrationChargePix, MHCalibrationChargeBlindPix and MHCalibrationChargePINDiode.
//
//   PreProcess(): Initialize pointers to MCalibrationChargeCam, MCalibrationChargeBlindPix
//                 MCalibrationChargePINDiode and  MCalibrationQECam
//
//                 Initialize pulser light wavelength
//               
//   ReInit():     MCalibrationCam::InitSize(NumPixels) is called from MGeomApply (which allocates
//                 memory in a TClonesArray of type MCalibrationChargePix)
//                 Initializes pointer to MBadPixelsCam
//
//   Process():    Nothing to be done, histograms getting filled by MHCalibrationChargeCam
//
//   PostProcess(): - FinalizePedestals()
//                  - FinalizeCharges()
//                  - FinalizeFFactorMethod()
//                  - FinalizeBadPixels()
//                  - FinalizeBlindCam()
//                  - FinalizePINDiode()
//                  - FinalizeFFactorQECam()
//                  - FinalizeBlindPixelQECam()
//                  - FinalizePINDiodeQECam()
//
//  Input Containers:
//   MCalibrationChargeCam
//   MCalibrationChargeBlindPix
//   MCalibrationChargePINDiode
//   MCalibrationQECam
//   MPedestalCam
//   MBadPixelsCam
//   MGeomCam
//   MTime
//
//  Output Containers:
//   MCalibrationChargeCam
//   MCalibrationChargeBlindPix
//   MCalibrationChargePINDiode
//   MCalibrationQECam
//   MBadPixelsCam
//
//
//  Preliminary description of the calibration in photons (email from 12/02/04)
//
//  Why calibrating in photons:
//  ===========================
//  
//  At the Barcelona meeting in 2002, we decided to calibrate the camera in
//  photons. This for the following reasons:
//  
//  * The physical quantity arriving at the camera are photons. This is
//    the direct physical information from the air shower. The photons
//    have a flux and a spectrum.
//  
//  * The photon fluxes depend mostly on the shower energy (with
//    corrections deriving from the observation conditions), while the photon
//    spectra depend mostly on the observation conditions: zenith angle,
//    quality of the air, also the impact parameter of the shower.
//  
//  * The photomultiplier, in turn, has different response properties
//    (quantum efficiencies) for photons of different colour. (Moreover,
//    different pixels have slightly different quantum efficiencies).
//    The resulting number of photo-electrons is then amplified (linearly)
//    with respect to the photo-electron flux.
//  
//  * In the ideal case, one would like to disentagle the effects
//    of the observation conditions from the primary particle energy (which
//    one likes to measure). To do so, one needs:
//  
//    1) A reliable calibration relating the FADC counts to the photo-electron
//       flux -> This is accomplished with the F-Factor method.
//  
//    2) A reliable calibration of the wavelength-dependent quantum efficiency
//       -> This is accomplished with the combination of the three methods,
//          together with QE-measurements performed by David in order to do
//          the interpolation.
//  
//    3) A reliable calibration of the observation conditions. This means:
//       - Tracing the atmospheric conditions   -> LIDAR
//       - Tracing the observation zenith angle -> Drive System
//
//    4) Some knowlegde about the impact parameter:
//       - This is the only part which cannot be accomplished well with a
//         single telescope. We would thus need to convolute the spectrum
//         over the distribution of impact parameters.
//  
//  
//  How an ideal calibration would look like:
//  =========================================
//  
//  We know from the combined PIN-Diode and Blind-Pixel Method the response of
//  each pixel to well-measured light fluxes in three representative
//  wavelengths (green, blue, UV). We also know the response to these light
//  fluxes in photo-electrons. Thus, we can derive:
//  
//  - conversion factors to photo-electrons
//  - conversion factors to photons in three wavelengths.
//  
//  Together with David's measurements and some MC-simulation, we should be
//  able to derive tables for typical Cherenkov-photon spectra - convoluted
//  with the impact parameters and depending on the athmospheric conditions
//  and the zenith angle (the "outer parameters").
//  
//  From these tables we can create "calibration tables" containing some
//  effective quantum efficiency depending on these outer parameters and which
//  are different for each pixel.
//  
//  In an ideal MCalibrate, one would thus have to convert first the FADC
//  slices to Photo-electrons and then, depending on the outer parameters,
//  look up the effective quantum efficiency and get the mean number of
//  photons which is then used for the further analysis.
//  
//  How the (first) MAGIC calibration should look like:
//  ===================================================
//  
//  For the moment, we have only one reliable calibration method, although
//  with very large systematic errors. This is the F-Factor method. Knowing
//  that the light is uniform over the whole camera (which I would not at all
//  guarantee in the case of the CT1 pulser), one could in principle already
//  perform a relative calibration of the quantum efficiencies in the UV.
//  However, the spread in QE at UV is about 10-15% (according to the plot
//  that Abelardo sent around last time. The spread in photo-electrons is 15%
//  for the inner pixels, but much larger (40%) for the outer ones.
//  
//  I'm not sure if we can already say that we have measured the relative
//  difference in quantum efficiency for the inner pixels and produce a first
//  QE-table for each pixel. To so, I would rather check in other wavelengths
//  (which we can do in about one-two weeks when the optical transmission of
//  the calibration trigger is installed).
//  
//  Thus, for the moment being, I would join Thomas proposal to calibrate in
//  photo-electrons and apply one stupid average quantum efficiency for all
//  pixels. This keeping in mind that we will have much preciser information
//  in about one to two weeks.
//  
//  
//  What MCalibrate should calculate and what should be stored:
//  ===========================================================
//  
//  It is clear that in the end, MCerPhotEvt will store photons.
//  MCalibrationCam stores the conversionfactors to photo-electrons and also
//  some tables of how to apply the conversion to photons, given the outer
//  parameters. This is not yet implemented and not even discussed.
//  
//  To start, I would suggest that we define the "average quantum efficiency"
//  (maybe something like 25+-3%) and apply them equally to all
//  photo-electrons. Later, this average factor can be easily replaced by a
//  pixel-dependent factor and later by a (pixel-dependent) table.
//  
//  
//  ClassVersion 2:
//   - Float_t fPheErrLimit;
//   + Float_t fPheErrLowerLimit; // Lower limit acceptance nr. phe's w.r.t. area idx mean (in sigmas)
//   + Float_t fPheErrUpperLimit; // Upper limit acceptance nr. phe's w.r.t. area idx mean (in sigmas)
//
//  ClassVersion 3:
//   + Bool_t  fUseExtractorRes;  // Include extractor resolution in F-Factor method
//
//////////////////////////////////////////////////////////////////////////////
#include "MCalibrationChargeCalc.h"

#include <TSystem.h>
#include <TH1.h>
#include <TF1.h>

#include "MLog.h"
#include "MLogManip.h"

#include "MParList.h"

#include "MStatusDisplay.h"

#include "MCalibrationPattern.h"

#include "MGeomCam.h"
#include "MGeomPix.h"
#include "MHCamera.h"

#include "MPedestalCam.h"
#include "MPedestalPix.h"

#include "MCalibrationIntensityChargeCam.h"
#include "MCalibrationIntensityQECam.h"
#include "MCalibrationIntensityBlindCam.h"

#include "MHCalibrationChargeCam.h"
#include "MHCalibrationChargeBlindCam.h"

#include "MCalibrationChargeCam.h"
#include "MCalibrationChargePix.h"
#include "MCalibrationChargePINDiode.h"
#include "MCalibrationBlindPix.h"
#include "MCalibrationBlindCam.h"

#include "MExtractedSignalCam.h"
#include "MExtractedSignalPix.h"
#include "MExtractedSignalBlindPixel.h"
#include "MExtractedSignalPINDiode.h"

#include "MBadPixelsIntensityCam.h"
#include "MBadPixelsCam.h"

#include "MCalibrationQECam.h"
#include "MCalibrationQEPix.h"

ClassImp(MCalibrationChargeCalc);

using namespace std;

const Float_t MCalibrationChargeCalc::fgChargeLimit            = 4.5;
const Float_t MCalibrationChargeCalc::fgChargeErrLimit         = 0.;
const Float_t MCalibrationChargeCalc::fgChargeRelErrLimit      = 1.;
const Float_t MCalibrationChargeCalc::fgLambdaErrLimit         = 0.2;
const Float_t MCalibrationChargeCalc::fgLambdaCheckLimit       = 0.5;
const Float_t MCalibrationChargeCalc::fgPheErrLowerLimit       = 6.0;
const Float_t MCalibrationChargeCalc::fgPheErrUpperLimit       = 5.5;
const Float_t MCalibrationChargeCalc::fgFFactorErrLimit        = 4.5;
const Float_t MCalibrationChargeCalc::fgArrTimeRmsLimit        = 3.5;
const TString MCalibrationChargeCalc::fgNamePedestalCam = "MPedestalCam";


// --------------------------------------------------------------------------
//
// Default constructor. 
//
// Sets the pointer to fQECam and fGeom to NULL
// 
// Calls AddToBranchList for: 
// - MRawEvtData.fHiGainPixId   
// - MRawEvtData.fLoGainPixId   
// - MRawEvtData.fHiGainFadcSamples
// - MRawEvtData.fLoGainFadcSamples
//
// Initializes:
// - fArrTimeRmsLimit   to fgArrTimeRmsLimit
// - fChargeLimit       to fgChargeLimit 
// - fChargeErrLimit    to fgChargeErrLimit   
// - fChargeRelErrLimit to fgChargeRelErrLimit
// - fFFactorErrLimit   to fgFFactorErrLimit
// - fLambdaCheckLimit  to fgLambdaCheckLimit 
// - fLambdaErrLimit    to fgLambdaErrLimit 
// - fNamePedestalCam   to fgNamePedestalCam
// - fPheErrLowerLimit  to fgPheErrLowerLimit
// - fPheErrUpperLimit  to fgPheErrUpperLimit
// - fPulserColor       to MCalibrationCam::kCT1
// - fOutputPath        to "."
// - fOutputFile        to "ChargeCalibStat.txt"
// - flag debug         to kFALSE
//
// Sets all checks
//
// Calls:
// - Clear()
//
MCalibrationChargeCalc::MCalibrationChargeCalc(const char *name, const char *title)
    : fUseExtractorRes(kFALSE),
    fGeom(NULL), fSignal(NULL), fCalibPattern(NULL), fExtractor(NULL)
{
        
  fName  = name  ? name  : "MCalibrationChargeCalc";
  fTitle = title ? title : "Task to calculate the calibration constants and MCalibrationCam ";
  
  AddToBranchList("MRawEvtData.fHiGainPixId");
  AddToBranchList("MRawEvtData.fLoGainPixId");
  AddToBranchList("MRawEvtData.fHiGainFadcSamples");
  AddToBranchList("MRawEvtData.fLoGainFadcSamples");
  
  SetArrTimeRmsLimit   ();
  SetChargeLimit       ();
  SetChargeErrLimit    ();  
  SetChargeRelErrLimit ();
  SetDebug       ( kFALSE );
  SetFFactorErrLimit   ();
  SetLambdaCheckLimit  ();
  SetLambdaErrLimit    ();
  SetNamePedestalCam   ();
  SetOutputPath        ();
  SetOutputFile        ();
  SetPheErrLowerLimit  ();
  SetPheErrUpperLimit  ();

  SetCheckArrivalTimes     ();
  SetCheckDeadPixels       ();
  SetCheckDeviatingBehavior();
  SetCheckExtractionWindow ();
  SetCheckHistOverflow     ();
  SetCheckOscillations     ();

  Clear();
  
}

// --------------------------------------------------------------------------
//
// Sets:
// - all variables to 0., 
// - all flags to kFALSE
// - all pointers to NULL
// - the pulser colour to kNONE
// - fBlindPixelFlags to 0
// - fPINDiodeFlags   to 0
//
void MCalibrationChargeCalc::Clear(const Option_t *o)
{
  
    fNumHiGainSamples          = 0.;
    fNumLoGainSamples          = 0.;
    fSqrtHiGainSamples         = 0.;
    fSqrtLoGainSamples         = 0.;
    fNumInnerFFactorMethodUsed = 0;

    fNumProcessed              = 0;

    fIntensBad   = NULL;
    fBadPixels   = NULL;
    fIntensCam   = NULL;
    fCam         = NULL;
    fHCam        = NULL;
    fIntensQE    = NULL;
    fQECam       = NULL;
    fIntensBlind = NULL;
    fBlindCam    = NULL;
    fHBlindCam   = NULL;
    fPINDiode    = NULL;
    fPedestals   = NULL;

    SetPulserColor ( MCalibrationCam::kNONE );

    fStrength    = 0.;
    fBlindPixelFlags.Set(0);
    fPINDiodeFlags  .Set(0);
    fResultFlags    .Set(0);
}


// -----------------------------------------------------------------------------------
//
// The following container are searched for and execution aborted if not in MParList:
//  - MPedestalCam
//  - MCalibrationPattern
//  - MExtractedSignalCam
//
Int_t MCalibrationChargeCalc::PreProcess(MParList *pList)
{
  
  /*
  if (IsInterlaced())
    {
      fTrigPattern = (MTriggerPattern*)pList->FindObject("MTriggerPattern");
      if (!fTrigPattern)
        {
          *fLog << err << "MTriggerPattern not found... abort." << endl;
          return kFALSE;
        }
    }
  */

  fCalibPattern = (MCalibrationPattern*)pList->FindObject("MCalibrationPattern");
  if (!fCalibPattern)
    {
      *fLog << err << "MCalibrationPattern not found... abort." << endl;
      return kFALSE;
    }

  // 
  // Containers that have to be there.
  //
  fSignal = (MExtractedSignalCam*)pList->FindObject("MExtractedSignalCam");
  if (!fSignal)
    {
      *fLog << err << "MExtractedSignalCam not found... aborting" << endl;
      return kFALSE;
    }

  if (fPedestals)
    return kTRUE;

  fPedestals = (MPedestalCam*)pList->FindObject(AddSerialNumber(fNamePedestalCam), "MPedestalCam");
  if (!fPedestals)
    {
      *fLog << err << fNamePedestalCam << " [MPedestalCam] not found... aborting" << endl;
      return kFALSE;
    }

  fPulserColor = MCalibrationCam::kNONE;

  return kTRUE;
}


// --------------------------------------------------------------------------
//
// Search for the following input containers and abort if not existing:
//  - MGeomCam
// -  MCalibrationIntensityChargeCam or MCalibrationChargeCam
//  - MCalibrationIntensityQECam or MCalibrationQECam 
//  - MBadPixelsIntensityCam or MBadPixelsCam
// 
// Search for the following input containers and give a warning if not existing:
//  - MCalibrationBlindPix
//  - MCalibrationChargePINDiode
//
// It retrieves the following variables from MCalibrationChargeCam:
//
//  - fNumHiGainSamples 
//  - fNumLoGainSamples 
//
// It defines the PixId of every pixel in:
//
// - MCalibrationIntensityChargeCam 
// - MCalibrationChargeCam 
// - MCalibrationQECam
//
// It sets all pixels in excluded which have the flag fBadBixelsPix::IsBad() set in:
// 
// - MCalibrationChargePix
// - MCalibrationQEPix
//
// Sets the pulser colour and tests if it has not changed w.r.t. fPulserColor in:
//
// - MCalibrationChargeCam
// - MCalibrationBlindPix (if existing)
// - MCalibrationChargePINDiode (if existing)
//
Bool_t MCalibrationChargeCalc::ReInit(MParList *pList )
{

  fGeom = (MGeomCam*)pList->FindObject("MGeomCam");
  if (!fGeom)
    {
      *fLog << err << "No MGeomCam found... aborting." << endl;
      return kFALSE;
    }
  
  fIntensCam = (MCalibrationIntensityChargeCam*)pList->FindObject(AddSerialNumber("MCalibrationIntensityChargeCam"));
  if (fIntensCam)
    *fLog << inf << "Found MCalibrationIntensityChargeCam... " << flush;
  else
    {
      fCam = (MCalibrationChargeCam*)pList->FindObject(AddSerialNumber("MCalibrationChargeCam"));
      if (!fCam)
        {
          *fLog << err << "Cannot find MCalibrationChargeCam ... abort." << endl;
          *fLog << "Maybe you forget to call an MFillH for the MHCalibrationChargeCam before..." << endl;
          return kFALSE;
        }
    }
  
  fHCam = (MHCalibrationChargeCam*)pList->FindObject(AddSerialNumber("MHCalibrationChargeCam"));
  if (!fHCam)
    {
      *fLog << err << "Cannot find MHCalibrationChargeCam ... abort." << endl;
      *fLog << "Maybe you forget to call an MFillH for the MHCalibrationChargeCam before..." << endl;
      return kFALSE;
    }

  fIntensQE = (MCalibrationIntensityQECam*)pList->FindObject(AddSerialNumber("MCalibrationIntensityQECam"));
  if (fIntensQE)
    *fLog << inf << "Found MCalibrationIntensityQECam... " << flush;
  else
    {
      fQECam = (MCalibrationQECam*)pList->FindObject(AddSerialNumber("MCalibrationQECam"));
      if (!fQECam)
        {
          *fLog << err << "Cannot find MCalibrationQECam ... abort." << endl;
          *fLog << "Maybe you forget to call an MFillH for the MHCalibrationQECam before..." << endl;
          return kFALSE;
        }
    }
  
  fIntensBlind = (MCalibrationIntensityBlindCam*)pList->FindObject(AddSerialNumber("MCalibrationIntensityBlindCam"));
  if (fIntensBlind)
    *fLog << inf << "Found MCalibrationIntensityBlindCam... " << flush;
  else
    {
      fBlindCam = (MCalibrationBlindCam*)pList->FindObject(AddSerialNumber("MCalibrationBlindCam"));
      if (!fBlindCam)
        *fLog << inf << "No MCalibrationBlindCam found... no Blind Pixel method!" << endl;
    }
  
  fHBlindCam = (MHCalibrationChargeBlindCam*)pList->FindObject(AddSerialNumber("MHCalibrationChargeBlindCam"));  
  if (!fHBlindCam)
    *fLog << inf << "No MHCalibrationChargeBlindCam found... no Blind Pixel method!" << endl;

  fIntensBad = (MBadPixelsIntensityCam*)pList->FindObject(AddSerialNumber("MBadPixelsIntensityCam"));
  if (fIntensBad)
    *fLog << inf << "Found MBadPixelsIntensityCam... " << flush;
  else
    {
      fBadPixels = (MBadPixelsCam*)pList->FindObject(AddSerialNumber("MBadPixelsCam"));
      if (!fBadPixels)
        {
          *fLog << err << "Cannot find MBadPixelsCam ... abort." << endl;
          return kFALSE;
        }
    }
  
  // 
  // Optional Containers 
  //
  fPINDiode = (MCalibrationChargePINDiode*)pList->FindObject("MCalibrationChargePINDiode");
  if (!fPINDiode)
    *fLog << inf << "No MCalibrationChargePINDiode found... no PIN Diode method!" << endl;
  
  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;

  UInt_t npixels     = fGeom->GetNumPixels();
  
  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix = (MCalibrationChargePix&)(*chargecam)[i];
      MCalibrationQEPix     &pqe = (MCalibrationQEPix&)    (*qecam)    [i];
      MBadPixelsPix         &bad = (*badcam)   [i];
      
      if (bad.IsBad())
        {
          pix.SetExcluded();
          pqe.SetExcluded();
          continue;
        }
      
      if (IsDebug())
        pix.SetDebug();
    }

  return kTRUE;
}

// ----------------------------------------------------------------------------------
//  
// Set the correct colour to the charge containers
// 
Int_t MCalibrationChargeCalc::Process()
{

  const MCalibrationCam::PulserColor_t col = fCalibPattern->GetPulserColor();
  const Float_t strength = fCalibPattern->GetPulserStrength();
  const Float_t strdiff  = TMath::Abs(strength-fStrength);

  if (col == fPulserColor && strdiff < 0.05 )
    {
      fNumProcessed++;
      return kTRUE;
    }
  
  if (col == MCalibrationCam::kNONE)
    return kTRUE;
  

  //
  // Now retrieve the colour and check if not various colours have been used
  //
  if (!fIntensCam)
    {
      if (fPulserColor != MCalibrationCam::kNONE)
        {
          *fLog << warn << "Multiple colours used simultaneously!" ;
          fHCam->Finalize();
          if (fHBlindCam)
            fHBlindCam->Finalize();

          Finalize();

          fHCam->ResetHists();
          if (fHBlindCam)
            fHBlindCam->ResetHists();

          *fLog << inf << "Starting next calibration... " << flush;

          fHCam->SetColor(col);
          if (fHBlindCam)
            fHBlindCam->SetColor(col);

          fCam->SetPulserColor(col);
          if (fBlindCam)
            fBlindCam->SetPulserColor(col);
        }
    }
  
  fPulserColor = col;
  fStrength    = strength;

  *fLog << inf << "Found new colour ... " << flush;
  
  switch (col)
    {
    case MCalibrationCam::kGREEN: *fLog << "Green";  break;
    case MCalibrationCam::kBLUE:  *fLog << "Blue";   break;
    case MCalibrationCam::kUV:    *fLog << "UV";     break;
    case MCalibrationCam::kCT1:   *fLog << "CT1";    break;
    default: break;
    }

  *fLog << inf << " with strength: " << strength << endl;
  
  fHCam->SetColor(col);
  if (fHBlindCam)
    fHBlindCam->SetColor(col);

  MCalibrationBlindCam  *blindcam  = fIntensBlind   
    ? (MCalibrationBlindCam*)fIntensBlind->GetCam() : fBlindCam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;

  chargecam->SetPulserColor(col);

  if (blindcam)
    blindcam->SetPulserColor(col);
  if (fPINDiode)
    fPINDiode->SetColor(col);

  fNumProcessed = 0;
  
  return kTRUE;
}

// -----------------------------------------------------------------------
//
// Return if number of executions is null.
//
Int_t MCalibrationChargeCalc::PostProcess()
{

  if (GetNumExecutions() < 1)
    return kTRUE;

  if (fPulserColor == MCalibrationCam::kNONE)
  {
      *fLog << warn << "WARNING - No Pulse colour has been set or used at all..." << endl;
      return kTRUE;
  }

  if (fNumProcessed == 0)
    return kTRUE;

  *fLog << endl;

  return Finalize();
}

// -----------------------------------------------------------------------
//
// Return kTRUE if fPulserColor is kNONE
//
// First loop over pixels, average areas and sectors, call:
//  - FinalizePedestals() 
//  - FinalizeCharges()
// for every entry. Count number of valid pixels in loop and return kFALSE
// if there are none (the "Michele check").
//
// Call FinalizeBadPixels()
//
// Call FinalizeFFactorMethod() (second and third loop over pixels and areas)
//
// Call FinalizeBlindCam() 
// Call FinalizePINDiode()
//
// Call  FinalizeFFactorQECam() (fourth loop over pixels and areas)
// Call  FinalizeBlindPixelQECam() (fifth loop over pixels and areas)
// Call  FinalizePINDiodeQECam() (sixth loop over pixels and areas)
//
// Call  FinalizeUnsuitablePixels()
//  
// Call MParContainer::SetReadyToSave() for fIntensCam, fCam, fQECam, fBadPixels and 
//                                          fBlindCam and fPINDiode if they exist
//
// Print out some statistics
//
Int_t MCalibrationChargeCalc::Finalize()
{
  fNumHiGainSamples  =  fSignal->GetNumUsedHiGainFADCSlices();
  fNumLoGainSamples  =  fSignal->GetNumUsedLoGainFADCSlices();

  fSqrtHiGainSamples =  TMath::Sqrt(fNumHiGainSamples);
  fSqrtLoGainSamples =  TMath::Sqrt(fNumLoGainSamples);
  
  if (fPINDiode)
    if (!fPINDiode->IsValid())
      {
        *fLog << warn << GetDescriptor() 
              << ": MCalibrationChargePINDiode is declared not valid... no PIN Diode method! " << endl;
        fPINDiode = NULL;
      }

  MCalibrationBlindCam  *blindcam  = fIntensBlind   
    ? (MCalibrationBlindCam*)fIntensBlind->GetCam() : fBlindCam;
  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;

  //
  // First loop over pixels, call FinalizePedestals and FinalizeCharges
  //
  Int_t   nvalid      = 0;

  for (Int_t pixid=0; pixid<fPedestals->GetSize(); pixid++)
    {

      MCalibrationChargePix &pix = (MCalibrationChargePix&)(*chargecam)[pixid];
      //
      // Check if the pixel has been excluded from the fits
      //
      if (pix.IsExcluded())
        continue;

      MPedestalPix  &ped = (*fPedestals)[pixid];
      MBadPixelsPix &bad = (*badcam)    [pixid];

      const Int_t aidx    = (*fGeom)[pixid].GetAidx();

      FinalizePedestals(ped,pix,aidx);

      if (FinalizeCharges(pix,bad,"pixel  "))
        nvalid++;

      FinalizeArrivalTimes(pix,bad,"pixel  ");
    }

  *fLog << endl;  

  //
  // The Michele check ...
  //
  if (nvalid == 0)
  {
    if (!fIntensCam)
      {
        *fLog << warn << GetDescriptor() << ": All pixels have non-valid calibration. " 
              << "Did you forget to fill the histograms "
              << "(filling MHCalibrationChargeCam from MExtractedSignalCam using MFillH) ? " << endl;
        *fLog << warn << GetDescriptor() << ": Or, maybe, you have used a pedestal run " 
              << "instead of a calibration run " << endl;
        return kFALSE;
      }
  }

  for (UInt_t aidx=0; aidx<fGeom->GetNumAreas(); aidx++)
    {

      const MPedestalPix     &ped = fPedestals->GetAverageArea(aidx);
      MCalibrationChargePix &pix = (MCalibrationChargePix&)chargecam->GetAverageArea(aidx);

      FinalizePedestals(ped,pix,aidx);
      FinalizeCharges(pix, chargecam->GetAverageBadArea(aidx),"area id");
      FinalizeArrivalTimes(pix, chargecam->GetAverageBadArea(aidx), "area id");
    }
  
  *fLog << endl;

  for (UInt_t sector=0; sector<fGeom->GetNumSectors(); sector++)
    {

      const MPedestalPix     &ped = fPedestals->GetAverageSector(sector);

      MCalibrationChargePix &pix = (MCalibrationChargePix&)chargecam->GetAverageSector(sector);
      FinalizePedestals(ped,pix, 0);
    }
  
  *fLog << endl;

  //
  // Finalize Bad Pixels
  // 
  FinalizeBadPixels();

  // 
  // Finalize F-Factor method
  //
  if (FinalizeFFactorMethod())
    chargecam->SetFFactorMethodValid(kTRUE);
  else
    {
      *fLog << warn << "Could not calculate the photons flux from the F-Factor method " << endl;
      chargecam->SetFFactorMethodValid(kFALSE); 
      if (!fIntensCam)
	return kFALSE;
    }

  *fLog << endl;

  //
  // Finalize Blind Pixel
  //
  qecam->SetBlindPixelMethodValid(FinalizeBlindCam());

  // 
  // Finalize PIN Diode
  //
  qecam->SetBlindPixelMethodValid(FinalizePINDiode());

  //
  // Finalize QE Cam
  //
  FinalizeFFactorQECam();
  FinalizeBlindPixelQECam();
  FinalizePINDiodeQECam();
  FinalizeCombinedQECam();

  //
  // Re-direct the output to an ascii-file from now on:
  //
  MLog *oldlog = fLog;
  MLog asciilog;
  if (!fOutputFile.IsNull())
  {
      asciilog.SetOutputFile(GetOutputFile(),kTRUE);
      SetLogStream(&asciilog);
  }

  //
  // Finalize calibration statistics
  //
  FinalizeUnsuitablePixels();

  chargecam->SetReadyToSave();
  qecam    ->SetReadyToSave();
  badcam   ->SetReadyToSave();
  
  if (blindcam)
    blindcam->SetReadyToSave();
  if (fPINDiode)
    fPINDiode->SetReadyToSave();
  
  *fLog << inf << endl;
  *fLog << GetDescriptor() << ": Fatal errors statistics:" << endl;  

  PrintUncalibrated(MBadPixelsPix::kChargeIsPedestal,    
                    Form("%s%2.1f%s","Signal less than ",fChargeLimit," Pedestal RMS:                "));
  PrintUncalibrated(MBadPixelsPix::kChargeRelErrNotValid,
                    Form("%s%2.1f%s","Signal Error bigger than ",fChargeRelErrLimit," times Mean Signal:   "));
  PrintUncalibrated(MBadPixelsPix::kLoGainSaturation,    
                    "Low Gain Saturation:                              ");
  PrintUncalibrated(MBadPixelsPix::kMeanTimeInFirstBin, 
                    Form("%s%2.1f%s","Mean Abs. Arr. Time in First ",1.," Bin(s):          "));
  PrintUncalibrated(MBadPixelsPix::kMeanTimeInLast2Bins, 
                    Form("%s%2.1f%s","Mean Abs. Arr. Time in Last ",2.," Bin(s):           "));
  PrintUncalibrated(MBadPixelsPix::kHiGainOverFlow,    
                    "Pixels with High Gain Overflow:                   ");
  PrintUncalibrated(MBadPixelsPix::kLoGainOverFlow,    
                    "Pixels with Low Gain Overflow :                   ");
  PrintUncalibrated(MBadPixelsPix::kFluctuatingArrivalTimes,
                    "Fluctuating Pulse Arrival Times:                  ");
  PrintUncalibrated(MBadPixelsPix::kDeadPedestalRms,
                    "Presumably dead from Pedestal Rms:                ");
  PrintUncalibrated(MBadPixelsPix::kDeviatingNumPhes,
                    "Deviating number of phes:                         ");
  PrintUncalibrated(MBadPixelsPix::kPreviouslyExcluded,
                    "Previously excluded:                              ");

  *fLog << inf << endl;
  *fLog << GetDescriptor() << ": Unreliable errors statistics:" << endl;  

  PrintUncalibrated(MBadPixelsPix::kChargeSigmaNotValid, 
                    "Signal Sigma smaller than Pedestal RMS: ");
  PrintUncalibrated(MBadPixelsPix::kHiGainOscillating,   
                    "Changing Hi Gain signal over time:      ");
  PrintUncalibrated(MBadPixelsPix::kLoGainOscillating,   
                    "Changing Lo Gain signal over time:      ");
  PrintUncalibrated(MBadPixelsPix::kHiGainNotFitted,     
                    "Unsuccesful Gauss fit to the Hi Gain:   ");
  PrintUncalibrated(MBadPixelsPix::kLoGainNotFitted,     
                    "Unsuccesful Gauss fit to the Lo Gain:   ");
  PrintUncalibrated(MBadPixelsPix::kDeviatingFFactor,    
                    "Deviating F-Factor:                     ");

  if (!fOutputFile.IsNull())
      SetLogStream(oldlog);

  return kTRUE;
}

// ----------------------------------------------------------------------------------
//  
// Retrieves pedestal and pedestal RMS from MPedestalPix 
// Retrieves total entries from MPedestalCam
// Sets pedestal*fNumHiGainSamples and pedestal*fNumLoGainSamples in MCalibrationChargePix
// Sets pedRMS *fSqrtHiGainSamples and pedRMS *fSqrtLoGainSamples in MCalibrationChargePix
//
// If the flag MCalibrationPix::IsHiGainSaturation() is set, call also: 
// - MCalibrationChargePix::CalcLoGainPedestal()
//
void MCalibrationChargeCalc::FinalizePedestals(const MPedestalPix &ped, MCalibrationChargePix &cal, const Int_t aidx)
{
  
  //
  // get the pedestals
  //
  const Float_t pedes  = ped.GetPedestal();
  const Float_t prms   = ped.GetPedestalRms();
  const Int_t   num    = fPedestals->GetTotalEntries();
  
  //
  // RMS error set by PedCalcFromLoGain, 0 in case MPedCalcPedRun was used.
  //
  const Float_t prmserr = num>0 ? prms/TMath::Sqrt(2.*num) : ped.GetPedestalRmsError();

  //
  // set them in the calibration camera
  //
  if (cal.IsHiGainSaturation())
    {
      cal.SetPedestal(pedes   * fNumLoGainSamples,
                      prms    * fSqrtLoGainSamples,
                      prmserr * fSqrtLoGainSamples);
      cal.CalcLoGainPedestal(fNumLoGainSamples);
    }
  else
    {

      cal.SetPedestal(pedes   * fNumHiGainSamples,
                      prms    * fSqrtHiGainSamples,
                      prmserr * fSqrtHiGainSamples);
    }
  
}

// ----------------------------------------------------------------------------------------------------
//
// Check fit results validity. Bad Pixels flags are set if:
//
// 1) Pixel has a mean smaller than fChargeLimit*PedRMS    ( Flag: MBadPixelsPix::kChargeIsPedestal)
// 2) Pixel has a mean error smaller than fChargeErrLimit  ( Flag: MBadPixelsPix::kChargeErrNotValid)
// 3) Pixel has mean smaller than fChargeRelVarLimit times its mean error 
//                                                         ( Flag: MBadPixelsPix::kChargeRelErrNotValid)
// 4) Pixel has a sigma bigger than its Pedestal RMS       ( Flag: MBadPixelsPix::kChargeSigmaNotValid )
// 
// Further returns if flags: MBadPixelsPix::kUnsuitableRun is set
// 
// Calls MCalibrationChargePix::CalcReducedSigma() and sets flag:  MBadPixelsPix::kChargeIsPedestal 
//       and returns kFALSE if not succesful. 
// 
// Calls MCalibrationChargePix::CalcFFactor() and sets flag: MBadPixelsPix::kDeviatingNumPhes) 
//       and returns kFALSE if not succesful.
//
// Calls MCalibrationChargePix::CalcConvFFactor()and sets flag: MBadPixelsPix::kDeviatingNumPhes) 
//       and returns kFALSE if not succesful.
//
Bool_t MCalibrationChargeCalc::FinalizeCharges(MCalibrationChargePix &cal, MBadPixelsPix &bad, const char* what)
{

  if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
    return kFALSE;

  if (cal.GetMean() < fChargeLimit*cal.GetPedRms())
    {
      *fLog << warn
            << Form("Fitted Charge: %5.2f < %2.1f",cal.GetMean(),fChargeLimit)
            << Form(" * Pedestal RMS %5.2f in %s%3i",cal.GetPedRms(),what,cal.GetPixId()) << endl;
      bad.SetUncalibrated( MBadPixelsPix::kChargeIsPedestal);
    }
  
   if (cal.GetMean() < fChargeRelErrLimit*cal.GetMeanErr()) 
    {
      *fLog << warn
            << Form("Fitted Charge: %4.2f < %2.1f",cal.GetMean(),fChargeRelErrLimit)
            << Form(" * its error %4.2f in %s%3i",cal.GetMeanErr(),what,cal.GetPixId()) << endl;
      bad.SetUncalibrated( MBadPixelsPix::kChargeRelErrNotValid );
    }

  if (cal.GetSigma() < cal.GetPedRms())
    {
      *fLog << warn << "Sigma of Fitted Charge: "
            << Form("%6.2f <",cal.GetSigma()) << " Ped. RMS="
            << Form("%5.2f", cal.GetPedRms()) << " in " << what
	    << Form("%3i",cal.GetPixId()) << endl;
      bad.SetUncalibrated( MBadPixelsPix::kChargeSigmaNotValid );
      return kFALSE;
    }

  if (!cal.CalcReducedSigma())
    {
      *fLog << warn << "Could not calculate the reduced sigma in " << what
            << ":                        " << Form("%4i",cal.GetPixId())
            << endl;
      bad.SetUncalibrated( MBadPixelsPix::kChargeSigmaNotValid );
      return kFALSE;
    }

  if (!cal.CalcFFactor())
    {
      *fLog << warn << "Could not calculate the F-Factor in " << what
            << ":                             " << Form("%4i",cal.GetPixId())
            << endl;
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      bad.SetUnsuitable(MBadPixelsPix::kUnsuitableRun);
      return kFALSE;
    }

  if (cal.GetPheFFactorMethod() < 0.)
    {
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      bad.SetUnsuitable(MBadPixelsPix::kUnsuitableRun);
      cal.SetFFactorMethodValid(kFALSE);
      return kFALSE;
    }

  if (!cal.CalcConvFFactor())
    {
      *fLog << warn << "Could not calculate the Conv. FADC counts to Phes in ";
      *fLog << what << ":           " << Form("%4i", cal.GetPixId()) << endl;
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      return kFALSE;
    }

  if (!fUseExtractorRes)
    return kTRUE;

  if (!fExtractor)
    {
      *fLog << err << "Extractor resolution has been chosen, but not extractor is set. Cannot calibrate" << endl;
      return kFALSE;
    }

  const Float_t resinphes = cal.IsHiGainSaturation()
    ? cal.GetPheFFactorMethod()*fExtractor->GetResolutionPerPheLoGain()
    : cal.GetPheFFactorMethod()*fExtractor->GetResolutionPerPheHiGain();

  Float_t resinfadc = cal.IsHiGainSaturation()
    ? resinphes/cal.GetMeanConvFADC2Phe()/cal.GetConversionHiLo()
    : resinphes/cal.GetMeanConvFADC2Phe();

  if (resinfadc > 1.5*cal.GetPedRms() )
    {
      *fLog << warn << " Extractor Resolution: " << resinfadc << " bigger than Pedestal RMS " << cal.GetPedRms() << endl;
      resinfadc = cal.GetPedRms();
    }

  if (!cal.CalcReducedSigma(resinfadc))
    {
        *fLog << warn << "Could not calculate the reduced sigma in " << what;
        *fLog << ":                        " << Form("%4i",cal.GetPixId()) << endl;
        bad.SetUncalibrated( MBadPixelsPix::kChargeSigmaNotValid );
        return kFALSE;
    }

  if (!cal.CalcFFactor())
    {
        *fLog << warn << "Could not calculate the F-Factor in " << what;
        *fLog << ":                             " << Form("%4i",cal.GetPixId()) << endl;
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      bad.SetUnsuitable(MBadPixelsPix::kUnsuitableRun);
      return kFALSE;
    }

  if (cal.GetPheFFactorMethod() < 0.)
    {
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      bad.SetUnsuitable(MBadPixelsPix::kUnsuitableRun);
      cal.SetFFactorMethodValid(kFALSE);
      return kFALSE;
    }

  if (!cal.CalcConvFFactor())
    {
      *fLog << warn << "Could not calculate the Conv. FADC counts to Phes in "
            << what << ":           " << Form("%4i",cal.GetPixId())
            << endl;
      bad.SetUncalibrated(MBadPixelsPix::kDeviatingNumPhes);
      return kFALSE;
    }

  return kTRUE;
}

// -----------------------------------------------------------------------------------
//
// Test the arrival Times RMS of the pixel and set the bit
// - MBadPixelsPix::kFluctuatingArrivalTimes
//
void MCalibrationChargeCalc::FinalizeArrivalTimes(MCalibrationChargePix &cal, MBadPixelsPix &bad, const char* what)
{
    if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        return;

    if (cal.GetAbsTimeRms() > fArrTimeRmsLimit)
    {
        *fLog << warn;
        *fLog << "RMS of pulse arrival times: " << Form("%2.1f", cal.GetAbsTimeRms());
        *fLog << " FADC sl. < " << Form("%2.1f", fArrTimeRmsLimit);
        *fLog << "   in " << what << Form("%3i", cal.GetPixId()) << endl;
        bad.SetUncalibrated( MBadPixelsPix::kFluctuatingArrivalTimes);
    }
}

// -----------------------------------------------------------------------------------
//
// Sets pixel to MBadPixelsPix::kUnsuitableRun, if one of the following flags is set:
// - MBadPixelsPix::kChargeIsPedestal
// - MBadPixelsPix::kChargeRelErrNotValid 
// - MBadPixelsPix::kMeanTimeInFirstBin 
// - MBadPixelsPix::kMeanTimeInLast2Bins 
// - MBadPixelsPix::kDeviatingNumPhes
// - MBadPixelsPix::kHiGainOverFlow
// - MBadPixelsPix::kLoGainOverFlow
//
// - Call MCalibrationPix::SetExcluded() for the bad pixels
//
// Sets pixel to MBadPixelsPix::kUnreliableRun, if one of the following flags is set:
// - MBadPixelsPix::kChargeSigmaNotValid 
//
void MCalibrationChargeCalc::FinalizeBadPixels()
{
  
  MBadPixelsCam *badcam = fIntensBad ? (MBadPixelsCam*)fIntensBad->GetCam() : fBadPixels;

  for (Int_t i=0; i<badcam->GetSize(); i++)
    {
      
      MBadPixelsPix &bad = (*badcam)[i];

      if (IsCheckDeadPixels())
        {
          if (bad.IsUncalibrated( MBadPixelsPix::kChargeIsPedestal))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun   );
 
          if (bad.IsUncalibrated( MBadPixelsPix::kChargeErrNotValid ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );

          if (bad.IsUncalibrated( MBadPixelsPix::kChargeRelErrNotValid ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
        }
      
      if (IsCheckExtractionWindow())
        {
          if (bad.IsUncalibrated( MBadPixelsPix::kMeanTimeInFirstBin ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
          
          if (bad.IsUncalibrated( MBadPixelsPix::kMeanTimeInLast2Bins ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
        }
      
      if (IsCheckDeviatingBehavior())
        {
          if (bad.IsUncalibrated( MBadPixelsPix::kDeviatingNumPhes ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
        }
      
      if (IsCheckHistOverflow())
        {
          if (bad.IsUncalibrated( MBadPixelsPix::kHiGainOverFlow   ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
          
          if (bad.IsUncalibrated( MBadPixelsPix::kLoGainOverFlow   ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
        }
      
      if (IsCheckArrivalTimes())
        {
          if (bad.IsUncalibrated( MBadPixelsPix::kFluctuatingArrivalTimes ))
            bad.SetUnsuitable(   MBadPixelsPix::kUnsuitableRun    );
        }

      if (bad.IsUncalibrated( MBadPixelsPix::kChargeSigmaNotValid ))
        bad.SetUnsuitable(   MBadPixelsPix::kUnreliableRun    );
    }
}

// ------------------------------------------------------------------------
//
//
// First loop: Calculate a mean and mean RMS of photo-electrons per area index 
//             Include only pixels which are not MBadPixelsPix::kUnsuitableRun nor 
//             MBadPixelsPix::kChargeSigmaNotValid (see FinalizeBadPixels()) and set 
//             MCalibrationChargePix::SetFFactorMethodValid(kFALSE) in that case.
//              
// Second loop: Get mean number of photo-electrons and its RMS including 
//              only pixels with flag MCalibrationChargePix::IsFFactorMethodValid() 
//              and further exclude those deviating by more than fPheErrLimit mean 
//              sigmas from the mean (obtained in first loop). Set 
//              MBadPixelsPix::kDeviatingNumPhes if excluded.
//
//              For the suitable pixels with flag MBadPixelsPix::kChargeSigmaNotValid 
//              set the number of photo-electrons as the mean number of photo-electrons
//              calculated in that area index.
// 
//              Set weighted mean and variance of photo-electrons per area index in: 
//              average area pixels of MCalibrationChargeCam (obtained from: 
//              MCalibrationChargeCam::GetAverageArea() )
// 
//              Set weighted mean  and variance of photo-electrons per sector in:
//              average sector pixels of MCalibrationChargeCam (obtained from: 
//              MCalibrationChargeCam::GetAverageSector() )
// 
//
// Third loop: Set mean number of photo-electrons and its RMS in the pixels
//             only excluded as: MBadPixelsPix::kChargeSigmaNotValid 
// 
Bool_t MCalibrationChargeCalc::FinalizeFFactorMethod()
{
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;

  const Int_t npixels  = fGeom->GetNumPixels();
  const Int_t nareas   = fGeom->GetNumAreas();
  const Int_t nsectors = fGeom->GetNumSectors();

  TArrayF lowlim        (nareas);
  TArrayF upplim        (nareas);
  TArrayD areavars      (nareas);
  TArrayD areaweights   (nareas);
  TArrayD sectorweights (nsectors);
  TArrayD areaphes      (nareas);
  TArrayD sectorphes    (nsectors);
  TArrayI numareavalid  (nareas);
  TArrayI numsectorvalid(nsectors);

  //
  // First loop: Get mean number of photo-electrons and the RMS
  //             The loop is only to recognize later pixels with very deviating numbers
  //
  MHCamera camphes(*fGeom,"Camphes","Phes in Camera");

  for (Int_t i=0; i<npixels; i++)
    {
      MCalibrationChargePix &pix = (MCalibrationChargePix&)(*chargecam)[i];
      MBadPixelsPix         &bad =                         (*badcam)[i];
      
      if (!pix.IsFFactorMethodValid())
        continue;

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        {
          pix.SetFFactorMethodValid(kFALSE);
          continue;
        }

      if (bad.IsUncalibrated(MBadPixelsPix::kChargeSigmaNotValid))
        continue;
      
      const Float_t nphe  = pix.GetPheFFactorMethod();
      const Int_t   aidx  = (*fGeom)[i].GetAidx();
      camphes.Fill(i,nphe);
      camphes.SetUsed(i);
      areaphes    [aidx] += nphe;
      areavars    [aidx] += nphe*nphe;
      numareavalid[aidx] ++;
    } 

  for (Int_t i=0; i<nareas; i++)
    {
      if (numareavalid[i] == 0)
        {
          *fLog << warn << GetDescriptor() << ": No pixels with valid number of photo-electrons found "
                << "in area index: " << i << endl;
          continue;
        }

      if (numareavalid[i] == 1)
        areavars[i] = 0.;
      else if (numareavalid[i] == 0)
        {
          areaphes[i]    = -1.;
          areaweights[i] = -1.;
        }
      else
        {
          areavars[i] = (areavars[i] - areaphes[i]*areaphes[i]/numareavalid[i]) / (numareavalid[i]-1);
          areaphes[i] = areaphes[i] / numareavalid[i];
        }
      
      if (areavars[i] < 0.)
        {
          *fLog << warn << GetDescriptor() << ": No pixels with valid variance of photo-electrons found "
                << "in area index: " << i << endl;
          continue;
        }
      
      lowlim[i] = areaphes[i] - fPheErrLowerLimit*TMath::Sqrt(areavars[i]);
      upplim[i] = areaphes[i] + fPheErrUpperLimit*TMath::Sqrt(areavars[i]);
 

      TH1D *hist = camphes.ProjectionS(TArrayI(),TArrayI(1,&i),"_py",100);
      hist->Fit("gaus","Q0");
      const Float_t mean  = hist->GetFunction("gaus")->GetParameter(1);
      const Float_t sigma = hist->GetFunction("gaus")->GetParameter(2);
      const Int_t   ndf   = hist->GetFunction("gaus")->GetNDF();

      if (IsDebug())
        hist->DrawClone();

      if (ndf < 5)
        {
          *fLog << warn << GetDescriptor() << ": Cannot use a Gauss fit to the number of photo-electrons " 
                << "in the camera with area index: " << i << endl;
          *fLog << warn << GetDescriptor() << ": Number of dof.: " << ndf << " is smaller than 5 " << endl;
          *fLog << warn << GetDescriptor() << ": Will use the simple mean and rms " << endl;
          delete hist;
          continue;
        }
      
      const Double_t prob = hist->GetFunction("gaus")->GetProb();

      if (prob < 0.001)
        {
          *fLog << warn << GetDescriptor() << ": Cannot use a Gauss fit to the number of photo-electrons " 
                << "in the camera with area index: " << i << endl;
          *fLog << warn << GetDescriptor() << ": Fit probability " << prob 
                << " is smaller than 0.001 " << endl;
          *fLog << warn << GetDescriptor() << ": Will use the simple mean and rms " << endl;
          delete hist;
          continue;
        }
      
      if (mean < 0.)
        {
          *fLog << inf << GetDescriptor() << ": Fitted mean number of photo-electrons " 
                << "with area idx " << i << ": " << mean << " is smaller than 0. " << endl;
          *fLog << warn << GetDescriptor() << ": Will use the simple mean and rms " << endl;          
          delete hist;
          continue;
        }
      
      *fLog << inf << GetDescriptor() << ": Mean number of phes with area idx " << i << ": "
            << Form("%7.2f+-%6.2f",mean,sigma) << endl;

      lowlim[i] = mean - fPheErrLowerLimit*sigma;
      upplim[i] = mean + fPheErrUpperLimit*sigma;

      delete hist;
    }

  *fLog << endl;

  numareavalid.Reset();
  areaphes    .Reset();
  areavars    .Reset();
  //
  // Second loop: Get mean number of photo-electrons and its RMS excluding 
  //              pixels deviating by more than fPheErrLimit sigma. 
  //              Set the conversion factor FADC counts to photo-electrons
  // 
  for (Int_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix = (MCalibrationChargePix&)(*chargecam)[i];

      if (!pix.IsFFactorMethodValid())
        continue;

      MBadPixelsPix         &bad =           (*badcam)[i];

      if (bad.IsUncalibrated(MBadPixelsPix::kChargeSigmaNotValid))
        continue;

      const Float_t nvar  = pix.GetPheFFactorMethodVar();
      if (nvar <= 0.)
        {
          pix.SetFFactorMethodValid(kFALSE);
          continue;
        }
      
      const Int_t   aidx   = (*fGeom)[i].GetAidx();
      const Int_t   sector = (*fGeom)[i].GetSector();
      const Float_t area   = (*fGeom)[i].GetA();
      const Float_t nphe   = pix.GetPheFFactorMethod();

      if ( nphe < lowlim[aidx] || nphe > upplim[aidx] )
        {
          *fLog << warn << "Number of phes: "
                << Form("%7.2f out of +%3.1f-%3.1f sigma limit: ",nphe,fPheErrUpperLimit,fPheErrLowerLimit)
                << Form("[%7.2f,%7.2f] pixel%4i",lowlim[aidx],upplim[aidx],i) << endl;
          bad.SetUncalibrated( MBadPixelsPix::kDeviatingNumPhes );

          if (IsCheckDeviatingBehavior())
                bad.SetUnsuitable(MBadPixelsPix::kUnsuitableRun);
          continue;
        }
      
      areaweights   [aidx]   += nphe*nphe;
      areaphes      [aidx]   += nphe;
      numareavalid  [aidx]   ++;

      if (aidx == 0)
        fNumInnerFFactorMethodUsed++;

      sectorweights [sector] += nphe*nphe/area/area;
      sectorphes    [sector] += nphe/area;
      numsectorvalid[sector] ++;
    } 

  *fLog << endl;

  for (Int_t aidx=0; aidx<nareas; aidx++)
    {

      MCalibrationChargePix &apix = (MCalibrationChargePix&)chargecam->GetAverageArea(aidx);

      if (numareavalid[aidx] == 1)
        areaweights[aidx] = 0.;
      else if (numareavalid[aidx] == 0)
        {
          areaphes[aidx]    = -1.;
          areaweights[aidx] = -1.;
        }
      else
        {
          areaweights[aidx] = (areaweights[aidx] - areaphes[aidx]*areaphes[aidx]/numareavalid[aidx])
                            / (numareavalid[aidx]-1);
          areaphes[aidx]    /=  numareavalid[aidx];
        }
      
      if (areaweights[aidx] < 0. || areaphes[aidx] <= 0.)
        {
          *fLog << warn << GetDescriptor() 
                << ": Mean number phes from area index " << aidx << " could not be calculated: "
                << " Mean: "  << areaphes[aidx] 
                << " Variance: " << areaweights[aidx]    << endl;
          apix.SetFFactorMethodValid(kFALSE);
          continue;
        }
      
      *fLog << inf << GetDescriptor() 
            << ": Average total phes for area idx " << aidx << ": "
            << Form("%7.2f +- %6.2f",areaphes[aidx],TMath::Sqrt(areaweights[aidx])) << endl;
                    
      apix.SetPheFFactorMethod   ( areaphes[aidx] );
      apix.SetPheFFactorMethodVar( areaweights[aidx] / numareavalid[aidx] );      
      apix.SetFFactorMethodValid ( kTRUE );

    }

  *fLog << endl;

  for (Int_t sector=0; sector<nsectors; sector++)
    {

      if (numsectorvalid[sector] == 1)
        sectorweights[sector] = 0.;
      else if (numsectorvalid[sector] == 0)
        {
          sectorphes[sector]    = -1.;
          sectorweights[sector] = -1.;
        }
      else
        {
          sectorweights[sector] = (sectorweights[sector] 
                                   - sectorphes[sector]*sectorphes[sector]/numsectorvalid[sector]
                                  ) 
                                / (numsectorvalid[sector]-1.);
          sectorphes[sector]     /=  numsectorvalid[sector];
        }
      
      MCalibrationChargePix &spix = (MCalibrationChargePix&)chargecam->GetAverageSector(sector);

      if (sectorweights[sector] < 0. || sectorphes[sector] <= 0.)
        {
          *fLog << warn << GetDescriptor() 
                <<": Mean number phes/area for sector " << sector << " could not be calculated: "
                << " Mean: "  << sectorphes[sector] 
                << " Variance: "             << sectorweights[sector]    << endl;
          spix.SetFFactorMethodValid(kFALSE);
          continue;
        }

      *fLog << inf << GetDescriptor() 
            << ": Avg number phes/mm^2 in sector " << sector << ": "
            << Form("%5.3f+-%4.3f",sectorphes[sector],TMath::Sqrt(sectorweights[sector]))
            << endl;

      spix.SetPheFFactorMethod   ( sectorphes[sector] );
      spix.SetPheFFactorMethodVar( sectorweights[sector] / numsectorvalid[sector]);      
      spix.SetFFactorMethodValid ( kTRUE );

    }

  //
  // Third loop: Set mean number of photo-electrons and its RMS in the pixels
  //             only excluded as: MBadPixelsPix::kChargeSigmaNotValid 
  // 
  for (Int_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix = (MCalibrationChargePix&)(*chargecam)[i];
      MBadPixelsPix         &bad =                         (*badcam)[i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        continue;
      
      if (bad.IsUncalibrated(MBadPixelsPix::kChargeSigmaNotValid))
        {
          const Int_t            aidx = (*fGeom)[i].GetAidx();
          MCalibrationChargePix &apix = (MCalibrationChargePix&)chargecam->GetAverageArea(aidx);

          pix.SetPheFFactorMethod   ( apix.GetPheFFactorMethod()    );
          pix.SetPheFFactorMethodVar( apix.GetPheFFactorMethodVar() );

          if (!pix.CalcConvFFactor())
            {
              *fLog << warn << GetDescriptor() 
                    << ": Could not calculate the Conv. FADC counts to Phes in pixel:  "
                    << Form("         %4i",pix.GetPixId()) 
                    << endl;
              bad.SetUncalibrated( MBadPixelsPix::kDeviatingNumPhes );
              if (IsCheckDeviatingBehavior())
                bad.SetUnsuitable  ( MBadPixelsPix::kUnsuitableRun    );
            }

        }
    }
  
  return kTRUE;
}



// ------------------------------------------------------------------------
//
// Returns kFALSE if pointer to MCalibrationBlindCam is NULL
//
// The check returns kFALSE if:
//
// 1) fLambda and fLambdaCheck are separated relatively to each other by more than fLambdaCheckLimit
// 2) BlindPixel has an fLambdaErr greater than fLambdaErrLimit
// 
// Calls:
// - MCalibrationBlindPix::CalcFluxInsidePlexiglass()
//
Bool_t MCalibrationChargeCalc::FinalizeBlindCam()
{

  MCalibrationBlindCam  *blindcam = fIntensBlind   
    ? (MCalibrationBlindCam*)fIntensBlind->GetCam() : fBlindCam;

  if (!blindcam)
    return kFALSE;  

  Int_t   nvalid  = 0;

  for (Int_t i=0; i<blindcam->GetSize(); i++)
    {
      
      MCalibrationBlindPix &blindpix = (MCalibrationBlindPix&)(*blindcam)[i];

      if (!blindpix.IsValid())
        continue;
      
      const Float_t lambda      = blindpix.GetLambda();
      const Float_t lambdaerr   = blindpix.GetLambdaErr();
      const Float_t lambdacheck = blindpix.GetLambdaCheck();

      if (2.*(lambdacheck-lambda)/(lambdacheck+lambda) > fLambdaCheckLimit)
        {
          *fLog << warn << GetDescriptor() << ": Lambda="
                << Form("%4.2f", lambda) << " and Lambda-Check="
                << Form("%4.2f", lambdacheck) << " differ by more than "
                << Form("%4.2f", fLambdaCheckLimit) << " in the Blind Pixel Nr."
                << Form("%2i", i) << endl;
          blindpix.SetValid(kFALSE);
          continue;
        }
      
      if (lambdaerr > fLambdaErrLimit) 
        {
            *fLog << warn << GetDescriptor() << ": Error of Fitted Lambda="
                << Form("%4.2f", lambdaerr) << " is greater than "
                << Form("%4.2f", fLambdaErrLimit)
                << " in Blind Pixel Nr." << Form("%2d", i) << endl;
          blindpix.SetValid(kFALSE);
          continue;
        }
      
      if (!blindpix.CalcFluxInsidePlexiglass())
        {
          *fLog << warn << "Could not calculate the flux of photons from Blind Pixel Nr." << i << endl;
          blindpix.SetValid(kFALSE);
          continue;
        }
      
      nvalid++;
    }

  if (!nvalid)
    return kFALSE;
  
  return kTRUE;
}

// ------------------------------------------------------------------------
//
// Returns kFALSE if pointer to MCalibrationChargePINDiode is NULL
//
// The check returns kFALSE if:
//
// 1) PINDiode has a fitted charge smaller than fChargeLimit*PedRMS
// 2) PINDiode has a fit error smaller than fChargeErrLimit
// 3) PINDiode has a fitted charge smaller its fChargeRelErrLimit times its charge error
// 4) PINDiode has a charge sigma smaller than its Pedestal RMS
// 
// Calls:
// - MCalibrationChargePINDiode::CalcFluxOutsidePlexiglass()
//
Bool_t MCalibrationChargeCalc::FinalizePINDiode()
{

  if (!fPINDiode)
    return kFALSE;  

  if (fPINDiode->GetMean() < fChargeLimit*fPINDiode->GetPedRms())
    {
      *fLog << warn << GetDescriptor() << ": Fitted Charge is smaller than "
            << fChargeLimit << " Pedestal RMS in PINDiode " << endl;
      return kFALSE;
    }
  
  if (fPINDiode->GetMeanErr() < fChargeErrLimit) 
    {
      *fLog << warn << GetDescriptor() << ": Error of Fitted Charge is smaller than "
            << fChargeErrLimit << " in PINDiode " << endl;
      return kFALSE;
    }
      
  if (fPINDiode->GetMean() < fChargeRelErrLimit*fPINDiode->GetMeanErr()) 
    {
      *fLog << warn << GetDescriptor() << ": Fitted Charge is smaller than "
            << fChargeRelErrLimit << "* its error in PINDiode " << endl;
      return kFALSE;
    }
      
  if (fPINDiode->GetSigma() < fPINDiode->GetPedRms())
    {
      *fLog << warn << GetDescriptor() 
            << ": Sigma of Fitted Charge smaller than Pedestal RMS in PINDiode " << endl;
      return kFALSE;
    }


  if (!fPINDiode->CalcFluxOutsidePlexiglass())
    {
      *fLog << warn << "Could not calculate the flux of photons from the PIN Diode, "
            << "will skip PIN Diode Calibration " << endl;
      return kFALSE;
    }
  
  return kTRUE;
}

// ------------------------------------------------------------------------
//
// Calculate the average number of photons outside the plexiglass with the 
// formula: 
// 
// av.Num.photons(area index) = av.Num.Phes(area index) 
//                            / MCalibrationQEPix::GetDefaultQE(fPulserColor) 
//                            / MCalibrationQEPix::GetPMTCollectionEff()
//                            / MCalibrationQEPix::GetLightGuidesEff(fPulserColor)
//                            / MCalibrationQECam::GetPlexiglassQE()
//
// Calculate the variance on the average number of photons assuming that the error on the 
// Quantum efficiency is reduced by the number of used inner pixels, but the rest of the 
// values keeps it ordinary error since it is systematic.
//
// Loop over pixels: 
//
// - Continue, if not MCalibrationChargePix::IsFFactorMethodValid() and set:
//                    MCalibrationQEPix::SetFFactorMethodValid(kFALSE,fPulserColor)
//
// - Call MCalibrationChargePix::CalcMeanFFactor(av.Num.photons) and set: 
//        MCalibrationQEPix::SetFFactorMethodValid(kFALSE,fPulserColor) if not succesful
//
// - Calculate the quantum efficiency with the formula:
// 
//   QE = ( Num.Phes / av.Num.photons ) * MGeomCam::GetPixRatio()
//
// - Set QE in MCalibrationQEPix::SetQEFFactor ( QE, fPulserColor );
//
// - Set Variance of QE in  MCalibrationQEPix::SetQEFFactorVar ( Variance, fPulserColor );
// - Set bit MCalibrationQEPix::SetFFactorMethodValid(kTRUE,fPulserColor) 
//
// - Call MCalibrationQEPix::UpdateFFactorMethod()
//
void MCalibrationChargeCalc::FinalizeFFactorQECam()
{

  if (fNumInnerFFactorMethodUsed < 2)
    {
      *fLog << warn << GetDescriptor() 
            << ": Could not calculate F-Factor Method: Less than 2 inner pixels valid! " << endl;
      return;
    }
  
  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;

  MCalibrationChargePix &avpix = (MCalibrationChargePix&)chargecam->GetAverageArea(0);
  MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    qecam->GetAverageArea(0);

  const Float_t avphotons   = avpix.GetPheFFactorMethod()       
                           / qepix.GetDefaultQE(fPulserColor) 
                           / qepix.GetPMTCollectionEff()
                           / qepix.GetLightGuidesEff(fPulserColor)
                           / qecam->GetPlexiglassQE();

  const Float_t avphotrelvar = avpix.GetPheFFactorMethodRelVar() 
                            + qepix.GetDefaultQERelVar(fPulserColor) / fNumInnerFFactorMethodUsed
                            + qepix.GetPMTCollectionEffRelVar()
                            + qepix.GetLightGuidesEffRelVar(fPulserColor)
                            + qecam->GetPlexiglassQERelVar();

  const UInt_t nareas   = fGeom->GetNumAreas();

  //
  // Set the results in the MCalibrationChargeCam
  //
  chargecam->SetNumPhotonsFFactorMethod (avphotons);

  if (avphotrelvar > 0.)
    chargecam->SetNumPhotonsFFactorMethodErr(TMath::Sqrt( avphotrelvar * avphotons * avphotons));  

  TArrayF lowlim           (nareas);
  TArrayF upplim           (nareas);
  TArrayD avffactorphotons (nareas);
  TArrayD avffactorphotvar (nareas);
  TArrayI numffactor       (nareas);

  const UInt_t npixels  = fGeom->GetNumPixels();

  MHCamera camffactor(*fGeom,"Camffactor","F-Factor in Camera");

  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix   = (MCalibrationChargePix&)(*chargecam)[i];
      MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    (*qecam)    [i];
      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        continue;

      const Float_t photons = avphotons / fGeom->GetPixRatio(i);
      const Float_t qe      = pix.GetPheFFactorMethod() / photons ;

      const Float_t qerelvar = avphotrelvar +  pix.GetPheFFactorMethodRelVar();

      qepix.SetQEFFactor    ( qe            , fPulserColor );
      qepix.SetQEFFactorVar ( qerelvar*qe*qe, fPulserColor );      
      qepix.SetFFactorMethodValid(  kTRUE   , fPulserColor );

      if (!qepix.UpdateFFactorMethod( qecam->GetPlexiglassQE() ))
        *fLog << warn << GetDescriptor() 
              << ": Cannot update Quantum efficiencies with the F-Factor Method" << endl;

      //
      // The following pixels are those with deviating sigma, but otherwise OK, 
      // probably those with stars during the pedestal run, but not the cal. run.
      //
      if (!pix.CalcMeanFFactor( photons , avphotrelvar ))
        {
          bad.SetUncalibrated( MBadPixelsPix::kDeviatingFFactor );
          if (IsCheckDeviatingBehavior())
            bad.SetUnsuitable  ( MBadPixelsPix::kUnreliableRun    );
          continue;
        }

      const Int_t aidx = (*fGeom)[i].GetAidx();
      const Float_t ffactor = pix.GetMeanFFactorFADC2Phot();

      camffactor.Fill(i,ffactor);
      camffactor.SetUsed(i);

      avffactorphotons[aidx] += ffactor;
      avffactorphotvar[aidx] += ffactor*ffactor;
      numffactor[aidx]++;
    }

  for (UInt_t i=0; i<nareas; i++)
    {

      if (numffactor[i] == 0)
        {
          *fLog << warn << GetDescriptor() << ": No pixels with valid total F-Factor found "
                << "in area index: " << i << endl;
          continue;
        }

      avffactorphotvar[i] = (avffactorphotvar[i] - avffactorphotons[i]*avffactorphotons[i]/numffactor[i]) 
                          / (numffactor[i]-1.);
      avffactorphotons[i] = avffactorphotons[i] / numffactor[i];

      if (avffactorphotvar[i] < 0.)
        {
          *fLog << warn << GetDescriptor() << ": No pixels with valid variance of total F-Factor found "
                << "in area index: " << i << endl;
          continue;
        }

      lowlim  [i] = 1.;   // Lowest known F-Factor of a PMT
      upplim  [i] = avffactorphotons[i] + fFFactorErrLimit*TMath::Sqrt(avffactorphotvar[i]);

      TArrayI area(1);
      area[0] = i;

      TH1D *hist = camffactor.ProjectionS(TArrayI(),area,"_py",100);
      hist->Fit("gaus","Q0");
      const Float_t mean  = hist->GetFunction("gaus")->GetParameter(1);
      const Float_t sigma = hist->GetFunction("gaus")->GetParameter(2);
      const Int_t   ndf   = hist->GetFunction("gaus")->GetNDF();

      if (IsDebug())
        camffactor.DrawClone();

      if (ndf < 2)
        {
          *fLog << warn << GetDescriptor() << ": Cannot use a Gauss fit to the F-Factor " 
                << "in the camera with area index: " << i << endl;
          *fLog << "Number of dof.: " << ndf << " is smaller than 2 " << endl;
          *fLog << "Will use the simple mean and rms." << endl;
          delete hist;
          continue;
        }
      
      const Double_t prob = hist->GetFunction("gaus")->GetProb();

      if (prob < 0.001)
        {
          *fLog << warn << GetDescriptor() << ": Cannot use a Gauss fit to the F-Factor " 
                << "in the camera with area index: " << i << endl;
          *fLog << "Fit probability " << prob
                << " is smaller than 0.001 " << endl;
          *fLog << "Will use the simple mean and rms." << endl;
          delete hist;
          continue;
        }

      *fLog << inf << GetDescriptor() << ": Mean F-Factor " 
          << "with area index #" << i << ": "
            << Form("%4.2f+-%4.2f",mean,sigma) << endl;

      lowlim  [i] = 1.;
      upplim  [i] = mean  + fFFactorErrLimit*sigma;

      delete hist;
    }
  
  *fLog << endl;
  
  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix   = (MCalibrationChargePix&)(*chargecam)[i];
      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        continue;
      
      const Float_t ffactor = pix.GetMeanFFactorFADC2Phot();
      const Int_t   aidx   = (*fGeom)[i].GetAidx();

      if ( ffactor < lowlim[aidx] || ffactor > upplim[aidx] )
        {
          *fLog << warn << "Overall F-Factor "
                << Form("%5.2f",ffactor) << " out of range ["
                << Form("%5.2f,%5.2f",lowlim[aidx],upplim[aidx]) << "] Pixel " << i << endl;

          bad.SetUncalibrated( MBadPixelsPix::kDeviatingFFactor );
          if (IsCheckDeviatingBehavior())
            bad.SetUnsuitable  ( MBadPixelsPix::kUnreliableRun    );
        }
    }

  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationChargePix &pix   = (MCalibrationChargePix&)(*chargecam)[i];
      MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    (*qecam)    [i];
      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        {
          qepix.SetFFactorMethodValid(kFALSE,fPulserColor);
          pix.SetFFactorMethodValid(kFALSE);
          continue;
        }
    }
}


// ------------------------------------------------------------------------
//
// Loop over pixels: 
//
// - Continue, if not MCalibrationBlindPix::IsFluxInsidePlexiglassAvailable() and set:
//                    MCalibrationQEPix::SetBlindPixelMethodValid(kFALSE,fPulserColor)
//
// - Calculate the quantum efficiency with the formula:
// 
//   QE =  Num.Phes / MCalibrationBlindPix::GetFluxInsidePlexiglass() 
//        / MGeomPix::GetA() * MCalibrationQECam::GetPlexiglassQE()
//
// - Set QE in MCalibrationQEPix::SetQEBlindPixel ( QE, fPulserColor );
// - Set Variance of QE in  MCalibrationQEPix::SetQEBlindPixelVar ( Variance, fPulserColor );
// - Set bit MCalibrationQEPix::SetBlindPixelMethodValid(kTRUE,fPulserColor) 
//
// - Call MCalibrationQEPix::UpdateBlindPixelMethod()
//
void MCalibrationChargeCalc::FinalizeBlindPixelQECam()
{


  MCalibrationBlindCam  *blindcam  = fIntensBlind
    ? (MCalibrationBlindCam*) fIntensBlind->GetCam(): fBlindCam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;
  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;

  if (!blindcam)
    return;

  //
  // Set the results in the MCalibrationChargeCam
  //
  if (!blindcam || !(blindcam->IsFluxInsidePlexiglassAvailable()))
    {
  
      const Float_t photons = blindcam->GetFluxInsidePlexiglass() * (*fGeom)[0].GetA()
                           / qecam->GetPlexiglassQE();
      chargecam->SetNumPhotonsBlindPixelMethod(photons);
          
      const Float_t photrelvar = blindcam->GetFluxInsidePlexiglassRelVar() 
                              + qecam->GetPlexiglassQERelVar();
 
      if (photrelvar > 0.)
        chargecam->SetNumPhotonsBlindPixelMethodErr(TMath::Sqrt( photrelvar * photons * photons));  
    }
  
  //
  //  With the knowledge of the overall photon flux, calculate the 
  //  quantum efficiencies after the Blind Pixel and PIN Diode method
  //
  const UInt_t npixels  = fGeom->GetNumPixels();
  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    (*qecam)    [i];
      
      if (!blindcam || !(blindcam->IsFluxInsidePlexiglassAvailable()))
        {
          qepix.SetBlindPixelMethodValid(kFALSE, fPulserColor);
          continue;
        }

      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (bad.IsUnsuitable (MBadPixelsPix::kUnsuitableRun))
        {
          qepix.SetBlindPixelMethodValid(kFALSE, fPulserColor);
          continue;
        }
      
      MCalibrationChargePix &pix   = (MCalibrationChargePix&)(*chargecam)[i];
      MGeomPix              &geo   =                         (*fGeom)    [i];

      const Float_t qe        = pix.GetPheFFactorMethod() 
                             / blindcam->GetFluxInsidePlexiglass() 
                             / geo.GetA() 
                             * qecam->GetPlexiglassQE();

      const Float_t qerelvar = blindcam->GetFluxInsidePlexiglassRelVar() 
                               + qecam->GetPlexiglassQERelVar()
                               + pix.GetPheFFactorMethodRelVar();

      qepix.SetQEBlindPixel    ( qe            , fPulserColor );
      qepix.SetQEBlindPixelVar ( qerelvar*qe*qe, fPulserColor );      
      qepix.SetBlindPixelMethodValid(  kTRUE   , fPulserColor );

      if (!qepix.UpdateBlindPixelMethod( qecam->GetPlexiglassQE()))
        *fLog << warn << GetDescriptor() 
              << ": Cannot update Quantum efficiencies with the Blind Pixel Method" << endl;
    }
}

// ------------------------------------------------------------------------
//
// Loop over pixels: 
//
// - Continue, if not MCalibrationChargePINDiode::IsFluxOutsidePlexiglassAvailable() and set:
//                    MCalibrationQEPix::SetPINDiodeMethodValid(kFALSE,fPulserColor)
//
// - Calculate the quantum efficiency with the formula:
// 
//   QE =  Num.Phes / MCalibrationChargePINDiode::GetFluxOutsidePlexiglass() / MGeomPix::GetA()
//
// - Set QE in MCalibrationQEPix::SetQEPINDiode ( QE, fPulserColor );
// - Set Variance of QE in  MCalibrationQEPix::SetQEPINDiodeVar ( Variance, fPulserColor );
// - Set bit MCalibrationQEPix::SetPINDiodeMethodValid(kTRUE,fPulserColor) 
//
// - Call MCalibrationQEPix::UpdatePINDiodeMethod()
//
void MCalibrationChargeCalc::FinalizePINDiodeQECam()
{
  
  const UInt_t npixels  = fGeom->GetNumPixels();

  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;

  if (!fPINDiode)
    return;

  //
  //  With the knowledge of the overall photon flux, calculate the 
  //  quantum efficiencies after the PIN Diode method
  //
  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    (*qecam)    [i];

      if (!fPINDiode)
        {
          qepix.SetPINDiodeMethodValid(kFALSE, fPulserColor);
          continue;
        }
      
      if (!fPINDiode->IsFluxOutsidePlexiglassAvailable())
        {
          qepix.SetPINDiodeMethodValid(kFALSE, fPulserColor);
          continue;
        }

      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (!bad.IsUnsuitable (MBadPixelsPix::kUnsuitableRun))
        {
          qepix.SetPINDiodeMethodValid(kFALSE, fPulserColor);
          continue;
        }
      
      MCalibrationChargePix &pix   = (MCalibrationChargePix&)(*chargecam)[i];
      MGeomPix              &geo =                           (*fGeom)    [i];
      
      const Float_t qe       =  pix.GetPheFFactorMethod() 
                               / fPINDiode->GetFluxOutsidePlexiglass() 
                               / geo.GetA();

      const Float_t qerelvar = fPINDiode->GetFluxOutsidePlexiglassRelVar() + pix.GetPheFFactorMethodRelVar();

      qepix.SetQEPINDiode    ( qe            , fPulserColor );
      qepix.SetQEPINDiodeVar ( qerelvar*qe*qe, fPulserColor );      
      qepix.SetPINDiodeMethodValid(  kTRUE   , fPulserColor );

      if (!qepix.UpdatePINDiodeMethod())
        *fLog << warn << GetDescriptor() 
              << ": Cannot update Quantum efficiencies with the PIN Diode Method" << endl;
    }
}

// ------------------------------------------------------------------------
//
// Loop over pixels: 
//
// - Call MCalibrationQEPix::UpdateCombinedMethod()
//
void MCalibrationChargeCalc::FinalizeCombinedQECam()
{
  
  const UInt_t npixels  = fGeom->GetNumPixels();

  MCalibrationQECam     *qecam     = fIntensQE      
    ? (MCalibrationQECam*)    fIntensQE->GetCam()   : fQECam;
  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)        fIntensBad->GetCam()  : fBadPixels;

  for (UInt_t i=0; i<npixels; i++)
    {
      
      MCalibrationQEPix     &qepix = (MCalibrationQEPix&)    (*qecam)    [i];
      MBadPixelsPix         &bad   =                         (*badcam)   [i];

      if (!bad.IsUnsuitable (MBadPixelsPix::kUnsuitableRun))
        {
          qepix.SetPINDiodeMethodValid(kFALSE, fPulserColor);
          continue;
        }
      
      qepix.UpdateCombinedMethod();
    }
}

// -----------------------------------------------------------------------------------------------
//
// - Print out statistics about BadPixels of type UnsuitableType_t 
// - store numbers of bad pixels of each type in fCam or fIntensCam
//
void MCalibrationChargeCalc::FinalizeUnsuitablePixels()
{
  
  *fLog << inf << endl;
  *fLog << GetDescriptor() << ": Charge Calibration status:" << endl;
  *fLog << dec << setfill(' ');

  const Int_t nareas = fGeom->GetNumAreas();

  TArrayI counts(nareas);

  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)fIntensBad->GetCam()  : fBadPixels;
  MCalibrationChargeCam *chargecam = fIntensCam 
    ? (MCalibrationChargeCam*)fIntensCam->GetCam()  : fCam;

  for (Int_t i=0; i<badcam->GetSize(); i++)
    {
      MBadPixelsPix &bad = (*badcam)[i];
      if (!bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        {
          const Int_t aidx = (*fGeom)[i].GetAidx();
          counts[aidx]++;
        }
    }

  if (fGeom->InheritsFrom("MGeomCamMagic"))
    *fLog << " " << setw(7) << "Successfully calibrated Pixels: " 
          << Form("%s%3i%s%3i","Inner: ",counts[0]," Outer: ",counts[1]) << endl;
  
  counts.Reset();

  for (Int_t i=0; i<badcam->GetSize(); i++)
    {
      MBadPixelsPix &bad =  (*badcam)[i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnsuitableRun))
        {
          const Int_t aidx = (*fGeom)[i].GetAidx();
          counts[aidx]++;
        }
    }

  for (Int_t aidx=0; aidx<nareas; aidx++)
    chargecam->SetNumUnsuitable(counts[aidx], aidx);

  if (fGeom->InheritsFrom("MGeomCamMagic"))
    *fLog << " " << setw(7) << "Uncalibrated Pixels:            " 
          << Form("%s%3i%s%3i","Inner: ",counts[0]," Outer: ",counts[1]) << endl;

  counts.Reset();

  for (Int_t i=0; i<badcam->GetSize(); i++)
    {

      MBadPixelsPix &bad = (*badcam)[i];

      if (bad.IsUnsuitable(MBadPixelsPix::kUnreliableRun))
        {
          const Int_t aidx = (*fGeom)[i].GetAidx();
          counts[aidx]++;
        }
    }

  for (Int_t aidx=0; aidx<nareas; aidx++)
    chargecam->SetNumUnreliable(counts[aidx], aidx);

  *fLog << " " << setw(7) << "Unreliable Pixels:              "
        << Form("%s%3i%s%3i","Inner: ",counts[0]," Outer: ",counts[1]) << endl;

}

// -----------------------------------------------------------------------------------------------
//
// Print out statistics about BadPixels of type UncalibratedType_t 
// 
void MCalibrationChargeCalc::PrintUncalibrated(MBadPixelsPix::UncalibratedType_t typ, const char *text) const 
{
  
  UInt_t countinner = 0;
  UInt_t countouter = 0;

  MBadPixelsCam         *badcam    = fIntensBad 
    ? (MBadPixelsCam*)fIntensBad->GetCam()  : fBadPixels;

  for (Int_t i=0; i<badcam->GetSize(); i++)
    {
      MBadPixelsPix &bad = (*badcam)[i];

      if (bad.IsUncalibrated(typ))
        {
          if (fGeom->GetPixRatio(i) == 1.)
            countinner++;
          else
            countouter++;
        }
    }

  *fLog << " " << setw(7) << text  
        << Form("%s%3i%s%3i","Inner: ",countinner," Outer: ",countouter) << endl;
}

// --------------------------------------------------------------------------
//
// Set the path for output file
// 
void MCalibrationChargeCalc::SetOutputPath(TString path)
{
  fOutputPath = path;
  if (fOutputPath.EndsWith("/"))
    fOutputPath = fOutputPath(0, fOutputPath.Length()-1);
}

// --------------------------------------------------------------------------
//
// Set the output file
// 
void MCalibrationChargeCalc::SetOutputFile(TString file)
{ 
  fOutputFile = file; 
}

// --------------------------------------------------------------------------
//
// Get the output file
// 
const char* MCalibrationChargeCalc::GetOutputFile()
{
  return Form("%s/%s", (const char*)fOutputPath, (const char*)fOutputFile);
}

// --------------------------------------------------------------------------
//
// Read the environment for the following data members:
// - fChargeLimit
// - fChargeErrLimit
// - fChargeRelErrLimit
// - fDebug
// - fFFactorErrLimit
// - fLambdaErrLimit
// - fLambdaCheckErrLimit
// - fPheErrLimit
// 
Int_t MCalibrationChargeCalc::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
{

  Bool_t rc = kFALSE;
  if (IsEnvDefined(env, prefix, "ChargeLimit", print))
    {
      SetChargeLimit(GetEnvValue(env, prefix, "ChargeLimit", fChargeLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "ChargeErrLimit", print))
    {
      SetChargeErrLimit(GetEnvValue(env, prefix, "ChargeErrLimit", fChargeErrLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "ChargeRelErrLimit", print))
    {
      SetChargeRelErrLimit(GetEnvValue(env, prefix, "ChargeRelErrLimit", fChargeRelErrLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "Debug", print))
    {
      SetDebug(GetEnvValue(env, prefix, "Debug", IsDebug()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "ArrTimeRmsLimit", print))
    {
      SetArrTimeRmsLimit(GetEnvValue(env, prefix, "ArrTimeRmsLimit", fArrTimeRmsLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "FFactorErrLimit", print))
    {
      SetFFactorErrLimit(GetEnvValue(env, prefix, "FFactorErrLimit", fFFactorErrLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "LambdaErrLimit", print))
    {
      SetLambdaErrLimit(GetEnvValue(env, prefix, "LambdaErrLimit", fLambdaErrLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "LambdaCheckLimit", print))
    {
      SetLambdaCheckLimit(GetEnvValue(env, prefix, "LambdaCheckLimit", fLambdaCheckLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckDeadPixels", print))
    {
      SetCheckDeadPixels(GetEnvValue(env, prefix, "CheckDeadPixels", IsCheckDeadPixels()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckDeviatingBehavior", print))
    {
      SetCheckDeviatingBehavior(GetEnvValue(env, prefix, "CheckDeviatingBehavior", IsCheckDeviatingBehavior()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckExtractionWindow", print))
    {
      SetCheckExtractionWindow(GetEnvValue(env, prefix, "CheckExtractionWindow", IsCheckExtractionWindow()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckHistOverflow", print))
    {
      SetCheckHistOverflow(GetEnvValue(env, prefix, "CheckHistOverflow", IsCheckHistOverflow()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckOscillations", print))
    {
      SetCheckOscillations(GetEnvValue(env, prefix, "CheckOscillations", IsCheckOscillations()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "CheckArrivalTimes", print))
    {
      SetCheckArrivalTimes(GetEnvValue(env, prefix, "CheckArrivalTimes", IsCheckArrivalTimes()));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "PheErrLowerLimit", print))
    {
      SetPheErrLowerLimit(GetEnvValue(env, prefix, "PheErrLowerLimit", fPheErrLowerLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "PheErrUpperLimit", print))
    {
      SetPheErrUpperLimit(GetEnvValue(env, prefix, "PheErrUpperLimit", fPheErrUpperLimit));
      rc = kTRUE;
    }
  if (IsEnvDefined(env, prefix, "UseExtractorRes", print))
    {
      SetUseExtractorRes(GetEnvValue(env, prefix, "UseExtractorRes", fUseExtractorRes));
      rc = kTRUE;
    }

  return rc;
}


  
