//# ImageConvolver.cc:  convolution of an image by given Array
//# Copyright (C) 1995,1996,1997,1998,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: ImageConvolver.tcc 20615 2009-06-09 02:16:01Z Malte.Marquarding $
//   
#include <imageanalysis/ImageAnalysis/ImageConvolver.h>
//
#include <casacore/casa/aips.h>
#include <casacore/coordinates/Coordinates/CoordinateUtil.h>
#include <casacore/casa/Exceptions/Error.h>
#include <casacore/casa/Logging/LogIO.h>
#include <casacore/casa/BasicMath/Math.h>
#include <casacore/casa/BasicSL/String.h>
#include <casacore/images/Images/PagedImage.h>
#include <casacore/images/Images/TempImage.h>
#include <casacore/images/Regions/RegionHandler.h>
#include <casacore/images/Regions/ImageRegion.h>
#include <casacore/images/Images/ImageUtilities.h>
#include <casacore/lattices/Lattices/ArrayLattice.h>
#include <casacore/lattices/LatticeMath/LatticeConvolver.h>
#include <casacore/lattices/Lattices/LatticeUtilities.h>
#include <casacore/lattices/LEL/LatticeExpr.h>
#include <casacore/lattices/LEL/LatticeExprNode.h>
#include <iostream>

#include <memory>


