//# ImagePolarimetry.cc: polarimetric analysis
//# Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003
//# 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: ImagePolarimetry.cc 20652 2009-07-06 05:04:32Z Malte.Marquarding $

#include <casacore/casa/OS/Timer.h>

#include <imageanalysis/ImageAnalysis/ImagePolarimetry.h>

#include <casacore/casa/Arrays/Array.h>
#include <casacore/casa/Arrays/ArrayMath.h>
#include <casacore/casa/Arrays/Vector.h>
#include <casacore/casa/Arrays/Matrix.h>
#include <casacore/casa/Arrays/MaskedArray.h>
#include <casacore/casa/Arrays/MaskArrMath.h>
#include <casacore/coordinates/Coordinates/CoordinateSystem.h>
#include <casacore/coordinates/Coordinates/StokesCoordinate.h>
#include <casacore/coordinates/Coordinates/LinearCoordinate.h>
#include <casacore/casa/Exceptions/Error.h>
#include <casacore/scimath/Functionals/Polynomial.h>
#include <casacore/images/Images/ImageInterface.h>
#include <casacore/images/Images/SubImage.h>
#include <casacore/images/Images/ImageExpr.h>
#include <imageanalysis/ImageAnalysis/ImageFFT.h>
#include <casacore/images/Regions/ImageRegion.h>
#include <casacore/images/Images/ImageSummary.h>
#include <casacore/images/Images/TempImage.h>
#include <casacore/lattices/Lattices/Lattice.h>
#include <casacore/lattices/LRegions/LCSlicer.h>
#include <casacore/lattices/LEL/LatticeExprNode.h>
#include <casacore/lattices/LEL/LatticeExpr.h>
#include <casacore/lattices/Lattices/TiledLineStepper.h>
#include <casacore/lattices/Lattices/LatticeStepper.h>
#include <casacore/lattices/Lattices/LatticeIterator.h>
#include <casacore/lattices/Lattices/LatticeUtilities.h>
#include <casacore/lattices/Lattices/MaskedLatticeIterator.h>
#include <casacore/lattices/LatticeMath/LatticeStatistics.h>
#include <casacore/lattices/LRegions/LCPagedMask.h>
#include <casacore/casa/Logging/LogIO.h>
#include <casacore/casa/Logging/LogOrigin.h>
#include <casacore/casa/BasicMath/Math.h>
#include <casacore/casa/BasicSL/Constants.h>
#include <casacore/scimath/Mathematics/NumericTraits.h>
#include <casacore/casa/System/ProgressMeter.h>
#include <casacore/casa/Quanta/QC.h>
#include <casacore/casa/Quanta/MVAngle.h>
#include <casacore/casa/Utilities/GenSort.h>
#include <casacore/casa/Utilities/Assert.h>
#include <casacore/casa/BasicSL/String.h>

#include <sstream>

using namespace casacore;
namespace casa {

const std::map<ImagePolarimetry::StokesTypes, String> ImagePolarimetry::polMap = {
    {I, "I"}, {Q, "Q"}, {U, "U"}, {V, "V"}
};

ImagePolarimetry::ImagePolarimetry (const ImageInterface<Float>& image)
: _image(image.cloneII())
{
   _stokes.resize(4);
   _stokesStats.resize(4);
   _stokes.set(0);
   _stokesStats.set(0);
   _findStokes();
   _createBeamsEqMat();
}

ImagePolarimetry::ImagePolarimetry(const ImagePolarimetry &other) {
   operator=(other);
}

ImagePolarimetry &ImagePolarimetry::operator=(const ImagePolarimetry &other) {
   if (this != &other) {
      _image.reset(other._image->cloneII());
      const auto n = _stokes.size();
      for (size_t i=0; i<n; ++i) {
         if (_stokes[i]) {
            delete _stokes[i];
            _stokes[i] = nullptr;
         }
         if (other._stokes[i]) {
            _stokes[i] = other._stokes[i]->cloneII();
         }
      }
      // Just delete fitter. It will make a new one when needed.
      if (_fitter) {
         delete _fitter;
         _fitter = nullptr;
      }
      // Remake Statistics objects as needed
      _oldClip = 0.0;
      for (size_t i=0; i<n; ++i) {
         if (_stokesStats[i]) {
            delete _stokesStats[i];
            _stokesStats[i] = nullptr;
         }
      }
      _beamsEqMat.assign(other._beamsEqMat);
   }
   return *this;
}

ImagePolarimetry::~ImagePolarimetry() {
   _cleanup();
}

ImageExpr<Complex> ImagePolarimetry::complexLinearPolarization() {
    _hasQU();
    _checkQUBeams(false);
    LatticeExprNode node(
        casacore::formComplex(
            *_stokes[ImagePolarimetry::Q], *_stokes[ImagePolarimetry::U]
        )
    );
    LatticeExpr<Complex> le(node);
    ImageExpr<Complex> ie(le, String("ComplexLinearPolarization"));
    // Need a Complex Linear Polarization type
    _fiddleStokesCoordinate(ie, Stokes::Plinear);
    ie.setUnits(_image->units());
    _setInfo(ie, Q);
    return ie;
}

void ImagePolarimetry::_setInfo(
    ImageInterface<Complex>& im, StokesTypes stokes
) const {
    auto info = _image->imageInfo();
	if (info.hasMultipleBeams()) {
		info.setBeams(_stokes[stokes]->imageInfo().getBeamSet());
	}
	im.setImageInfo(info);
}

void ImagePolarimetry::_setInfo(
    ImageInterface<Float>& im, StokesTypes stokes
) const {
    auto info = _image->imageInfo();
	if (info.hasMultipleBeams()) {
		info.setBeams(_stokes[stokes]->imageInfo().getBeamSet());
	}
	im.setImageInfo(info);
}

ImageExpr<Complex> ImagePolarimetry::complexFractionalLinearPolarization() {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    _hasQU();
    ThrowIf(
       ! _stokes[ImagePolarimetry::I],
       "This image does not have Stokes I so cannot "
        "provide fractional linear polarization"
    );
    _checkIQUBeams(false);
    LatticeExprNode nodeQU(
        casacore::formComplex(
            *_stokes[ImagePolarimetry::Q], *_stokes[ImagePolarimetry::U]
        )
    );
    LatticeExprNode nodeI(*_stokes[ImagePolarimetry::I]);
    LatticeExpr<Complex> le(nodeQU/nodeI);
    ImageExpr<Complex> ie(le, String("ComplexFractionalLinearPolarization"));
    // Need a Complex Linear Polarization type
    _fiddleStokesCoordinate(ie, Stokes::PFlinear);
    ie.setUnits(Unit(""));
    _setInfo(ie, I);
    return ie;
}

ImageExpr<Float> ImagePolarimetry::fracLinPol(
    Bool debias, Float clip, Float sigma
) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    _hasQU();
    ThrowIf(
        ! _stokes[ImagePolarimetry::I],
        "This image does not have Stokes I so cannot "
        "provide fractional linear polarization"
    );
    Vector<StokesTypes> types(3);
    types[0] = I; types[1] = Q; types[2] = U;
    _checkIQUBeams(false);
    auto nodePol = _makePolIntNode(os, debias, clip, sigma, true, false);
    LatticeExprNode nodeI(*_stokes[ImagePolarimetry::I]);
    LatticeExpr<Float> le(nodePol/nodeI);
    ImageExpr<Float> ie(le, String("FractionalLinearPolarization"));
    ie.setUnits(Unit(""));
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    ie.setImageInfo(ii);
   _fiddleStokesCoordinate(ie, Stokes::PFlinear);
   return ie;
}

ImageExpr<Float> ImagePolarimetry::sigmaFracLinPol(Float clip, Float sigma) {
    // sigma_m = m * sqrt( (sigmaP/p)**2 + (sigmaI/I)**2) )
    // sigmaP = sigmaQU
    // sigmaI = sigmaI
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    _hasQU();
    ThrowIf(
        ! _stokes[ImagePolarimetry::I],
        "This image does not have Stokes I so cannot provide "
        "fractional linear polarization"
    );
    _checkIQUBeams(false);
    // Make nodes.  Don't bother debiasing.
   Bool debias = false;
   auto nodePol = _makePolIntNode(os, debias, clip, sigma, true, false);
   LatticeExprNode nodeI(*_stokes[ImagePolarimetry::I]);

   // Make expression.  We assume sigmaI = sigmaQU which is true with
   // no dynamic range limititation.  Perhaps we should work out
   // sigmaI as well.

   const auto sigma2 = sigmaLinPolInt(clip, sigma);
   LatticeExprNode n0(nodePol/nodeI);
   LatticeExprNode n1(pow(sigma2/nodePol,2));
   LatticeExprNode n2(pow(sigma2/nodeI,2));
   LatticeExpr<Float> le(n0 * sqrt(n1 + n2));
   ImageExpr<Float> ie(le, String("FractionalLinearPolarizationError"));
   ie.setUnits(Unit(""));
   auto ii = _image->imageInfo();
   ii.removeRestoringBeam();
   ie.setImageInfo(ii);
   _fiddleStokesCoordinate(ie, Stokes::PFlinear);
   return ie;
}

