//# MomentCalcBase.tcc: //# Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003,2004 //# 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.tcc 19940 2007-02-27 05:35:22Z Malte.Marquarding $ // #include <casacore/casa/aips.h> #include <casacore/casa/Arrays/Vector.h> #include <casacore/casa/Arrays/ArrayMath.h> #include <casacore/coordinates/Coordinates/CoordinateSystem.h> #include <casacore/coordinates/Coordinates/SpectralCoordinate.h> #include <casacore/scimath/Fitting/NonLinearFitLM.h> #include <casacore/scimath/Functionals/Polynomial.h> #include <casacore/scimath/Functionals/CompoundFunction.h> #include <imageanalysis/ImageAnalysis/MomentsBase.h> #include <casacore/lattices/LatticeMath/LatticeStatsBase.h> #include <casacore/casa/BasicMath/Math.h> #include <casacore/casa/Logging/LogIO.h> #include <casacore/casa/Utilities/Assert.h> #include <casacore/casa/Exceptions/Error.h> namespace casa { template <class T> MomentCalcBase<T>::~MomentCalcBase() {} template <class T> void MomentCalcBase<T>::init( casacore::uInt nOutPixelsPerCollapse ) { AlwaysAssert (nOutPixelsPerCollapse == 1, casacore::AipsError); } template <class T> casacore::uInt MomentCalcBase<T>::allNoise ( T& dMean, const casacore::Vector<T>& data, const casacore::Vector<casacore::Bool>& mask, const T peakSNR, const T stdDeviation ) const { // Try and work out whether this spectrum is all noise // or not. We don't bother with it if it is noise. // We compare the peak with sigma and a cutoff SNR // Returns 1 if all noise // Returns 2 if all masked // Returns 0 otherwise casacore::ClassicalStatistics<AccumType, DataIterator, MaskIterator> statsCalculator; statsCalculator.addData(data.begin(), mask.begin(), data.size()); StatsData<AccumType> stats = statsCalculator.getStatistics(); if (stats.npts == 0) { return 2; } T dMin = *stats.min; T dMax = *stats.max; dMean = stats.mean; // Assume we are continuum subtracted so outside of line mean=0 const T rat = max(abs(dMin),abs(dMax)) / stdDeviation; casacore::uInt ret = rat < peakSNR ? 1 : 0; return ret; } template <class T> void MomentCalcBase<T>::constructorCheck(casacore::Vector<T>& calcMoments, casacore::Vector<casacore::Bool>& calcMomentsMask, const casacore::Vector<casacore::Int>& selectMoments, const casacore::uInt nLatticeOut) const { // Number of output lattices must equal the number of moments // the user asked to calculate AlwaysAssert(nLatticeOut == selectMoments.nelements(), casacore::AipsError); // Number of requested moments must be in allowed range auto nMaxMoments = MomentsBase<T>::NMOMENTS; AlwaysAssert(selectMoments.nelements() <= nMaxMoments, casacore::AipsError); AlwaysAssert(selectMoments.nelements() > 0, casacore::AipsError); // Resize the vector that will hold ALL possible moments calcMoments.resize(nMaxMoments); calcMomentsMask.resize(nMaxMoments); } template <class T> void MomentCalcBase<T>::costlyMoments(MomentsBase<T>& iMom, casacore::Bool& doMedianI, casacore::Bool& doMedianV, casacore::Bool& doAbsDev) const { doMedianI = false; doMedianV = false; doAbsDev = false; using IM = MomentsBase<casacore::Float>; // for (casacore::uInt i=0; i<iMom.moments_p.nelements(); i++) { if (iMom.moments_p(i) == IM::MEDIAN) doMedianI = true; if (iMom.moments_p(i) == IM::MEDIAN_COORDINATE) doMedianV = true; if (iMom.moments_p(i) == IM::ABS_MEAN_DEVIATION) doAbsDev = true; } } template <class T> Bool MomentCalcBase<T>::doFit(const MomentsBase<T>& iMom) const { // Get it from ImageMoments private data return iMom.doFit_p; } template <class T> void MomentCalcBase<T>::doCoordCalc(casacore::Bool& doCoordProfile, casacore::Bool& doCoordRandom, const MomentsBase<T>& iMom) const // // doCoordProfile - we need the coordinate for each pixel of the profile // doCoordRandom - we need the coordinate for occaisional use // { // Figure out if we need to compute the coordinate of each profile pixel index // for each profile. This is very expensive for non-separable axes. doCoordProfile = false; doCoordRandom = false; using IM = MomentsBase<casacore::Float>; // for (casacore::uInt i=0; i<iMom.moments_p.nelements(); i++) { if (iMom.moments_p(i) == IM::WEIGHTED_MEAN_COORDINATE || iMom.moments_p(i) == IM::WEIGHTED_DISPERSION_COORDINATE) { doCoordProfile = true; } if (iMom.moments_p(i) == IM::MAXIMUM_COORDINATE || iMom.moments_p(i) == IM::MINIMUM_COORDINATE || iMom.moments_p(i) == IM::MEDIAN_COORDINATE) { doCoordRandom = true; } } } template <class T> Bool MomentCalcBase<T>::findNextDatum (casacore::uInt& iFound, const casacore::uInt& n, const casacore::Vector<casacore::Bool>& mask, const casacore::uInt& iStart, const casacore::Bool& findGood) const // // Find the next good (or bad) point in an array. // A good point in the array has a non-zero value. // // Inputs: // n Number of points in array // mask casacore::Vector containing counts. // iStart The index of the first point to consider // findGood If true look for next good point. // If false look for next bad point // Outputs: // iFound Index of found point // casacore::Bool false if didn't find another valid datum { for (casacore::uInt i=iStart; i<n; i++) { if ( (findGood && mask(i)) || (!findGood && !mask(i)) ) { iFound = i; return true; } } return false; } template <class T> casacore::Bool MomentCalcBase<T>::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, const T peakGuess, const T posGuess, const T widthGuess, const T levelGuess ) const { // Fit Gaussian pos * exp(-4ln2*(x-pos)**2/width**2) // width = fwhm // Returns false if fit fails or all masked // Select unmasked pixels casacore::uInt j = 0; auto nAll = y.size(); casacore::Vector<T> xSel(nAll); casacore::Vector<T> ySel(nAll); for (casacore::uInt i=0; i<nAll; ++i) { if (mask[i]) { xSel[j] = x[i]; ySel[j] = y[i]; ++j; } } auto nPts = j; if (nPts == 0) { return false; } xSel.resize(nPts, true); ySel.resize(nPts, true); // Create fitter as gaussian + constant offset casacore::NonLinearFitLM<T> fitter; casacore::Gaussian1D<casacore::AutoDiff<T> > gauss; casacore::Polynomial<casacore::AutoDiff<T> > poly; casacore::CompoundFunction<casacore::AutoDiff<T> > func; func.addFunction(gauss); func.addFunction(poly); fitter.setFunction(func); // Initial guess casacore::Vector<T> v(4); v[0] = peakGuess; v[1] = posGuess; v[2] = widthGuess; v[3] = levelGuess; fitter.setParameterValues(v); // Set maximum number of iterations to 50. Default is 10 fitter.setMaxIter(50); // Set converge criteria. fitter.setCriteria(0.001); // Perform fit on unmasked data casacore::Vector<T> resultSigma(nPts, 1); casacore::Vector<T> solution; try { solution = fitter.fit(xSel, ySel, resultSigma); } catch (const casacore::AipsError& x1) { ++nFailed; return false; } // Return values of fit // FIXME shouldn't these only be set if the fit converged? peak = solution[0]; pos = solution[1]; width = abs(solution[2]); level = solution[3]; // Return status auto converged = fitter.converged(); if (! converged) { ++nFailed; } return converged; } template <class T> Bool MomentCalcBase<T>::getAutoGaussianFit (casacore::uInt& nFailed, casacore::Vector<T>& gaussPars, const casacore::Vector<T>& x, const casacore::Vector<T>& y, const casacore::Vector<casacore::Bool>& mask, const T peakSNR, const T stdDeviation ) const // // Automatically fit a Gaussian and return the Gaussian parameters. // If a plotting device is active, we also plot the spectra and fits // // Inputs: // x,y casacore::Vector containing the data // mask true is good // plotter Plot spectrum and optionally the window // x,yLabel Labels // title // casacore::Input/output // nFailed Cumulative number of failed fits // Output: // gaussPars The gaussian parameters, peak, pos, fwhm // casacore::Bool If false then this spectrum has been rejected (all // masked, all noise, failed fit) // { // See if this spectrum is all noise. If so, forget it. // Return straight away if all masked T dMean; casacore::uInt iNoise = this->allNoise(dMean, y, mask, peakSNR, stdDeviation); if (iNoise == 2) return false; if (iNoise==1) { gaussPars = 0; return false; } // Work out guesses for Gaussian T peakGuess, posGuess, widthGuess, levelGuess; T pos, width, peak, level; if (!getAutoGaussianGuess(peakGuess, posGuess, widthGuess, levelGuess, x, y, mask)) return false; peakGuess = peakGuess - levelGuess; // Fit gaussian. Do it twice. if (!fitGaussian (nFailed, peak, pos, width, level, x, y, mask, peakGuess, posGuess, widthGuess, levelGuess)) { gaussPars = 0; return false; } gaussPars(0) = peak; gaussPars(1) = pos; gaussPars(2) = width; gaussPars(3) = level; return true; } template <class T> casacore::Bool MomentCalcBase<T>::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 { // Make a wild stab in the dark as to what the Gaussian // parameters of this spectrum might be casacore::ClassicalStatistics<AccumType, DataIterator, MaskIterator> statsCalculator; statsCalculator.addData(y.begin(), mask.begin(), y.size()); StatsData<AccumType> stats = statsCalculator.getStatistics(); if (stats.npts == 0) { // all masked return false; } // Find peak and position of peak posGuess = x[stats.maxpos.second]; peakGuess = *stats.max; levelGuess = stats.mean; // Nothing much is very robust. Assume the line is reasonably // sampled and set its width to a few pixels. Totally ridiculous. widthGuess = 5; return true; } template <class T> void MomentCalcBase<T>::lineSegments (casacore::uInt& nSeg, casacore::Vector<casacore::uInt>& start, casacore::Vector<casacore::uInt>& nPts, const casacore::Vector<casacore::Bool>& mask) const // // Examine an array and determine how many segments // of good points it consists of. A good point // occurs if the array value is greater than zero. // // Inputs: // mask The array mask. true is good. // Outputs: // nSeg Number of segments // start Indices of start of each segment // nPts Number of points in segment // { casacore::Bool finish = false; nSeg = 0; casacore::uInt iGood, iBad; const casacore::uInt n = mask.nelements(); start.resize(n); nPts.resize(n); for (casacore::uInt i=0; !finish;) { if (!findNextDatum (iGood, n, mask, i, true)) { finish = true; } else { nSeg++; start(nSeg-1) = iGood; if (!findNextDatum (iBad, n, mask, iGood, false)) { nPts(nSeg-1) = n - start(nSeg-1); finish = true; } else { nPts(nSeg-1) = iBad - start(nSeg-1); i = iBad + 1; } } } start.resize(nSeg,true); nPts.resize(nSeg,true); } template <class T> Int& MomentCalcBase<T>::momentAxis(MomentsBase<T>& iMom) const { return iMom.momentAxis_p; } template <class T> String MomentCalcBase<T>::momentAxisName(const casacore::CoordinateSystem& cSys, const MomentsBase<T>& iMom) const { // Return the name of the moment/profile axis casacore::Int worldMomentAxis = cSys.pixelAxisToWorldAxis(iMom.momentAxis_p); return cSys.worldAxisNames()(worldMomentAxis); } template <class T> T& MomentCalcBase<T>::peakSNR(MomentsBase<T>& iMom) const { // Get it from ImageMoments private data return iMom.peakSNR_p; } template <class T> void MomentCalcBase<T>::selectRange(casacore::Vector<T>& pixelRange, casacore::Bool& doInclude, casacore::Bool& doExclude, MomentsBase<T>& iMom) const { // Get it from ImageMoments private data pixelRange = iMom.selectRange_p; doInclude = (!(iMom.noInclude_p)); doExclude = (!(iMom.noExclude_p)); } template <class T> Vector<casacore::Int> MomentCalcBase<T>::selectMoments(MomentsBase<T>& iMom) const // // Fill the moment selection vector according to what the user requests // { using IM = MomentsBase<casacore::Float>; casacore::Vector<casacore::Int> sel(IM::NMOMENTS); casacore::uInt j = 0; for (casacore::uInt i=0; i<iMom.moments_p.nelements(); i++) { if (iMom.moments_p(i) == IM::AVERAGE) { sel(j++) = IM::AVERAGE; } else if (iMom.moments_p(i) == IM::INTEGRATED) { sel(j++) = IM::INTEGRATED; } else if (iMom.moments_p(i) == IM::WEIGHTED_MEAN_COORDINATE) { sel(j++) = IM::WEIGHTED_MEAN_COORDINATE; } else if (iMom.moments_p(i) == IM::WEIGHTED_DISPERSION_COORDINATE) { sel(j++) = IM::WEIGHTED_DISPERSION_COORDINATE; } else if (iMom.moments_p(i) == IM::MEDIAN) { sel(j++) = IM::MEDIAN; } else if (iMom.moments_p(i) == IM::STANDARD_DEVIATION) { sel(j++) = IM::STANDARD_DEVIATION; } else if (iMom.moments_p(i) == IM::RMS) { sel(j++) = IM::RMS; } else if (iMom.moments_p(i) == IM::ABS_MEAN_DEVIATION) { sel(j++) = IM::ABS_MEAN_DEVIATION; } else if (iMom.moments_p(i) == IM::MAXIMUM) { sel(j++) = IM::MAXIMUM; } else if (iMom.moments_p(i) == IM::MAXIMUM_COORDINATE) { sel(j++) = IM::MAXIMUM_COORDINATE; } else if (iMom.moments_p(i) == IM::MINIMUM) { sel(j++) = IM::MINIMUM; } else if (iMom.moments_p(i) == IM::MINIMUM_COORDINATE) { sel(j++) = IM::MINIMUM_COORDINATE; } else if (iMom.moments_p(i) == IM::MEDIAN_COORDINATE) { sel(j++) = IM::MEDIAN_COORDINATE; } } sel.resize(j,true); return sel; } template <class T> void MomentCalcBase<T>::setPosLabel (casacore::String& title, const casacore::IPosition& pos) const { ostringstream oss; oss << "Position = " << pos+1; casacore::String temp(oss); title = temp; } template <class T> void MomentCalcBase<T>::setCoordinateSystem (casacore::CoordinateSystem& cSys, MomentsBase<T>& iMom) { cSys = iMom.coordinates() ; } template <class T> void MomentCalcBase<T>::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 // // casacore::Input: // doCoordProfile - we need the coordinate for each pixel of the profile // and we precompute it if we can // doCoordRandom - we need the coordinate for occaisional use // // This function does two things. It sets up the pixelIn // and worldOut vectors needed by getMomentCoord. It also // precomputes the vector of coordinates for the moment axis // profile if it is separable // { // Do we need the scale factor for the integrated moment casacore::Int axis = iMom.momentAxis_p; casacore::Bool doIntScaleFactor = false; integratedScaleFactor = 1.0; for (casacore::uInt i=0; i<iMom.moments_p.nelements(); i++) { if (iMom.moments_p(i) == MomentsBase<casacore::Float>::INTEGRATED) { doIntScaleFactor = true; break; } } // sepWorldCoord.resize(0); if (!doCoordProfile && !doCoordRandom && !doIntScaleFactor) return; // Resize these vectors used for occaisional coordinate transformations pixelIn.resize(cSys.nPixelAxes()); worldOut.resize(cSys.nWorldAxes()); if (!doCoordProfile && !doIntScaleFactor) return; // Find the coordinate for the moment axis casacore::Int coordinate, axisInCoordinate; cSys.findPixelAxis(coordinate, axisInCoordinate, axis); // Find out whether this coordinate is separable or not casacore::Int nPixelAxes = cSys.coordinate(coordinate).nPixelAxes(); casacore::Int nWorldAxes = cSys.coordinate(coordinate).nWorldAxes(); // Precompute the profile coordinates if it is separable and needed // The Integrated moment scale factor is worked out here as well so the // logic is a bit contorted casacore::Bool doneIntScale = false; if (nPixelAxes == 1 && nWorldAxes == 1) { pixelIn = cSys_p.referencePixel(); // casacore::Vector<casacore::Double> frequency(iMom.getShape()(axis)); if (doCoordProfile) { for (casacore::uInt i=0; i<frequency.nelements(); i++) { frequency(i) = getMomentCoord(iMom, pixelIn, worldOut, casacore::Double(i)); } } // If the coordinate of the moment axis is Spectral convert to km/s // Although I could work this out here, it would be decoupled from // ImageMoments which works the same thing out and sets the units. // So to ensure coupling, i pass in this switch via the IM object if (iMom.convertToVelocity_p) { AlwaysAssert(cSys.type(coordinate)==casacore::Coordinate::SPECTRAL, casacore::AipsError); // Should never fail ! // const casacore::SpectralCoordinate& sc = cSys.spectralCoordinate(coordinate); casacore::SpectralCoordinate sc0(sc); // Convert sc0.setVelocity (casacore::String("km/s"), iMom.velocityType_p); if (doCoordProfile) { sc0.frequencyToVelocity (sepWorldCoord, frequency); } // Find increment in world units at reference pixel if needed if (doIntScaleFactor) { casacore::Quantum<casacore::Double> vel0, vel1; casacore::Double pix0 = sc0.referencePixel()(0) - 0.5; casacore::Double pix1 = sc0.referencePixel()(0) + 0.5; sc0.pixelToVelocity (vel0, pix0); sc0.pixelToVelocity (vel1, pix1); integratedScaleFactor = abs(vel1.getValue() - vel0.getValue()); doneIntScale = true; } } } else { os << casacore::LogIO::NORMAL << "You have asked for a coordinate moment from a non-separable " << endl; os << "axis. This means a coordinate must be computed for each pixel " << endl; os << "of each profile which will cause performance degradation" << casacore::LogIO::POST; } // if (doIntScaleFactor && !doneIntScale) { // We need the Integrated moment scale factor but the moment // axis is non-separable const casacore::Coordinate& c = cSys.coordinate(coordinate); casacore::Double inc = c.increment()(axisInCoordinate); integratedScaleFactor = abs(inc*inc); doneIntScale = true; } } template <class T> T& MomentCalcBase<T>::stdDeviation(MomentsBase<T>& iMom) const { return iMom.stdDeviation_p; } // Fill the ouput moments array template<class T> void MomentCalcBase<T>::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 the moments vector // // Inputs // integratedScaleFactor width of a channel in km/s or Hz or whatever // Outputs: // calcMoments The moments // { // casacore::Short hand to fish ImageMoments enum values out // Despite being our friend, we cannot refer to the // enum values as just, say, "AVERAGE" using IM = MomentsBase<casacore::Float>; // Normalize and fill moments calcMomentsMask = true; calcMoments(IM::AVERAGE) = s0 / nPts; calcMoments(IM::INTEGRATED) = s0 * integratedScaleFactor; if (abs(s0) > 0.0) { calcMoments(IM::WEIGHTED_MEAN_COORDINATE) = s1 / s0; // calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE) = (s2 / s0) - calcMoments(IM::WEIGHTED_MEAN_COORDINATE) * calcMoments(IM::WEIGHTED_MEAN_COORDINATE); calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE) = abs(calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE)); if (calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE) > 0.0) { calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE) = sqrt(calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE)); } else { calcMoments(IM::WEIGHTED_DISPERSION_COORDINATE) = 0.0; calcMomentsMask(IM::WEIGHTED_DISPERSION_COORDINATE) = false; } } else { calcMomentsMask(IM::WEIGHTED_MEAN_COORDINATE) = false; calcMomentsMask(IM::WEIGHTED_DISPERSION_COORDINATE) = false; } // Standard deviation about mean of I if (nPts>1 && casacore::Float((s0Sq - s0*s0/nPts)/(nPts-1)) > 0) { calcMoments(IM::STANDARD_DEVIATION) = sqrt((s0Sq - s0*s0/nPts)/(nPts-1)); } else { calcMoments(IM::STANDARD_DEVIATION) = 0; calcMomentsMask(IM::STANDARD_DEVIATION) = false; } // Rms of I calcMoments(IM::RMS) = sqrt(s0Sq/nPts); // Absolute mean deviation calcMoments(IM::ABS_MEAN_DEVIATION) = sumAbsDev / nPts; // Maximum value calcMoments(IM::MAXIMUM) = dMax; // casacore::Coordinate of min/max value if (doCoord) { calcMoments(IM::MAXIMUM_COORDINATE) = getMomentCoord( iMom, pixelIn, worldOut, casacore::Double(iMax), iMom.convertToVelocity_p ); calcMoments(IM::MINIMUM_COORDINATE) = getMomentCoord( iMom, pixelIn, worldOut, casacore::Double(iMin), iMom.convertToVelocity_p ); } else { calcMoments(IM::MAXIMUM_COORDINATE) = 0.0; calcMoments(IM::MINIMUM_COORDINATE) = 0.0; calcMomentsMask(IM::MAXIMUM_COORDINATE) = false; calcMomentsMask(IM::MINIMUM_COORDINATE) = false; } // Minimum value calcMoments(IM::MINIMUM) = dMin; // Medians calcMoments(IM::MEDIAN) = dMedian; calcMoments(IM::MEDIAN_COORDINATE) = vMedian; } }