namespace casa { //# NAMESPACE CASA - BEGIN

template <class T> 
ImageConvolver<T>::ImageConvolver ()
{}

template <class T>
ImageConvolver<T>::ImageConvolver(const ImageConvolver<T> &other)
{
   operator=(other);
}

template <class T> 
ImageConvolver<T>::~ImageConvolver ()
{}


template <class T>
ImageConvolver<T> &ImageConvolver<T>::operator=(const ImageConvolver<T> &other)
{

// There is no state

   if (this != &other) {
   }
   return *this;
}


template <class T>
void ImageConvolver<T>::convolve(casacore::LogIO& os,  
                                 casacore::ImageInterface<T>& imageOut,
                                 const casacore::ImageInterface<T>& imageIn,
                                 const casacore::ImageInterface<T>& kernel,
                                 ScaleTypes scaleType, casacore::Double scale,
                                 casacore::Bool copyMiscellaneous, casacore::Bool warnOnly)
{
// Check Coordinates
    checkCoordinates (os, imageIn.coordinates(), kernel.coordinates(),
                      warnOnly);

// Convolve

    convolve (os, imageOut, imageIn, kernel,
              scaleType, scale, copyMiscellaneous);
}

template <class T>
void ImageConvolver<T>::convolve(casacore::LogIO& os,  
                                 casacore::ImageInterface<T>& imageOut,
                                 const casacore::ImageInterface<T>& imageIn,
                                 const casacore::Array<T>& kernel,
                                 ScaleTypes scaleType, casacore::Double scale,
                                 casacore::Bool copyMiscellaneous)
{
    casacore::ArrayLattice<T> kernelLattice(kernel);
    convolve (os, imageOut, imageIn, kernelLattice, 
              scaleType, scale, copyMiscellaneous);
}


template <class T>
void ImageConvolver<T>::convolve(casacore::LogIO& os,  
                                 casacore::ImageInterface<T>& imageOut,
                                 const casacore::ImageInterface<T>& imageIn,
                                 const casacore::Lattice<T>& kernel,
                                 ScaleTypes scaleType, casacore::Double scale,
                                 casacore::Bool copyMiscellaneous)
{

// Check
   const casacore::IPosition& inShape = imageIn.shape();
   const casacore::IPosition& outShape = imageOut.shape();
   if (!inShape.isEqual(outShape)) {
      os << "Input and output images must have same shape" << casacore::LogIO::EXCEPTION;
   }
    if (kernel.ndim() > imageIn.ndim()) {
        os << "Kernel lattice has more axes than the image!" << casacore::LogIO::EXCEPTION;
    }

// Add degenerate axes if needed

    casacore::Lattice<T>* pNewKernel = 0;
    casacore::LatticeUtilities::addDegenerateAxes (pNewKernel, kernel, inShape.nelements());
    std::unique_ptr<casacore::Lattice<T> > pnkMgr(pNewKernel);
// Normalize kernel.  
  
    casacore::LatticeExprNode node;
    if (scaleType==AUTOSCALE) {
       node = casacore::LatticeExprNode((*pNewKernel) / sum(*pNewKernel));
    } else if (scaleType==SCALE) {
       T t = static_cast<T>(scale);
       node = casacore::LatticeExprNode(t * (*pNewKernel));
    } else if (scaleType==NONE) {
       node = casacore::LatticeExprNode(*pNewKernel);
    }
    casacore::LatticeExpr<T> kernelExpr(node);
// Create convolver

    casacore::LatticeConvolver<T> lc(kernelExpr, imageIn.shape(),  casacore::ConvEnums::LINEAR);
//
    if (imageIn.isMasked()) {

// Generate output mask if needed

      makeMask(imageOut, os);

// Copy input mask to output.  Copy input pixels
// and set to zero where masked

      casacore::LatticeUtilities::copyDataAndMask(os, imageOut, imageIn, true);
// Convolve in situ

      lc.convolve(imageOut);
    } else {

// Convolve to output

      lc.convolve(imageOut, imageIn);
    }

// Overwrite output casacore::CoordinateSystem 
   imageOut.setCoordinateInfo (imageIn.coordinates());
// Copy miscellaneous things across as required

    if (copyMiscellaneous) casacore::ImageUtilities::copyMiscellaneous(imageOut, imageIn);
// Delete the restoring beam (should really check that the beam is in the
// plane of convolution)
    casacore::ImageInfo ii = imageOut.imageInfo();
    ii.removeRestoringBeam();
    imageOut.setImageInfo (ii);
}

template <class T>
void ImageConvolver<T>::makeMask(casacore::ImageInterface<T>& out, casacore::LogIO& os)  const
{
   if (out.canDefineRegion()) {   
    
// Generate mask name 
      
      casacore::String maskName = out.makeUniqueRegionName(casacore::String("mask"), 0);
    
// Make the mask if it does not exist
      
      if (!out.hasRegion (maskName, casacore::RegionHandler::Masks)) {
         out.makeMask(maskName, true, true, false, true);
         os << casacore::LogIO::NORMAL << "Created mask `" << maskName << "'" << casacore::LogIO::POST;
      }
   } else {
      os << casacore::LogIO::WARN << "Cannot make requested mask for this type of image" << endl;
   }
}


template <class T>
void ImageConvolver<T>::checkCoordinates (casacore::LogIO& os, const casacore::CoordinateSystem& cSysImage,
                                          const casacore::CoordinateSystem& cSysKernel,
                                          casacore::Bool warnOnly) const
{
   const casacore::uInt nPixelAxesK = cSysKernel.nPixelAxes();
   const casacore::uInt nPixelAxesI = cSysImage.nPixelAxes();
   if (nPixelAxesK > nPixelAxesI) {
        os << "Kernel has more pixel axes than the image" << casacore::LogIO::EXCEPTION;
    }
//
   const casacore::uInt nWorldAxesK = cSysKernel.nWorldAxes();
   const casacore::uInt nWorldAxesI = cSysImage.nWorldAxes();
   if (nWorldAxesK > nWorldAxesI) {
        os << "Kernel has more world axes than the image" << casacore::LogIO::EXCEPTION;
    }
//
   const casacore::Vector<casacore::Double>& incrI = cSysImage.increment();
   const casacore::Vector<casacore::Double>& incrK = cSysKernel.increment();
   const casacore::Vector<casacore::String>& unitI = cSysImage.worldAxisUnits();
   const casacore::Vector<casacore::String>& unitK = cSysKernel.worldAxisUnits();
//
   for (casacore::uInt i=0; i<nWorldAxesK; i++) {

// Compare casacore::Coordinate types and reference

      if (casacore::CoordinateUtil::findWorldAxis(cSysImage, i) != 
          casacore::CoordinateUtil::findWorldAxis(cSysKernel, i)) {
         if (warnOnly) {
            os << casacore::LogIO::WARN << "Coordinate types are not the same for axis " << i+1 << casacore::LogIO::POST;
         } else {
            os << "Coordinate types are not the same for axis " << i+1 << casacore::LogIO::EXCEPTION;
         }
      }

// Compare units 

      casacore::Unit u1(unitI[i]);
      casacore::Unit u2(unitK[i]);
      if (u1 != u2) {
         if (warnOnly) {
            os << casacore::LogIO::WARN << "Axis units are not consistent for axis " << i+1 << casacore::LogIO::POST;
         } else {
            os << "Axis units are not consistent for axis " << i+1 << casacore::LogIO::EXCEPTION;
         }
      }

// Compare increments ; this is not a very correct test as there may be
// values in the LinearTransform inside the coordinate.  Should really
// convert some values...  See how we go.

      casacore::Quantum<casacore::Double> q2(incrK[i], u2);
      casacore::Double val2 = q2.getValue(u1);
      if (!casacore::near(incrI[i], val2, 1.0e-6)) {
         if (warnOnly) {
            os << casacore::LogIO::WARN << "Axis increments are not consistent for axis " << i+1 << casacore::LogIO::POST;
         } else {
            os << "Axis increments are not consistent for axis " << i+1 << casacore::LogIO::EXCEPTION;
         } 
     }
   }
}

} //# NAMESPACE CASA - END