ImageExpr<Float> ImagePolarimetry::fracTotPol(
    Bool debias, Float clip, Float sigma
) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    Bool doLin, doCirc;
    _setDoLinDoCirc(doLin, doCirc);
    auto nodePol = _makePolIntNode(os, debias, clip, sigma, doLin, doCirc);
    LatticeExprNode nodeI(*_stokes[ImagePolarimetry::I]);
    LatticeExpr<Float> le(nodePol/nodeI);
    ImageExpr<Float> ie(le, String("FractionalTotalPolarization"));
    ie.setUnits(Unit(""));
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    ie.setImageInfo(ii);
    _fiddleStokesCoordinate(ie, Stokes::PFtotal);
    return ie;
}

void ImagePolarimetry::_setDoLinDoCirc(Bool& doLin, Bool& doCirc) const {
	LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
	doLin = _stokes[ImagePolarimetry::Q] && _stokes[ImagePolarimetry::U];
	doCirc = _stokes[ImagePolarimetry::V];
	// Should never happen
	AlwaysAssert((doLin || doCirc), AipsError);
	ThrowIf(
	    ! _stokes[ImagePolarimetry::I],
		"This image does not have Stokes I so this calculation "
	    "cannot be carried out"
	);
	if (doLin && ! _checkIQUBeams(false, false)) {
	    os << LogIO::WARN << "I, Q, and U beams are not the same, cannot do "
	        << "linear portion" << LogIO::POST;
	    doLin = false;
	}
	if (doCirc && ! _checkIVBeams(false, false)) {
	    os << LogIO::WARN << "I and V beams are not the same, cannot do "
	        << "circular portion" << LogIO::POST;
	    doCirc = false;
	}
	ThrowIf(
	    ! (doLin || doCirc), "Can do neither linear nor circular portions"
	);
}

ImageExpr<Float> ImagePolarimetry::sigmaFracTotPol(Float clip, Float sigma) {
    // sigma_m = m * sqrt( (sigmaP/P)**2 + (sigmaI/I)**2) )
    // sigmaP = sigmaQU
    // sigmaI = sigmaI
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    Bool doLin, doCirc;
    _setDoLinDoCirc(doLin, doCirc);
    // Make nodes.  Don't bother debiasing.
    Bool debias = false;
    auto nodePol = _makePolIntNode(os, debias, clip, sigma, doLin, doCirc);
    LatticeExprNode nodeI(*_stokes[ImagePolarimetry::I]);
    // Make expression.  We assume sigmaI = sigmaQU which is true with
    // no dynamic range limitation.  Perhaps we should work out
    // sigmaI as well.
    const auto sigma2 = sigmaTotPolInt(clip, sigma);
    LatticeExprNode n0(nodePol/nodeI);
    LatticeExprNode n1(pow(sigma2/nodePol,2));
    LatticeExprNode n2(pow(sigma2/nodeI,2));
    LatticeExpr<Float> le(n0 * sqrt(n1 + n2));
    ImageExpr<Float> ie(le, String("FractionalLinearPolarizationError"));
    ie.setUnits(Unit(""));
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    ie.setImageInfo(ii);
    _fiddleStokesCoordinate(ie, Stokes::PFlinear);
    return ie;
}

void ImagePolarimetry::fourierRotationMeasure(
    ImageInterface<Complex>& cpol, Bool zeroZeroLag
) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    _hasQU();
    _checkQUBeams(true, false);
    CoordinateSystem dCS;
    Stokes::StokesTypes dType = Stokes::Plinear;
    const auto shape = singleStokesShape(dCS, dType);
    if(! cpol.shape().isEqual(shape)) {
        os << "The provided  image has the wrong shape " << cpol.shape()
            << endl;
        os << "It should be of shape " << shape  << LogIO::EXCEPTION;
    }
    const auto& cSys = _image->coordinates();
    Int spectralCoord, fAxis;
    _findFrequencyAxis (spectralCoord, fAxis, cSys, -1);
    // Make Complex (Q,U) image
    LatticeExprNode node;
    if (zeroZeroLag) {
       TempImage<Float> tQ(
           _stokes[ImagePolarimetry::Q]->shape(),
           _stokes[ImagePolarimetry::Q]->coordinates()
       );
       if (_stokes[ImagePolarimetry::Q]->isMasked()) {
           tQ.makeMask(String("mask0"), true, true, false, false);
       }
       LatticeUtilities::copyDataAndMask(
           os, tQ, *_stokes[ImagePolarimetry::Q], false
       );
       _subtractProfileMean (tQ, fAxis);
       TempImage<Float> tU(
           _stokes[ImagePolarimetry::U]->shape(),
           _stokes[ImagePolarimetry::U]->coordinates()
       );
       if (_stokes[ImagePolarimetry::U]->isMasked()) {
           tU.makeMask(String("mask0"), true, true, false, false);
       }
       LatticeUtilities::copyDataAndMask(
           os, tU, *_stokes[ImagePolarimetry::U], false
       );
       _subtractProfileMean (tU, fAxis);
       // The TempImages will be cloned be LatticeExprNode so it's ok
       // that they go out of scope
       node = LatticeExprNode(formComplex(tQ, tU));
    }
    else {
        node = LatticeExprNode(
            formComplex(
                *_stokes[ImagePolarimetry::Q], *_stokes[ImagePolarimetry::U]
            )
        );
    }
    LatticeExpr<Complex> le(node);
    ImageExpr<Complex> ie(le, String("ComplexLinearPolarization"));
    // Do FFT of spectral coordinate
    Vector<Bool> axes(ie.ndim(),false);
    axes(fAxis) = true;
    ImageFFT<Complex> fftserver;
    fftserver.fft(ie, axes);
    // Recover Complex result. Coordinates are updated to include Fourier
    // coordinate, miscellaneous things (MiscInfo, ImageInfo, units, history)
    // and mask (if output has one) are copied to cpol
    fftserver.getComplex(cpol);
    // Fiddle time coordinate to be a RotationMeasure coordinate
    auto f = _findCentralFrequency(
        cSys.coordinate(spectralCoord), ie.shape()(fAxis)
    );
    _fiddleTimeCoordinate(cpol, f, spectralCoord);
    // Set Stokes coordinate to be correct type
    _fiddleStokesCoordinate(cpol, Stokes::Plinear);
    // Set units and ImageInfo
    cpol.setUnits(_image->units());
    _setInfo(cpol, Q);
}

Float ImagePolarimetry::sigmaLinPolInt(Float clip, Float sigma) {
    // sigma_P = sigma_QU
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    ThrowIf(
        ! _stokes[ImagePolarimetry::Q] && ! _stokes[ImagePolarimetry::U],
       "This image does not have Stokes Q and U so cannot "
        "provide linear polarization"
    );
    _checkQUBeams(false);
    Float sigma2 = 0.0;
    if (sigma > 0) {
        sigma2 = sigma;
    }
    else {
        os << LogIO::NORMAL << "Determined noise from Q&U images to be ";
        auto sq = _sigma(ImagePolarimetry::Q, clip);
        auto su = _sigma(ImagePolarimetry::U, clip);
        sigma2 = (sq+su)/2.0;
    }
    return sigma2;
}

ImageExpr<Float> ImagePolarimetry::linPolPosAng(Bool radians) const {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    ThrowIf(
        ! _stokes[ImagePolarimetry::Q] && ! _stokes[ImagePolarimetry::U],
        "This image does not have Stokes Q and U so cannot "
        "provide linear polarization"
    );
    _checkQUBeams(false);
    // Make expression. LEL function "pa" returns degrees
    Float fac = radians ? C::pi / 180.0 : 1.0;
    LatticeExprNode node(
        fac*pa(*_stokes[ImagePolarimetry::U], *_stokes[ImagePolarimetry::Q])
    );
    LatticeExpr<Float> le(node);
    ImageExpr<Float> ie(le, String("LinearlyPolarizedPositionAngle"));
    ie.setUnits(Unit(radians ? "rad" : "deg"));
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    ie.setImageInfo(ii);
    _fiddleStokesCoordinate(ie, Stokes::Pangle);
    return ie;
}

