//# MomentCalcBase.h:
//# Copyright (C) 1997,1999,2000,2001,2002
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This library is free software; you can redistribute it and/or modify it
//# under the terms of the GNU Library General Public License as published by
//# the Free Software Foundation; either version 2 of the License, or (at your
//# option) any later version.
//#
//# This library is distributed in the hope that it will be useful, but WITHOUT
//# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; if not, write to the Free Software Foundation,
//# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//# $Id: MomentCalculator.h 20299 2008-04-03 05:56:44Z gervandiepen $

#ifndef IMAGEANALYSIS_MOMENTCALCBASE_H
#define IMAGEANALYSIS_MOMENTCALCBASE_H

namespace casa {

//# Forward Declarations
template <class T> class MomentsBase;
// <summary>
// Abstract base class for moment calculator classes
// </summary>
// <use visibility=export>
// <reviewed reviewer="" date="yyyy/mm/dd" tests="" demos="">
// </reviewed>
//
// <prerequisite>
//   <li> <linkto class="MomentsBase">MomentsBase</linkto>
//   <li> <linkto class="ImageMoments">ImageMoments</linkto>
//   <li> <linkto class="casacore::LatticeApply">casacore::LatticeApply</linkto>
//   <li> <linkto class="casacore::LineCollapser">casacore::LineCollapser</linkto>
// </prerequisite>
//
// <synopsis>
//  This class, its concrete derived classes, and the classes casacore::LineCollapser,
//  ImageMoments, MSMoments, and casacore::LatticeApply are connected as follows.   casacore::LatticeApply offers 
//  functions so that the application programmer does not need to worry about how 
//  to optimally iterate through a casacore::Lattice; it deals with tiling and to a 
//  lesser extent memory.    casacore::LatticeApply functions are used by offering a class 
//  object to them that has a member function with a name and signature 
//  specified by an abstract base class that casacore::LatticeApply uses and the 
//  offered class inherits from.   Specifically, in this case, MomentCalcBase
//  inherits from casacore::LineCollapser and casacore::LatticeApply uses objects and methods of this
//  class (but does not inherit from it).  This defines the functions
//  <src>collapse</src> and <src>multiProcess</src> which operate on a vector
//  extracted from a Lattice.  The former returns one number, the latter a vector
//  of numbers from that profile.  MomentCalcBase is a base class for
//  for moment calculation and the <src>multiProcess</src>
//  functions are used to compute moments  (e.g., mean, sum, sum squared, 
//  intensity weighted velocity etc).
//
//  It is actually the concrete classes derived from MomentCalcBase (call them,
//  as a group, the MomentCalculator classes) that implement the <src>multiProcess</src> 
//  functions.  These derived classes allow different 
//  algorithms to be written with which moments of the vector can be computed. 
//
//  Now, so far, we have a casacore::LatticeApply function which iterates through Lattices,
//  extracts vectors, and offers them up to functions implemented in the derived 
//  MomentCalculator classes to compute the moments.   As well as that, we need some
//  class to actually construct the MomentCalculator classes and to feed them to 
//  LatticeApply.   This is the role of the ImageMoments or MSMoments classes.  
//  They are a high level 
//  class which takes control information from users specifying which moments they 
//  would like to calculate and how. They also provide the ancilliary masking lattice to 
//  the MomentCalculator constructors. The actual computational work is done by the 
//  MomentCalculator classes. So MomentsBase, MomentCalcBase and their derived 
//  MomentCalculator classes are really one unit; none of them are useful without 
//  the others.  The separation of functionality is caused by having the
//  casacore::LatticeApply class that knows all about optimally iterating through Lattices.
//
//  The coupling between these classes is done partly by the "friendship".   MomentsBase and 
//  its inheritances 
//  grant friendship to MomentCalcBase so that the latter has access to the private data and 
//  private functions of the formers.  MomentCalcBase then operates as an interface between 
//  its derived MomentCalculator classes and ImageMoments or MSMoments. It retrieves private data 
//  from these classes, and also activates private functions in these classes, on behalf 
//  of the MomentCalculator classes. The rest of the coupling is done via the constructors 
//  of the derived MomentCalculator classes.  
//
//  Finally, MomentCalcBase also has a number of protected functions that are common to its
//  derived classes (e.g. fitting, accumulating sums etc).  It also has protected
//  data that is common to all the MomentCalculator classes.  This protected data is accessed 
//  directly by name rather than with interface functions as there is too much of it.  Of 
//  course, since MomentCalcBase is an abstract base class, it is up to the MomentCalculator 
//  classes to give the MomentCalcBase protected data objects values.
//
//  For discussion about different moments and algorithms to compute them see the 
//  discussion in <linkto class="MomentsBase">MomentsBase</linkto>, 
//  <linkto class="ImageMoments">ImageMoments</linkto>, 
//  <linkto class="MSMoments">MSMoments</linkto> and also in
//  the derived classes documentation.
// </synopsis>
//
// <example>
//  Since MomentCalcBase is an abstract class, we defer code examples to
//  the derived classes.
// </example>
//
// <motivation>
// We were desirous of writing functions to optimally iterate through Lattices
// so that the application programmer did not have to know anything about tiling
// or memory if possible.   These are the casacore::LatticeApply functions. To incorporate 
// MomentsBase and its inheritances into this scheme required some of it to be shifted into 
// MomentCalcBase and its derived classes.
// </motivation>
//
// <note role=tip>
// Note that there are is assignment operator or copy constructor.
// Do not use the ones the system would generate either.
// </note>
//
// <todo asof="yyyy/mm/dd">
//  <li> Derive more classes !
// </todo>


template <class T> class MomentCalcBase : public casacore::LineCollapser<T,T> {
public:

    using AccumType = typename casacore::NumericTraits<T>::PrecisionType;
    using DataIterator = typename casacore::Vector<T>::const_iterator;
    using MaskIterator = casacore::Vector<casacore::Bool>::const_iterator;

    virtual ~MomentCalcBase();

    // Returns the number of failed fits if doing fitting
    virtual inline casacore::uInt nFailedFits() const { return nFailed_p; }

protected:

    // A number of private data members are kept here in the base class
    // as they are common to the derived classes.  Since this class
    // is abstract, they have to be filled by the derived classes.

    // CoordinateSystem
    casacore::CoordinateSystem cSys_p;

    // This vector is a container for all the possible moments that
    // can be calculated.  They are in the order given by the MomentsBase
    // enum MomentTypes
    casacore::Vector<T> calcMoments_p;
    casacore::Vector<casacore::Bool> calcMomentsMask_p;

    // This vector tells us which elements of the calcMoments_p vector
    // we wish to select
    casacore::Vector<casacore::Int> selectMoments_p;

    // Although the general philosophy of these classes is to compute
    // all the posisble moments and then select the ones we want,
    // some of them are too expensive to calculate unless they are
    // really wanted.  These are the median moments and those that
    // require a second pass.  These control Bools tell us whether
    // we really want to compute the expensive ones.
    casacore::Bool doMedianI_p, doMedianV_p, doAbsDev_p;

    // These vectors are used to transform coordinates between pixel and world
    casacore::Vector<casacore::Double> pixelIn_p, worldOut_p;

    // All computations involving casacore::Coordinate conversions are relatively expensive
    // These Bools signifies whether we need coordinate calculations or not for
    // the full profile, and for some occaisional calculations
    casacore::Bool doCoordProfile_p, doCoordRandom_p;

    // This vector houses the world coordinate values for the profile if it
    // was from a separable axis. This means this vector can be pre computed
    // just once, instead of working out the coordinates for each profile
    // (expensive).  It should only be filled if doCoordCalc_p is true
    casacore::Vector<casacore::Double> sepWorldCoord_p;

    // This vector is used to hold the abscissa values
    casacore::Vector<T> abcissa_p;

    // This string tells us the name of the moment axis (VELO or FREQ etc)
    casacore::String momAxisType_p;

    // This is the number of Gaussian fits that failed.
    casacore::uInt nFailed_p;

    // This scale factor is the increment along the moment axis
    // applied so that units for the Integrated moment are like
    // Jy/beam.km/s (or whatever is needed for the moment axis units)
    // For non-linear velocities (e.g. optical) this is approximate
    // only and is computed at the reference pixel
    casacore::Double integratedScaleFactor_p;

    // Accumulate statistical sums from a vector
    inline void accumSums(
        typename casacore::NumericTraits<T>::PrecisionType& s0,
        typename casacore::NumericTraits<T>::PrecisionType& s0Sq,
        typename casacore::NumericTraits<T>::PrecisionType& s1,
        typename casacore::NumericTraits<T>::PrecisionType& s2,
        casacore::Int& iMin, casacore::Int& iMax, T& dMin, T& dMax,
        casacore::Int i, T datum, casacore::Double coord
    ) const {
        // Accumulate statistical sums from this datum
        //
        // casacore::Input:
        //  i              Index
        //  datum          Pixel value
        //  coord          casacore::Coordinate value on moment axis
        // casacore::Input/output:
        //  iMin,max       index of dMin and dMax
        //  dMin,dMax      minimum and maximum value
        // Output:
        //  s0             sum (I)
        //  s0Sq           sum (I*I)
        //  s1             sum (I*v)
        //  s2             sum (I*v*v)
        typename casacore::NumericTraits<T>::PrecisionType dDatum = datum;
        s0 += dDatum;
        s0Sq += dDatum*dDatum;
        s1 += dDatum*coord;
        s2 += dDatum*coord*coord;
        if (datum < dMin) {
            iMin = i;
            dMin = datum;
        }
        if (datum > dMax) {
            iMax = i;
            dMax = datum;
        }
    }

    // Determine if the spectrum is pure noise
    casacore::uInt allNoise(T& dMean,
        const casacore::Vector<T>& data,
        const casacore::Vector<casacore::Bool>& mask,
        T peakSNR,
        T stdDeviation
    ) const;

    // Check validity of constructor inputs
    void constructorCheck(
        casacore::Vector<T>& calcMoments,
        casacore::Vector<casacore::Bool>& calcMomentsMask,
        const casacore::Vector<casacore::Int>& selectMoments,
        casacore::uInt nLatticeOut
    ) const;

    // Find out from the selectMoments array whether we want
    // to compute the more expensive moments
    void costlyMoments(
        MomentsBase<T>& iMom, casacore::Bool& doMedianI,
        casacore::Bool& doMedianV, casacore::Bool& doAbsDev
    ) const;

    // Return the casacore::Bool saying whether we need to compute coordinates
    // or not for the requested moments
    void doCoordCalc(
        casacore::Bool& doCoordProfile,
        casacore::Bool& doCoordRandom,
        const MomentsBase<T>& iMom
    ) const;

    // Return the casacore::Bool from the ImageMoments or MSMoments object saying whether we
    // are going to fit Gaussians to the profiles or not.
    casacore::Bool doFit(const MomentsBase<T>& iMom) const;

    // Find the next masked or unmasked point in a vector
    casacore::Bool findNextDatum(
        casacore::uInt& iFound, const casacore::uInt& n,
        const casacore::Vector<casacore::Bool>& mask, const casacore::uInt& iStart,
        const casacore::Bool& findGood
    ) const;

    // Fit a Gaussian to x and y arrays given guesses for the gaussian parameters
    casacore::Bool fitGaussian(
        casacore::uInt& nFailed, T& peak, T& pos,
        T& width, T& level, const casacore::Vector<T>& x,
        const casacore::Vector<T>& y, const casacore::Vector<casacore::Bool>& mask,
        T peakGuess, T posGuess, T widthGuess,
        T levelGuess
    ) const;

    // Automatically fit a Gaussian to a spectrum, including finding the
    // starting guesses.
    casacore::Bool getAutoGaussianFit(
        casacore::uInt& nFailed, casacore::Vector<T>& gaussPars,
        const casacore::Vector<T>& x, const casacore::Vector<T>& y,
        const casacore::Vector<casacore::Bool>& mask, T peakSNR,
        T stdDeviation
    ) const;

    // Automatically work out a guess for the Gaussian parameters
    // Returns false if all pixels masked.
    casacore::Bool getAutoGaussianGuess(
        T& peakGuess, T& posGuess,
        T& widthGuess, T& levelGuess,
        const casacore::Vector<T>& x, const casacore::Vector<T>& y,
        const casacore::Vector<casacore::Bool>& mask
    ) const;

    // Compute the world coordinate for the given moment axis pixel
    inline casacore::Double getMomentCoord(
        const MomentsBase<T>& iMom, casacore::Vector<casacore::Double>& pixelIn,
        casacore::Vector<casacore::Double>& worldOut, casacore::Double momentPixel,
        casacore::Bool asVelocity=false
    ) const {
        // Find the value of the world coordinate on the moment axis
        // for the given moment axis pixel value.
        //
        // Input
        //   momentPixel   is the index in the profile extracted from the data
        // casacore::Input/output
        //   pixelIn       Pixels to convert.  Must all be filled in except for
        //                 pixelIn(momentPixel).
        //   worldOut      casacore::Vector to hold result
        //
        // Should really return a casacore::Fallible as I don't check and see
        // if the coordinate transformation fails or not
        //
        // Should really check the result is true, but for speed ...
        pixelIn[iMom.momentAxis_p] = momentPixel;
        cSys_p.toWorld(worldOut, pixelIn);
        if (asVelocity) {
            casacore::Double velocity;
            cSys_p.spectralCoordinate().frequencyToVelocity(
                velocity, worldOut(iMom.worldMomentAxis_p)
            );
            return velocity;
        }
        return worldOut(iMom.worldMomentAxis_p);
    }

    // Examine a mask and determine how many segments of unmasked points
    // it consists of.
    void lineSegments (
        casacore::uInt& nSeg, casacore::Vector<casacore::uInt>& start,
        casacore::Vector<casacore::uInt>& nPts, const casacore::Vector<casacore::Bool>& mask
    ) const;

    // Return the moment axis from the ImageMoments object
    casacore::Int& momentAxis(MomentsBase<T>& iMom) const;

    // Return the name of the moment/profile axis
    casacore::String momentAxisName(
        const casacore::CoordinateSystem&,
        const MomentsBase<T>& iMom
    ) const;

    // Return the peak SNR for determination of all noise spectra from
    // the ImageMoments or MSMoments object
    T& peakSNR(MomentsBase<T>& iMom) const;

    // Return the selected pixel intensity range from the ImageMoments or MSMoments
    // object and the Bools describing whether it is inclusion or exclusion
    void selectRange(
        casacore::Vector<T>& pixelRange,
        casacore::Bool& doInclude,
        casacore::Bool& doExlude,
        MomentsBase<T>& iMom
    ) const;

    // The MomentCalculators compute a vector of all possible moments.
    // This function returns a vector which selects the desired moments from that
    // "all moment" vector.
    casacore::Vector<casacore::Int> selectMoments(MomentsBase<T>& iMom) const;

    // Fill the ouput moments array
    void setCalcMoments(
        const MomentsBase<T>& iMom, casacore::Vector<T>& calcMoments,
        casacore::Vector<casacore::Bool>& calcMomentsMask, casacore::Vector<casacore::Double>& pixelIn,
        casacore::Vector<casacore::Double>& worldOut, casacore::Bool doCoord,
        casacore::Double integratedScaleFactor, T dMedian,
        T vMedian, casacore::Int nPts,
        typename casacore::NumericTraits<T>::PrecisionType s0,
        typename casacore::NumericTraits<T>::PrecisionType s1,
        typename casacore::NumericTraits<T>::PrecisionType s2,
        typename casacore::NumericTraits<T>::PrecisionType s0Sq,
        typename casacore::NumericTraits<T>::PrecisionType sumAbsDev,
        T dMin, T dMax, casacore::Int iMin, casacore::Int iMax
    ) const;

    // Fill a string with the position of the cursor
    void setPosLabel(casacore::String& title, const casacore::IPosition& pos) const;

    // Install casacore::CoordinateSystem and SpectralCoordinate
    // in protected data members
    void setCoordinateSystem(
        casacore::CoordinateSystem& cSys, MomentsBase<T>& iMom
    );

    // Set up separable moment axis coordinate vector and
    // conversion vectors if not separable
    void setUpCoords(
        const MomentsBase<T>& iMom, casacore::Vector<casacore::Double>& pixelIn,
        casacore::Vector<casacore::Double>& worldOut, casacore::Vector<casacore::Double>& sepWorldCoord,
        casacore::LogIO& os, casacore::Double& integratedScaleFactor,
        const casacore::CoordinateSystem& cSys, casacore::Bool doCoordProfile,
        casacore::Bool doCoordRandom
    ) const;

    // Return standard deviation of image from ImageMoments or MSMoments object
    T& stdDeviation(MomentsBase<T>& iMom) const;

protected:
    // Check if #pixels is indeed 1.
    virtual void init (casacore::uInt nOutPixelsPerCollapse);
};

}

#ifndef CASACORE_NO_AUTO_TEMPLATES
#include <imageanalysis/ImageAnalysis/MomentCalcBase.tcc>
#endif
#endif