ImageExpr<Float> ImagePolarimetry::sigmaLinPolPosAng(
    Bool radians, Float clip, Float sigma
) {
    // sigma_PA = sigmaQU / 2P
    ThrowIf(
        ! (_stokes[ImagePolarimetry::Q] || _stokes[ImagePolarimetry::U]),
        "This image does not have Stokes Q and U so "
        "cannot provide linear polarization"
    );
    _checkQUBeams(false);
    Float sigma2 = sigma > 0 ? sigma : this->sigma(clip);
    Float fac = 0.5 * sigma2;
    if (! radians) {
        fac *= 180 / C::pi;
    }
    LatticeExprNode node(
        fac / amp(*_stokes[ImagePolarimetry::U], *_stokes[ImagePolarimetry::Q])
    );
    LatticeExpr<Float> le(node);
    ImageExpr<Float> ie(le, String("LinearlyPolarizedPositionAngleError"));
    ie.setUnits(Unit(radians ? "rad" : "deg"));
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    ie.setImageInfo(ii);
    _fiddleStokesCoordinate(ie, Stokes::Pangle);
    return ie;
}

Float ImagePolarimetry::sigma(Float clip) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    Float sigma2 = 0.0;
    if (_stokes[ImagePolarimetry::V]) {
        os << LogIO::NORMAL << "Determined noise from V image to be ";
        sigma2 = _sigma(ImagePolarimetry::V, clip);
    }
    else if (
        _stokes[ImagePolarimetry::Q] && _stokes[ImagePolarimetry::U]
        && _checkQUBeams(false, false)
    ) {
        sigma2 = sigmaLinPolInt(clip);
    }
    else if (_stokes[ImagePolarimetry::Q]) {
        os << LogIO::NORMAL << "Determined noise from Q image to be "
            << LogIO::POST;
        sigma2 = _sigma(ImagePolarimetry::Q, clip);
    }
    else if (_stokes[ImagePolarimetry::U]) {
        os << LogIO::NORMAL << "Determined noise from U image to be "
            << LogIO::POST;
        sigma2 = _sigma(ImagePolarimetry::U, clip);
    }
    else if (_stokes[ImagePolarimetry::I]!=0) {
        os << LogIO::NORMAL << "Determined noise from I image to be "
            << LogIO::POST;
        sigma2 = _sigma(ImagePolarimetry::I, clip);
    }
    os << sigma2 << LogIO::POST;
    return sigma2;
}

void ImagePolarimetry::rotationMeasure(
    ImageInterface<Float>*& rmOutPtr, ImageInterface<Float>*& rmOutErrorPtr,
    ImageInterface<Float>*& pa0OutPtr, ImageInterface<Float>*& pa0OutErrorPtr,
    ImageInterface<Float>*& nTurnsOutPtr, ImageInterface<Float>*& chiSqOutPtr,
    Int axis,  Float rmMax, Float maxPaErr, Float sigma, Float rmFg,
    Bool showProgress
) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    _hasQU();
    _checkQUBeams(false);
    // Do we have anything to do ?
    ThrowIf(
        ! (rmOutPtr || rmOutErrorPtr || pa0OutPtr || pa0OutErrorPtr),
        "No output images specified"
    );
    // Find expected shape of output RM images (Stokes and spectral axes gone)
    CoordinateSystem cSysRM;
    Int fAxis, sAxis;
    const auto shapeRM = rotationMeasureShape(cSysRM, fAxis, sAxis, os, axis);
    const auto shapeNTurns = shapeRM;
    const auto shapeChiSq = shapeRM;
    // Check RM image shapes
    if (rmOutPtr && ! rmOutPtr->shape().isEqual(shapeRM)) {
        os << "The provided Rotation Measure image has the wrong shape "
             << rmOutPtr->shape() << endl;
        os << "It should be of shape " << shapeRM << LogIO::EXCEPTION;
    }
    if (rmOutErrorPtr && !rmOutErrorPtr->shape().isEqual(shapeRM)) {
        os << "The provided Rotation Measure error image has the wrong shape "
            << rmOutErrorPtr->shape() << endl;
        os << "It should be of shape " << shapeRM << LogIO::EXCEPTION;
    }
    // Check position angle image shapes
    CoordinateSystem cSysPA;
    const auto shapePA = positionAngleShape(cSysPA, fAxis, sAxis, os, axis);
    if (pa0OutPtr && ! pa0OutPtr->shape().isEqual(shapePA)) {
        os << "The provided position angle at zero wavelength image has the "
            << "wrong shape " << pa0OutPtr->shape() << endl;
        os << "It should be of shape " << shapePA << LogIO::EXCEPTION;
    }
    if (pa0OutErrorPtr && ! pa0OutErrorPtr->shape().isEqual(shapePA)) {
        os << "The provided position angle at zero wavelength image has the "
            << "wrong shape " << pa0OutErrorPtr->shape() << endl;
        os << "It should be of shape " << shapePA << LogIO::EXCEPTION;
    }
    if (nTurnsOutPtr && ! nTurnsOutPtr->shape().isEqual(shapeNTurns)) {
        os << "The provided nTurns image has the wrong shape "
            << nTurnsOutPtr->shape() << endl;
        os << "It should be of shape " << shapeNTurns << LogIO::EXCEPTION;
    }
    if (chiSqOutPtr && ! chiSqOutPtr->shape().isEqual(shapeChiSq)) {
        os << "The provided chi squared image has the wrong shape "
            << chiSqOutPtr->shape() << endl;
        os << "It should be of shape " << shapeChiSq << LogIO::EXCEPTION;
    }
    // Generate linear polarization position angle image expressions
    // and error in radians
    Bool radians = true;
    Float clip = 10.0;
    const auto pa = linPolPosAng(radians);
    const auto paerr = sigmaLinPolPosAng(radians, clip, sigma);
    CoordinateSystem cSys0 = pa.coordinates();
    // Set frequency axis units to Hz
    auto fAxisWorld = cSys0.pixelAxisToWorldAxis(fAxis);
    ThrowIf(
        fAxisWorld < 0,
        "World axis has been removed for the frequency pixel axis"
    );
    auto axisUnits = cSys0.worldAxisUnits();
    axisUnits(fAxisWorld) = String("Hz");
    ThrowIf(
        ! cSys0.setWorldAxisUnits(axisUnits),
        "Failed to set frequency axis units to Hz because "
        + cSys0.errorMessage()
    );
    // Do we have enough frequency pixels ?
    const uInt nFreq = pa.shape()(fAxis);
    ThrowIf(
        nFreq < 3,
        "This image only has " + String::toString(nFreq)
        + " frequencies, this is not enough"
    );
    // Copy units only over.  The output images don't have a beam
    // so unset beam.   MiscInfo and history require writable II.
    // We leave this to the caller  who knows what sort of II these are.
    auto ii = _image->imageInfo();
    ii.removeRestoringBeam();
    if (rmOutPtr) {
        rmOutPtr->setImageInfo(ii);
        rmOutPtr->setUnits(Unit("rad/m/m"));
    }
    if (rmOutErrorPtr) {
        rmOutErrorPtr->setImageInfo(ii);
        rmOutErrorPtr->setUnits(Unit("rad/m/m"));
    }
    if (pa0OutPtr) {
        pa0OutPtr->setImageInfo(ii);
        pa0OutPtr->setUnits(Unit("deg"));
    }
    if (pa0OutErrorPtr) {
        pa0OutErrorPtr->setImageInfo(ii);
        pa0OutErrorPtr->setUnits(Unit("deg"));
    }
    if (nTurnsOutPtr) {
        nTurnsOutPtr->setImageInfo(ii);
        nTurnsOutPtr->setUnits(Unit(""));
    }
    if (chiSqOutPtr) {
        chiSqOutPtr->setImageInfo(ii);
        chiSqOutPtr->setUnits(Unit(""));
    }
    // Get lambda squared in m**2
    Vector<Double> freqs(nFreq);
    Vector<Float> wsq(nFreq);
    Vector<Double> world;
    Vector<Double> pixel(cSys0.referencePixel().copy());
    Double c = QC::c( ).getValue(Unit("m/s"));
    Double csq = c*c;
    for (uInt i=0; i<nFreq; ++i) {
        pixel(fAxis) = i;
        ThrowIf(
            !cSys0.toWorld(world, pixel),
            "Failed to convert pixel to world because "
            + cSys0.errorMessage()
        );
        freqs(i) = world(fAxisWorld);
        // m**2
        wsq(i) = csq / freqs(i) / freqs(i);
    }
    // Sort into increasing wavelength
    Vector<uInt> sortidx;
    GenSortIndirect<Float>::sort(
        sortidx, wsq, Sort::Ascending, Sort::QuickSort|Sort::NoDuplicates
    );
    Vector<Float> wsqsort(sortidx.size());
    for (uInt i=0; i<wsqsort.size(); ++i) {
        wsqsort[i] = wsq[sortidx[i]];
    }
    // Make fitter
    if (! _fitter) {
        _fitter = new LinearFitSVD<Float>;
        // Create and set the polynomial functional
        // p = c(0) + c(1)*x where x = lambda**2
        // PA = PA0 + RM*Lambda**2
        Polynomial<AutoDiff<Float> > poly1(1);
        // Makes a copy of poly1
        _fitter->setFunction(poly1);
    }
    // Deal with masks.  The outputs are all given a mask if possible as we
    // don't know at this point whether output points will be masked or not
    IPosition whereRM;
    auto isMaskedRM = false;
    Lattice<Bool>* outRMMaskPtr = nullptr;
    if (rmOutPtr) {
        isMaskedRM = _dealWithMask (outRMMaskPtr, rmOutPtr, os, "RM");
        whereRM.resize(rmOutPtr->ndim());
        whereRM = 0;
    }
    auto isMaskedRMErr = false;
    Lattice<Bool>* outRMErrMaskPtr = nullptr;
    if (rmOutErrorPtr) {
        isMaskedRMErr = _dealWithMask(
            outRMErrMaskPtr, rmOutErrorPtr, os, String("RM error")
        );
        whereRM.resize(rmOutErrorPtr->ndim());
        whereRM = 0;
    }
    IPosition wherePA;
    auto isMaskedPa0 = false;
    Lattice<Bool>* outPa0MaskPtr = nullptr;
    if (pa0OutPtr) {
        isMaskedPa0 = _dealWithMask(
            outPa0MaskPtr, pa0OutPtr, os, String("Position Angle")
        );
        wherePA.resize(pa0OutPtr->ndim());
        wherePA = 0;
    }
    auto isMaskedPa0Err = false;
    Lattice<Bool>* outPa0ErrMaskPtr = nullptr;
    if (pa0OutErrorPtr) {
        isMaskedPa0Err = _dealWithMask(
            outPa0ErrMaskPtr, pa0OutErrorPtr, os, "Position Angle error"
        );
        wherePA.resize(pa0OutErrorPtr->ndim());
        wherePA = 0;
    }
    IPosition whereNTurns;
    auto isMaskedNTurns = false;
    Lattice<Bool>* outNTurnsMaskPtr = 0;
    if (nTurnsOutPtr) {
        isMaskedNTurns = _dealWithMask(
            outNTurnsMaskPtr, nTurnsOutPtr, os, "nTurns"
        );
        whereNTurns.resize(nTurnsOutPtr->ndim());
        whereNTurns = 0;
    }
    IPosition whereChiSq;
    auto isMaskedChiSq = false;
    Lattice<Bool>* outChiSqMaskPtr = nullptr;
    if (chiSqOutPtr) {
        isMaskedChiSq = _dealWithMask(
            outChiSqMaskPtr, chiSqOutPtr, os, String("chi sqared")
        );
        whereChiSq.resize(chiSqOutPtr->ndim());
        whereChiSq = 0;
    }
    Array<Bool> tmpMaskRM(IPosition(shapeRM.size(), 1), true);
    Array<Float> tmpValueRM(IPosition(shapeRM.size(), 1), 0.0f);
    Array<Bool> tmpMaskPA(IPosition(shapePA.size(), 1), true);
    Array<Float> tmpValuePA(IPosition(shapePA.size(), 1), 0.0f);
    Array<Float> tmpValueNTurns(IPosition(shapeNTurns.size(), 1), 0.0f);
    Array<Bool> tmpMaskNTurns(IPosition(shapeNTurns.size(), 1), true);
    Array<Float> tmpValueChiSq(IPosition(shapeChiSq.size(), 1), 0.0f);
    Array<Bool> tmpMaskChiSq(IPosition(shapeChiSq.size(), 1), true);
    // Iterate
    const IPosition tileShape = pa.niceCursorShape();
    TiledLineStepper ts(pa.shape(), tileShape, fAxis);
    RO_MaskedLatticeIterator<Float> it(pa, ts);
    Float rm, rmErr, pa0, pa0Err, rChiSq, nTurns;
    uInt j, k, l, m;
    static const Double degPerRad = 180/C::pi;
    maxPaErr /= degPerRad;
    maxPaErr = abs(maxPaErr);
    Bool doRM = whereRM.size() > 0;
    Bool doPA = wherePA.size() > 0;
    Bool doNTurns = whereNTurns.size() > 0;
    Bool doChiSq = whereChiSq.size() > 0;
    unique_ptr<ProgressMeter> pProgressMeter;
    if (showProgress) {
        Double nMin = 0.0;
        Double nMax = 1.0;
        for (Int i=0; i<Int(pa.ndim()); ++i) {
            if (i!=fAxis) {
                nMax *= pa.shape()[i];
            }
        }
        pProgressMeter.reset(
            new ProgressMeter(
                nMin, nMax, "Profiles fitted", "Fitting",
                "", "", true, max(1,Int(nMax/100))
            )
        );
    }
    // As a (temporary?) workaround the cache of the main image is set up in
    // such a way that it can hold the full frequency and stokes axes.
    // The stokes axis is important, otherwise the cache is set up
    // (by the TiledStMan) such that it can hold only 1 stokes
    // with the result that iterating is tremendously slow.
    // We also need to cast the const away from _image.
    const IPosition mainShape = _image->shape();
    uInt nrtiles = (1 + (mainShape(fAxis)-1) / tileShape(fAxis)) *
            (1 + (mainShape(sAxis)-1) / tileShape(sAxis));
    auto* mainImagePtr = const_cast<ImageInterface<Float>*>(_image.get());
    mainImagePtr->setCacheSizeInTiles (nrtiles);
    String posString;
    auto ok = false;
    IPosition shp;
    for (it.reset(); ! it.atEnd(); it++) {
        // Find rotation measure for this line
        ok = _findRotationMeasure(
            rm, rmErr, pa0, pa0Err, rChiSq, nTurns, sortidx, wsqsort,
            it.vectorCursor(), it.getMask(false),
            paerr.getSlice(it.position(),it.cursorShape()),
            rmFg, rmMax, maxPaErr, posString
        );
        // Plonk values into output  image.  This is slow and clunky, but
        // should be relatively fast c.f. the fitting.  Could be reimplemented
        // with LatticeApply if need be.  Buffering is hard because the
        // navigator doesn't take a regular path.  If I used a LatticeStepper
        // instead, the path would be regular and then I could buffer, but then
        // the iteration would be less efficient !!!
        j = k = l = m = 0;
        for (Int i=0; i<Int(it.position().size()); ++i) {
            if (doRM && i != fAxis && i != sAxis) {
                whereRM(j) = it.position()[i];
                ++j;
            }
            if (doPA && i != fAxis) {
                wherePA(k) = it.position()[i];
                ++k;
            }
            if (doNTurns && i != fAxis && i != sAxis) {
                whereNTurns[l] = it.position()[i];
                ++l;
            }
            if (doChiSq && i != fAxis && i != sAxis) {
                whereChiSq[m] = it.position()[i];
                ++m;
            }
        }
        if (isMaskedRM) {
            tmpMaskRM.set(ok);
            outRMMaskPtr->putSlice(tmpMaskRM, whereRM);
        }
        if (isMaskedRMErr) {
            tmpMaskRM.set(ok);
            outRMErrMaskPtr->putSlice(tmpMaskRM, whereRM);
        }
        if (isMaskedPa0) {
            tmpMaskPA.set(ok);
            outPa0MaskPtr->putSlice(tmpMaskPA, wherePA);
        }
        if (isMaskedPa0Err) {
            tmpMaskPA.set(ok);
            outPa0ErrMaskPtr->putSlice(tmpMaskPA, wherePA);
        }
        if (isMaskedNTurns) {
            tmpMaskNTurns.set(ok);
            outNTurnsMaskPtr->putSlice(tmpMaskNTurns, whereNTurns);
        }
        if (isMaskedChiSq) {
            tmpMaskChiSq.set(ok);
            outChiSqMaskPtr->putSlice(tmpMaskChiSq, whereChiSq);
        }
        // If the output value is masked, the value itself is 0
        if (rmOutPtr) {
            tmpValueRM.set(rm);
            rmOutPtr->putSlice(tmpValueRM, whereRM);
        }
        if (rmOutErrorPtr) {
            tmpValueRM.set(rmErr);
            rmOutErrorPtr->putSlice(tmpValueRM, whereRM);
        }
        // Position angles in degrees
        if (pa0OutPtr) {
            tmpValuePA.set(pa0*degPerRad);
            pa0OutPtr->putSlice(tmpValuePA, wherePA);
        }
        if (pa0OutErrorPtr) {
            tmpValuePA.set(pa0Err*degPerRad);
            pa0OutErrorPtr->putSlice(tmpValuePA, wherePA);
        }
        // Number of turns and chi sq
        if (nTurnsOutPtr) {
            tmpValueNTurns.set(nTurns);
            nTurnsOutPtr->putSlice(tmpValueNTurns, whereNTurns);
        }
        if (chiSqOutPtr) {
            tmpValueChiSq.set(rChiSq);
            chiSqOutPtr->putSlice(tmpValueChiSq, whereChiSq);
        }
        if (showProgress) {
            pProgressMeter->update(Double(it.nsteps()));
        }
    }
    // Clear the cache of the main image again.
    mainImagePtr->clearCache();
}

IPosition ImagePolarimetry::rotationMeasureShape(
    CoordinateSystem& cSys, Int& fAxis, Int& sAxis, LogIO&, Int spectralAxis
) const {
    const auto cSys0 = coordinates();
    Int spectralCoord;
    _findFrequencyAxis(spectralCoord, fAxis, cSys0, spectralAxis);
    Int afterCoord = -1;
    auto stokesCoord = cSys0.findCoordinate(Coordinate::STOKES, afterCoord);
    auto pixelAxes = cSys0.pixelAxes(stokesCoord);
    sAxis = pixelAxes(0);
    // What shape should the image be ?  Frequency and stokes axes should be gone.
    const auto shape0 = shape();
    IPosition shape(shape0.size()-2);
    Int j = 0;
    for (Int i=0; i<Int(shape0.size()); ++i) {
        if (i != fAxis && i != sAxis) {
            shape[j] = shape0[i];
            ++j;
        }
    }
    CoordinateSystem tmp;
    cSys = tmp;
    for (Int i=0; i<Int(cSys0.nCoordinates()); ++i) {
        if (i != spectralCoord && i != stokesCoord) {
            cSys.addCoordinate(cSys0.coordinate(i));
        }
    }
    return shape;
}

IPosition ImagePolarimetry::positionAngleShape(
    CoordinateSystem& cSys,  Int& fAxis, Int& sAxis, LogIO&, Int spectralAxis
) const {
    CoordinateSystem cSys0 = coordinates();
    Int spectralCoord = -1;
    _findFrequencyAxis (spectralCoord, fAxis, cSys0, spectralAxis);
    Int afterCoord = -1;
    Int stokesCoord = cSys0.findCoordinate(Coordinate::STOKES, afterCoord);
    Vector<Int> pixelAxes = cSys0.pixelAxes(stokesCoord);
    sAxis = pixelAxes(0);
    _fiddleStokesCoordinate(cSys0, Stokes::Pangle);
    CoordinateSystem tmp;
    cSys = tmp;
    for (Int i=0; i<Int(cSys0.nCoordinates()); ++i) {
       if (i != spectralCoord) {
           cSys.addCoordinate(cSys0.coordinate(i));
       }
    }
    // What shape should the image be ?  Frequency axis should be gone.
    // and Stokes length 1
    const auto shape0 = ImagePolarimetry::shape();
    IPosition shape(shape0.size()-1);
    Int j = 0;
    for (Int i=0; i<Int(shape0.size()); ++i) {
        if (i == sAxis) {
            shape[j] = 1;
            ++j;
        }
        else {
            if (i != fAxis) {
                shape[j] = shape0[i];
                ++j;
            }
        }
    }
    return shape;
}

ImageExpr<Float> ImagePolarimetry::stokesI() const {
   return _makeStokesExpr(
       _stokes[ImagePolarimetry::I], Stokes::I, "StokesI"
   );
}

Float ImagePolarimetry::sigmaStokesI(Float clip) {
   return _sigma(ImagePolarimetry::I, clip);
}

ImageExpr<Float> ImagePolarimetry::stokesQ() const {
   return _makeStokesExpr(_stokes[ImagePolarimetry::Q], Stokes::Q, "StokesQ");
}

Float ImagePolarimetry::sigmaStokesQ(Float clip) {
   return _sigma(ImagePolarimetry::Q, clip);
}

ImageExpr<Float> ImagePolarimetry::stokesU() const {
   return _makeStokesExpr(_stokes[ImagePolarimetry::U], Stokes::U, "StokesU");
}

Float ImagePolarimetry::sigmaStokesU(Float clip) {
   return _sigma(ImagePolarimetry::U, clip);
}

ImageExpr<Float> ImagePolarimetry::stokesV() const {
   return _makeStokesExpr(_stokes[ImagePolarimetry::V], Stokes::V, "StokesV");
}

Float ImagePolarimetry::sigmaStokesV(Float clip) {
   return _sigma(ImagePolarimetry::V, clip);
}

ImageExpr<Float> ImagePolarimetry::stokes(
    ImagePolarimetry::StokesTypes stokes
) const {
   const auto type = _stokesType(stokes);
   return _makeStokesExpr(_stokes[stokes], type, _stokesName(stokes));
}

Float ImagePolarimetry::sigmaStokes(
    ImagePolarimetry::StokesTypes stokes, Float clip
) {
   return _sigma(stokes, clip);
}

void ImagePolarimetry::summary(LogIO& os) const {
   ImageSummary<Float> s(*_image);
   s.list(os);
}

ImageExpr<Float> ImagePolarimetry::totPolInt(
    Bool debias, Float clip, Float sigma
) {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    Bool doLin, doCirc;
    _setDoLinDoCirc(doLin, doCirc);
    auto node = _makePolIntNode(os, debias, clip, sigma, doLin, doCirc);
    LatticeExpr<Float> le(node);
    ImageExpr<Float> ie(le, String("totalPolarizedIntensity"));
    // Dodgy. The beam is now rectified
    ie.setUnits(_image->units());
    StokesTypes stokes = _stokes[Q] ? Q : _stokes[U] ? U : V;
    _setInfo(ie, stokes);
    _fiddleStokesCoordinate(ie, Stokes::Ptotal);
    return ie;
}

Float ImagePolarimetry::sigmaTotPolInt(Float clip, Float sigma) {
    // sigma_P = sigma_QUV
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    Bool doLin, doCirc;
    _setDoLinDoCirc(doLin, doCirc);
    Float sigma2 = sigma > 0 ? sigma : this->sigma(clip);
    return sigma2;
}


IPosition ImagePolarimetry::singleStokesShape(
    CoordinateSystem& cSys, Stokes::StokesTypes type
) const {
    // We know the image has a Stokes coordinate or it
    // would have failed at construction
    auto cSys0 = _image->coordinates();
    _fiddleStokesCoordinate(cSys0, type);
    cSys = cSys0;
    Int afterCoord = -1;
    const auto iStokes = cSys0.findCoordinate(Coordinate::STOKES, afterCoord);
    const auto pixelAxes = cSys0.pixelAxes(iStokes);
    auto shape = _image->shape();
    shape[pixelAxes[0]] = 1;
    return shape;
}

ImageExpr<Float> ImagePolarimetry::depolarizationRatio(
    const ImageInterface<Float>& im1, const ImageInterface<Float>& im2,
    Bool debias, Float clip, Float sigma
) {
    ImagePolarimetry p1(im1);
    ImagePolarimetry p2(im2);
    ImageExpr<Float> m1(p1.fracLinPol(debias, clip, sigma));
    ImageExpr<Float> m2(p2.fracLinPol(debias, clip, sigma));
    LatticeExprNode n1(m1/m2);
    LatticeExpr<Float> le(n1);
    ImageExpr<Float> depol(le, "DepolarizationRatio");
    return depol;
}

ImageExpr<Float> ImagePolarimetry::sigmaDepolarizationRatio(
    const ImageInterface<Float>& im1, const ImageInterface<Float>& im2,
    Bool debias, Float clip, Float sigma
) {
    Vector<StokesTypes> types(3);
    types[0] = I;
    types[1] = Q;
    types[2] = U;
    _checkBeams(im1, im2, types);
    ImagePolarimetry p1(im1);
    ImagePolarimetry p2(im2);
    ImageExpr<Float> m1 = p1.fracLinPol(debias, clip, sigma);
    ImageExpr<Float> sm1 = p1.sigmaFracLinPol(clip, sigma);
    ImageExpr<Float> m2 = p2.fracLinPol(debias, clip, sigma);
    ImageExpr<Float> sm2 = p2.sigmaFracLinPol(clip, sigma);
    LatticeExprNode n0(m1/m2);
    LatticeExprNode n1(sm1*sm1/m1/m1);
    LatticeExprNode n2(sm2*sm2/m2/m2);
    LatticeExprNode n3(n0 * sqrt(n1+n2));
    LatticeExpr<Float> le(n3);
    ImageExpr<Float> sigmaDepol(le, "DepolarizationRatioError");
     return sigmaDepol;
}

void ImagePolarimetry::_cleanup() {
    _image.reset();
    for (uInt i=0; i<4; ++i) {
        delete _stokes[i];
        _stokes[i] = nullptr;
        delete _stokesStats[i];
        _stokesStats[i] = nullptr;
    }
    delete _fitter;
    _fitter = nullptr;
}

void ImagePolarimetry::_findFrequencyAxis(
    Int& spectralCoord, Int& fAxis, const CoordinateSystem& cSys,
    Int spectralAxis
) const {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    spectralCoord = -1;
    fAxis = -1;
    if (spectralAxis >= 0) {
        ThrowIf(
            spectralAxis >= Int(cSys.nPixelAxes()),
            "Illegal spectral axis " + String::toString(spectralAxis) +" given"
        );
        fAxis = spectralAxis;
        Int axisInCoordinate;
        cSys.findPixelAxis(spectralCoord, axisInCoordinate, fAxis);
        // Check coordinate type is one of expected types
        ThrowIf(
            ! (
                cSys.type(spectralCoord)==Coordinate::TABULAR
                || cSys.type(spectralCoord)==Coordinate::LINEAR
                || cSys.type(spectralCoord)==Coordinate::SPECTRAL
            ),
            "The specified axis of type " + cSys.showType(spectralCoord)
            + " cannot be a frequency axis"
        );
    }
    else {
        spectralCoord = _findSpectralCoordinate(cSys, os, false);
        if (spectralCoord < 0) {
            for (uInt i=0; i<cSys.nCoordinates(); ++i) {
                if (
                    cSys.type(i)==Coordinate::TABULAR
                    || cSys.type(i)==Coordinate::LINEAR
                ) {
                    const auto axisNames = cSys.coordinate(i).worldAxisNames();
                    String tmp = axisNames[0];
                    tmp.upcase();
                    if (tmp.contains(String("FREQ"))) {
                        spectralCoord = i;
                        break;
                    }
                }
            }
        }
        ThrowIf(
            spectralCoord < 0,
            "Cannot find SpectralCoordinate in this image"
        );
        fAxis = cSys.pixelAxes(spectralCoord)[0];
    }
}

void ImagePolarimetry::_findStokes() {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    const CoordinateSystem& cSys = _image->coordinates();
    Int polAxisNum = cSys.polarizationAxisNumber();
    ThrowIf(
        polAxisNum < 0, "There is no Stokes Coordinate in this image"
    );
    const uInt ndim = _image->ndim();
    auto shape = _image->shape();
    IPosition blc(ndim,0);
    auto trc = shape - 1;
    for (const auto& kv : polMap) {
        const auto pix = cSys.stokesPixelNumber(kv.second);
        if (pix >= 0) {
            _stokes[kv.first] = _makeSubImage(blc, trc, polAxisNum, pix);
        }
    }
    if((_stokes[Q] && ! _stokes[U]) || (! _stokes[Q] && _stokes[U])) {
        _cleanup();
        ThrowCc(
            "This Stokes coordinate has only one of Q and U. This is not useful"
        );
    }
    if (! (_stokes[Q] || _stokes[U] || _stokes[V])) {
       _cleanup();
       ThrowCc("This image has no Stokes Q, U, nor V.  This is not useful");
    }
}

void ImagePolarimetry::_fiddleStokesCoordinate(
    ImageInterface<Float>& im, Stokes::StokesTypes type
) const {
    CoordinateSystem cSys = im.coordinates();
    _fiddleStokesCoordinate(cSys, type);
    im.setCoordinateInfo(cSys);
}

void ImagePolarimetry::_fiddleStokesCoordinate(
    CoordinateSystem& cSys, Stokes::StokesTypes type
) const {
    Int afterCoord = -1;
    Int iStokes = cSys.findCoordinate(Coordinate::STOKES, afterCoord);
    const Vector<Int> which(1, Int(type));
    StokesCoordinate stokes(which);
    cSys.replaceCoordinate(stokes, iStokes);
}

void ImagePolarimetry::_fiddleStokesCoordinate(
    ImageInterface<Complex>& ie, Stokes::StokesTypes type
) const {
    CoordinateSystem cSys = ie.coordinates();
    _fiddleStokesCoordinate(cSys, type);
    ie.setCoordinateInfo(cSys);
}

void ImagePolarimetry::_fiddleTimeCoordinate(
    ImageInterface<Complex>& ie, const Quantum<Double>& f, Int coord
) const {
    LogIO os(LogOrigin("ImagePolarimetry", __func__, WHERE));
    CoordinateSystem cSys = ie.coordinates();
    unique_ptr<Coordinate> pC(cSys.coordinate(coord).clone());
    AlwaysAssert(pC->nPixelAxes()==1,AipsError);
    AlwaysAssert(pC->type()==Coordinate::LINEAR,AipsError);
    auto axisUnits = pC->worldAxisUnits();
    axisUnits = String("s");
    ThrowIf(
        ! pC->setWorldAxisUnits(axisUnits),
        "Failed to set TimeCoordinate units to seconds because "
        + pC->errorMessage()
    );
    // Find factor to convert from time (s) to rad/m/m
    auto inc = pC->increment();
    const auto ff = f.getValue(Unit("Hz"));
    const auto lambda = QC::c( ).getValue(Unit("m/s")) / ff;
    const auto fac = -C::pi * ff / 2.0 / lambda / lambda;
    inc *= fac;
    Vector<String> axisNames(1);
    axisNames = String("RotationMeasure");
    axisUnits = String("rad/m/m");
    Vector<Double> refVal(1,0.0);
    LinearCoordinate lC(
        axisNames, axisUnits, refVal, inc,
        pC->linearTransform().copy(), pC->referencePixel().copy()
    );
    cSys.replaceCoordinate(lC, coord);
    ie.setCoordinateInfo(cSys);
}

Quantum<Double> ImagePolarimetry::_findCentralFrequency(
    const Coordinate& coord, Int shape
) const {
    AlwaysAssert(coord.nPixelAxes()==1,AipsError);
    Vector<Double> pixel(1);
    Vector<Double> world;
    pixel(0) = Double(shape - 1) / 2.0;
    ThrowIf(
        ! coord.toWorld(world, pixel),
        "Failed to convert pixel to world for SpectralCoordinate because "
        + coord.errorMessage()
    );
    const auto units = coord.worldAxisUnits();
    return Quantum<Double>(world(0), units(0));
}

Int ImagePolarimetry::_findSpectralCoordinate(
    const CoordinateSystem& cSys, LogIO& os, Bool fail
) const {
    Int afterCoord = -1;
    Int coord = cSys.findCoordinate(Coordinate::SPECTRAL, afterCoord);
    ThrowIf(coord < 0 && fail, "No spectral coordinate in this image");
    if (afterCoord>0) {
        os << LogIO::WARN << "This image has more than one spectral "
            << "coordinate; only first used" << LogIO::POST;
    }
    return coord;
}

Bool ImagePolarimetry::_findRotationMeasure(
    Float& rmFitted, Float& rmErrFitted, Float& pa0Fitted, Float& pa0ErrFitted,
    Float& rChiSqFitted, Float& nTurns, const Vector<uInt>& sortidx,
    const Vector<Float>& wsq2, const Vector<Float>& pa2,
    const Array<Bool>& paMask2, const Array<Float>& paerr2, Float rmFg,
    Float rmMax, Float maxPaErr, const String& posString
) {
    // wsq is lambda squared in m**2 in increasing wavelength order
    // pa is position angle in radians
    // paerr is pa error in radians
    // maxPaErr is maximum tolerated error in position angle
    // rmfg is a user specified foreground RM rad/m/m
    // rmmax is a user specified maximum RM
    static Vector<Float> paerr;
    static Vector<Float> pa;
    static Vector<Float> wsq;
    // Abandon if less than 2 points
    uInt n = sortidx.size();
    if (n<2) {
        return false;
    }
    rmFitted = rmErrFitted = pa0Fitted = pa0ErrFitted = rChiSqFitted = 0.0;
    // Sort into decreasing frequency order and correct for foreground rotation
    // Remember wsq already sorted.  Discard points that are too noisy or masked
    const Vector<Float>& paerr1(paerr2.nonDegenerate(0));
    const Vector<Bool>& paMask1(paMask2.nonDegenerate(0));
    paerr.resize(n);
    pa.resize(n);
    wsq.resize(n);
    uInt j = 0;
    for (uInt i=0; i<n; ++i) {
        if (abs(paerr1(sortidx(i))) < maxPaErr && paMask1(sortidx(i))) {
            pa(j) = pa2(sortidx(i)) - rmFg*wsq2(i);
            paerr(j) = paerr1(sortidx(i));
            wsq(j) = wsq2(i);
            ++j;
        }
    }
    n = j;
    if (n<=1) {
        return false;
    }
    pa.resize(n, true);
    paerr.resize(n, true);
    wsq.resize(n, true);
    // Treat supplementary and primary points separately
    Bool ok = n == 2
        ? _rmSupplementaryFit(
            nTurns, rmFitted, rmErrFitted, pa0Fitted, pa0ErrFitted,
            rChiSqFitted, wsq, pa, paerr
        )
        : _rmPrimaryFit(
            nTurns, rmFitted, rmErrFitted, pa0Fitted, pa0ErrFitted,
            rChiSqFitted, wsq, pa, paerr, rmMax, /*plotter,*/ posString
        );
    // Put position angle into the range 0->pi
    static MVAngle tmpMVA1;
    if (ok) {
        MVAngle tmpMVA0(pa0Fitted);
        tmpMVA1 = tmpMVA0.binorm(0.0);
        pa0Fitted = tmpMVA1.radian();
        // Add foreground back on
        rmFitted += rmFg;
    }
    return ok;
}

void ImagePolarimetry::_hasQU () const {
   ThrowIf(
       ! (_stokes[Q] && _stokes[U]),
       "This image does not have Stokes Q and U which are "
       "required for this function"
   );
}

ImageExpr<Float> ImagePolarimetry::_makeStokesExpr(
    ImageInterface<Float>* imPtr, Stokes::StokesTypes type, const String& name
) const {
    ThrowIf(! imPtr, "This image does not have Stokes " + Stokes::name(type));
    LatticeExprNode node(*imPtr);
    LatticeExpr<Float> le(node);
    ImageExpr<Float> ie(le, name);
    ie.setUnits(_image->units());
    _fiddleStokesCoordinate(ie, type);
    return ie;
}

ImageInterface<Float>* ImagePolarimetry::_makeSubImage(
    IPosition& blc, IPosition& trc, Int axis, Int pix
) const {
    blc[axis] = pix;
    trc[axis] = pix;
    LCSlicer slicer(blc, trc, RegionType::Abs);
    ImageRegion region(slicer);
    return new SubImage<Float>(*_image, region);
}

LatticeExprNode ImagePolarimetry::_makePolIntNode(
    LogIO& os, Bool debias, Float clip, Float sigma, Bool doLin, Bool doCirc
) {
    LatticeExprNode linNode, circNode, node;
    Float sigma2 = debias ? (sigma > 0.0 ? sigma : this->sigma(clip)) : 0.0;
    if (doLin) {
        linNode = LatticeExprNode(pow(*_stokes[U], 2) + pow(*_stokes[Q], 2));
    }
    if (doCirc) {
        circNode = LatticeExprNode(pow(*_stokes[V], 2));
    }
    Float sigmasq = sigma2 * sigma2;
    if (doLin && doCirc) {
        node = linNode + circNode;
        if (debias) {
            node = node - LatticeExprNode(sigmasq);
            os << LogIO::NORMAL << "Debiasing with sigma = " << sqrt(sigmasq)
                << LogIO::POST;
        }
    }
    else if (doLin) {
        node = linNode;
        if (debias) {
            node = node - LatticeExprNode(sigmasq);
            os << LogIO::NORMAL << "Debiasing with sigma  = " << sqrt(sigmasq)
                << LogIO::POST;
        }
    }
    else if (doCirc) {
        node = circNode;
        if (debias) {
            node = node - LatticeExprNode(sigmasq);
            os << LogIO::NORMAL << "Debiasing with sigma = " << sqrt(sigmasq)
                << LogIO::POST;
        }
    }
    return LatticeExprNode(sqrt(node));
}

Bool ImagePolarimetry::_rmPrimaryFit(
    Float& nTurns, Float& rmFitted, Float& rmErrFitted, Float& pa0Fitted,
    Float& pa0ErrFitted, Float& rChiSqFitted, const Vector<Float>& wsq,
    const Vector<Float>& pa, const Vector<Float>& paerr, Float rmMax,
    const String&
) {
    static Vector<Float> plotPA;
    static Vector<Float> plotPAErr;
    static Vector<Float> plotPAErrY1;
    static Vector<Float> plotPAErrY2;
    static Vector<Float> plotPAFit;
    // Assign position angle to longest wavelength consistent with RM < RMMax
    const uInt n = wsq.size();
    Double dwsq = wsq(n-1) - wsq(0);
    Float ppa = abs(rmMax)*dwsq + pa(0);
    Float diff = ppa - pa(n-1);
    Float t = diff >= 0 ? 0.5 : -0.5;
    Int maxnpi = Int(diff/C::pi + t);
    ppa = -abs(rmMax)*dwsq + pa(0);
    diff = ppa - pa(n-1);
    t = diff >= 0 ? 0.5 : -0.5;
    Int minnpi = Int(diff/C::pi + t);
    // Loop over range of n*pi ambiguity
    Vector<Float> fitpa(n);
    Vector<Float> pars;
    Float chiSq = 1e30;
    for (Int h=minnpi; h<=maxnpi; ++h) {
        fitpa[n-1] = pa[n-1] + C::pi*h;
        Float rm0 = (fitpa(n-1) - pa(0))/dwsq;
        // Assign position angles to remaining wavelengths
        for (uInt k=1; k<n-1; ++k) {
            ppa = pa[0] + rm0*(wsq[k]-wsq[0]);
            diff = ppa - pa[k];
            t = diff >= 0 ? 0.5 : -0.5;
            Int npi = Int(diff/C::pi + t);
            fitpa[k] = pa[k] + npi*C::pi;
        }
        fitpa[0] = pa[0];
        // Do least squares fit
        if (!_rmLsqFit (pars, wsq, fitpa, paerr)) {
            return false;
        }
        if (pars[4] < chiSq) {
            chiSq = pars[4];
            nTurns = h;                   // Number of turns
            rmFitted = pars[0];           // Fitted RM
            rmErrFitted = pars[1];        // Error in RM
            pa0Fitted = pars[2];          // Fitted intrinsic angle
            pa0ErrFitted = pars[3];       // Error in angle
            rChiSqFitted = pars[4];       // Recued chi squared
            if (n > 2) {
                rChiSqFitted /= Float(n - 2);
            }
        }
    }
    return true;
}

Bool ImagePolarimetry::_rmSupplementaryFit(
    Float& nTurns, Float& rmFitted, Float& rmErrFitted, Float& pa0Fitted,
    Float& pa0ErrFitted, Float& rChiSqFitted,  const Vector<Float>& wsq,
    const Vector<Float>& pa, const Vector<Float>& paerr
) {
    // For supplementary points find lowest residual RM
    const uInt n = wsq.size();
    Float absRM = 1e30;
    Vector<Float> fitpa(pa.copy());
    Vector<Float> pars;
    for (Int i=-2; i<3; ++i) {
        fitpa[n-1] = pa[n-1] + C::pi*i;
        // Do least squares fit
        if (! _rmLsqFit (pars, wsq, fitpa, paerr)) {
            return false;
        }
        // Save solution  with lowest absolute RM
        if (abs(pars[0]) < absRM) {
            absRM = abs(pars[0]);
            nTurns = i;                        // nTurns
            rmFitted = pars(0);                // Fitted RM
            rmErrFitted = pars[1];             // Error in RM
            pa0Fitted = pars[2];               // Fitted intrinsic angle
            pa0ErrFitted = pars[3];            // Error in angle
            rChiSqFitted = pars[4];            // Reduced chi squared
            if (n > 2) {
                rChiSqFitted /= Float(n - 2);
            }
        }
    }
    return true;
}

Bool ImagePolarimetry::_rmLsqFit(
    Vector<Float>& pars, const Vector<Float>& wsq, const Vector<Float> pa,
    const Vector<Float>& paerr
) const {
    // Perform fit on unmasked data
    static Vector<Float> solution;
    try {
        solution = _fitter->fit(wsq, pa, paerr);
    } catch (AipsError x) {
        return false;
    }
    const Vector<Double>& cv = _fitter->compuCovariance().diagonal();
    pars.resize(5);
    pars[0] = solution[1];
    pars[1] = sqrt(cv[1]);
    pars[2] = solution[0];
    pars[3] = sqrt(cv[0]);
    pars[4] = _fitter->chiSquare();
    return true;
}

String ImagePolarimetry::_stokesName(
    ImagePolarimetry::StokesTypes index
) const {
    const auto iter = polMap.find(index);
    return (iter == polMap.end()) ? "??" : iter->second;
}

Stokes::StokesTypes ImagePolarimetry::_stokesType (ImagePolarimetry::StokesTypes index) const
{
   if (index==ImagePolarimetry::I) {
      return Stokes::I;
   } else if (index==ImagePolarimetry::Q) {
      return Stokes::Q;
   } else if (index==ImagePolarimetry::U) {
      return Stokes::U;
   } else if (index==ImagePolarimetry::V) {
      return Stokes::V;
   } else {
      return Stokes::Undefined;
   }
}


Float ImagePolarimetry::_sigma(
    ImagePolarimetry::StokesTypes index, Float clip
) {
    Float clip2 = clip == 0 ? 10.0 : abs(clip);
    if (clip2 != _oldClip && _stokesStats[index]) {
        delete _stokesStats[index];
        _stokesStats[index] = 0;
    }
    if (! _stokesStats[index]) {
        // Find sigma for all points inside +/- clip-sigma of the mean
        // More joys of LEL
        const ImageInterface<Float>* p = _stokes[index];
        LatticeExprNode n1 (*p);
        LatticeExprNode n2 (n1[abs(n1-mean(n1)) < clip2*stddev(n1)]);
        LatticeExpr<Float> le(n2);
        _stokesStats[index] = new LatticeStatistics<Float>(le, false, false);
    }
    Array<Float> sigmaA;
    _stokesStats[index]->getConvertedStatistic(sigmaA, LatticeStatsBase::SIGMA);
    ThrowIf(
        sigmaA.empty(),
        "No good points in clipped determination of the noise for the Stokes "
        + _stokesName(index) + " image"
    );
    _oldClip = clip2;
    return sigmaA(IPosition(1,0));
}

void ImagePolarimetry::_subtractProfileMean(
    ImageInterface<Float>& im, uInt axis
) const {
    const IPosition tileShape = im.niceCursorShape();
    TiledLineStepper ts(im.shape(), tileShape, axis);
    LatticeIterator<Float> it(im, ts);
    Float dMean;
    if (im.isMasked()) {
        const Lattice<Bool>& mask = im.pixelMask();
        for (it.reset(); !it.atEnd(); it++) {
            const auto& p = it.cursor();
            const auto& m = mask.getSlice(it.position(), it.cursorShape());
            const MaskedArray<Float> ma(p, m, true);
            dMean = mean(ma);
            it.rwVectorCursor() -= dMean;
        }
    }
    else {
        for (it.reset(); !it.atEnd(); it++) {
            dMean = mean(it.vectorCursor());
            it.rwVectorCursor() -= dMean;
        }
    }
}

Bool ImagePolarimetry::_dealWithMask(
    Lattice<Bool>*& pMask, ImageInterface<Float>*& pIm, LogIO& os,
    const String& type
) const {
    auto isMasked = pIm->isMasked();
    if (! isMasked) {
        if (pIm->canDefineRegion()) {
            pIm->makeMask("mask0", true, true, true, true);
            isMasked = true;
        }
        else {
            os << LogIO::WARN << "Could not create a mask for the output "
                << type << " image" << LogIO::POST;
        }
    }
    if (isMasked) {
        pMask = &(pIm->pixelMask());
        if (! pMask->isWritable()) {
            isMasked = false;
            os << LogIO::WARN << "The output " << type
                << " image has a mask but it's not writable" << LogIO::POST;
        }
    }
    return isMasked;
}

void ImagePolarimetry::_createBeamsEqMat() {
	_beamsEqMat.assign(Matrix<Bool>(4, 4, false));
	Bool hasMultiBeams = _image->imageInfo().hasMultipleBeams();
	for (uInt i=0; i<4; ++i) {
		for (uInt j=i; j<4; ++j) {
			if (! (_stokes[i] &&  _stokes[j])) {
				_beamsEqMat(i, j) = false;
			}
			else if (i == j) {
				_beamsEqMat(i, j) = true;
			}
			else if (hasMultiBeams) {
				_beamsEqMat(i, j) = (
					_stokes[i]->imageInfo().getBeamSet()
					== _stokes[j]->imageInfo().getBeamSet()
				);
			}
			else {
				_beamsEqMat(i, j) = true;
			}
			_beamsEqMat(j, i) = _beamsEqMat(i, j);

		}
	}
}

Bool ImagePolarimetry::_checkBeams(
    const vector<StokesTypes>& stokes, Bool requireChannelEquality, Bool throws
) const {
	for (
		auto iter = stokes.cbegin(); iter != stokes.cend(); ++iter
	) {
		for (
			auto jiter=iter; jiter!=stokes.cend(); ++jiter
		) {
			if (iter == jiter) {
				continue;
			}
			if (! _beamsEqMat(*iter, *jiter)) {
				ThrowIf(
				    throws,
				    "Input image has multiple beams and the corresponding "
				    "beams for the stokes planes necessary for this "
				    "computation are not equal."
				);
				return False;
			}
		}
	}
	if (
		requireChannelEquality
		&& _stokes[stokes[0]]->coordinates().hasSpectralAxis()
		&& _stokes[stokes[0]]->imageInfo().hasMultipleBeams()
	) {
		const auto& beamSet
		    = _stokes[stokes[0]]->imageInfo().getBeamSet().getBeams();
		auto start = beamSet.cbegin();
		++start;
		for (auto iter=start; iter!=beamSet.cend(); ++iter) {
			if (*iter != *(beamSet.begin())) {
				ThrowIf(
				    throws,
					"At least one beam in this image is not equal to all the "
				    "others along its spectral axis so this computation cannot "
				    "be performed"
				);
				return False;
			}
		}
	}
	return true;
}

Bool ImagePolarimetry::_checkIQUBeams(
    Bool requireChannelEquality, Bool throws
) const {
	static const vector<StokesTypes> types = {I, Q, U};
	return _checkBeams(types, requireChannelEquality, throws);
}

Bool ImagePolarimetry::_checkIVBeams(
    Bool requireChannelEquality, Bool throws
) const {
	static const vector<StokesTypes> types = {I, V};
	return _checkBeams(types, requireChannelEquality, throws);
}

Bool ImagePolarimetry::_checkQUBeams(
    Bool requireChannelEquality, Bool throws
) const {
	static const vector<StokesTypes> types = {Q, U};
	return _checkBeams(types, requireChannelEquality, throws);
}

void ImagePolarimetry::_checkBeams(
	const ImagePolarimetry& im1, const ImagePolarimetry& im2,
	const Vector<ImagePolarimetry::StokesTypes>& stokes
) {
	for (auto iter=stokes.cbegin(); iter!=stokes.cend(); iter++) {
		ThrowIf(
			im1.stokes(*iter).imageInfo().getBeamSet()
			!= im2.stokes(*iter).imageInfo().getBeamSet(),
			"Beams or beamsets are not equal between the two images, caonnot "
			"perform calculation"
		);
	}
}

}