//# ImageDecomposer.cc: defines the ImageDecomposer class //# Copyright (C) 1994,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: ImageDecomposer.tcc 20739 2009-09-29 01:15:15Z Malte.Marquarding $ #include <imageanalysis/ImageAnalysis/ImageDecomposer.h> #include <casacore/casa/Arrays/Slicer.h> #include <casacore/casa/Arrays/Cube.h> #include <casacore/casa/IO/ArrayIO.h> #include <casacore/lattices/Lattices/TiledShape.h> #include <casacore/scimath/Fitting/FitGaussian.h> #include <casacore/coordinates/Coordinates/CoordinateSystem.h> #include <casacore/lattices/LRegions/LatticeRegion.h> #include <casacore/lattices/LRegions/LCMask.h> #include <casacore/lattices/LEL/LatticeExpr.h> #include <casacore/lattices/LEL/LatticeExprNode.h> #include <casacore/lattices/Lattices/LatticeIterator.h> #include <casacore/casa/BasicMath/Math.h> #include <casacore/casa/Utilities/Assert.h> #include <casacore/images/Images/TempImage.h> #include <casacore/images/Images/SubImage.h> namespace casa { template <class T> ImageDecomposer<T>::ImageDecomposer(const casacore::ImageInterface<T>& image) : itsImagePtr(image.cloneII()), itsMapPtr(0), itsShape(itsImagePtr->shape()), itsDim(itsShape.nelements()), itsNRegions(0), itsNComponents(0), itsDeblendIt(true), itsThresholdVal(0.1), itsNContour(11), itsMinRange(2), itsNAxis(2), itsFitIt(true), itsMaximumRMS(0.1), itsMaxRetries(-1), itsMaxIter(256), itsConvCriteria(0.001) { itsMapPtr = new casacore::TempLattice<casacore::Int>(casacore::TiledShape(itsShape), 1); if (!itsMapPtr) { delete itsImagePtr; throw(casacore::AipsError("Failed to create internal TempLattice")); } // itsMapPtr->set(0); } template <class T> ImageDecomposer<T>::ImageDecomposer(const ImageDecomposer<T>& other) : itsImagePtr(other.itsImagePtr->cloneII()), itsMapPtr(0), itsShape(other.itsShape), itsDim(other.itsDim), itsNRegions(0), itsNComponents(0) { itsMapPtr = new casacore::TempLattice<casacore::Int>(casacore::TiledShape(itsShape), 1); if (!itsMapPtr) { delete itsImagePtr; throw(casacore::AipsError("Failed to create internal TempLattice")); } // itsNRegions = other.itsNRegions; itsNComponents = other.itsNComponents; itsList = other.itsList.copy(); copyOptions(other); // Copy values from other.itsMapPtr itsMapPtr->copyData(*(other.itsMapPtr)); } /* template <class T> ImageDecomposer<T> &ImageDecomposer<T>::operator=(const ImageDecomposer<T> &other) { if (this != &other) { delete itsImagePtr; delete itsMapPtr; itsImagePtr = other.itsImagePtr->cloneII(); itsShape = other.itsShape; itsDim = other.itsDim; itsNRegions = 0; itsNComponents = 0; itsMapPtr = new casacore::TempLattice<casacore::Int>(casacore::TiledShape(itsShape), 1); itsMapPtr->copyData(*(other.itsMapPtr)); itsList = other.itsList.copy(); copyOptions(other); } return *this; } */ template <class T> ImageDecomposer<T>::~ImageDecomposer() { if (itsImagePtr) { delete itsImagePtr; itsImagePtr = 0; } if (itsMapPtr) { delete itsMapPtr; itsMapPtr = 0; } } // Basic set/get functions // ----------------------- /* template <class T> void ImageDecomposer<T>::setImage(casacore::ImageInterface<T>& image) { if (itsImagePtr) { delete itsImagePtr; itsImagePtr = 0; } if (itsMapPtr) { delete itsMapPtr; itsMapPtr = 0; } // itsImagePtr = image.cloneII(); itsShape.resize(0); //necessary for dimension change itsShape = itsImagePtr->shape(); itsDim = itsShape.nelements(); itsNRegions = 0; itsNComponents = 0; // itsMapPtr = new casacore::TempLattice<casacore::Int>(casacore::TiledShape(itsShape), 1); if (!itsMapPtr) { delete itsImagePtr; throw(casacore::AipsError("Failed to create internal TempLattice")); } itsMapPtr->set(0); } */ template <class T> void ImageDecomposer<T>::setDeblend(casacore::Bool deblendIt) { itsDeblendIt = deblendIt; return; } template <class T> void ImageDecomposer<T>::setDeblendOptions(T thresholdVal,casacore::uInt nContour, casacore::Int minRange, casacore::Int nAxis) { itsThresholdVal = thresholdVal; itsNContour = nContour; itsMinRange = minRange; itsNAxis = nAxis; return; } template <class T> void ImageDecomposer<T>::setFit(casacore::Bool fitIt) { itsFitIt = fitIt; return; } template <class T> void ImageDecomposer<T>::setFitOptions(T maximumRMS, casacore::Int maxRetries, casacore::uInt maxIter, T convCriteria) { itsMaximumRMS = maximumRMS; itsMaxRetries = maxRetries; itsMaxIter = maxIter; itsConvCriteria = convCriteria; return; } template <class T> void ImageDecomposer<T>::copyOptions(const ImageDecomposer<T> &other) { itsDeblendIt = other.itsDeblendIt; itsThresholdVal = other.itsThresholdVal; itsNContour = other.itsNContour; itsMinRange = other.itsMinRange; itsNAxis = other.itsNAxis; itsFitIt = other.itsFitIt; itsMaximumRMS = other.itsMaximumRMS; itsMaxRetries = other.itsMaxRetries; itsMaxIter = other.itsMaxIter; itsConvCriteria = other.itsConvCriteria; return; } template <class T> int ImageDecomposer<T>::getCell(casacore::Int x, casacore::Int y) const { return itsMapPtr->getAt(casacore::IPosition(2,x,y)); } template <class T> int ImageDecomposer<T>::getCell(casacore::Int x, casacore::Int y, casacore::Int z) const { return itsMapPtr->getAt(casacore::IPosition(3,x,y,z)); } template <class T> int ImageDecomposer<T>::getCell(const casacore::IPosition& coord) const { return itsMapPtr->getAt(coord); //note: 3D casacore::IPosition works on 2D image } template <class T> void ImageDecomposer<T>::setCell(casacore::Int x, casacore::Int y, casacore::Int sval) { itsMapPtr->putAt(sval, casacore::IPosition(2,x,y)); return; } template <class T> void ImageDecomposer<T>::setCell(casacore::Int x, casacore::Int y, casacore::Int z, casacore::Int sval) { itsMapPtr->putAt(sval, casacore::IPosition(3,x,y,z)); return; } template <class T> void ImageDecomposer<T>::setCell(const casacore::IPosition& coord, casacore::Int sval) { itsMapPtr->putAt(sval, coord); return; } template <class T> casacore::IPosition ImageDecomposer<T>::shape() const { return itsShape; } template <class T> int ImageDecomposer<T>::shape(casacore::uInt axis) const { if (itsDim > axis) return itsShape(axis); return 1; } //The numRegions and numComponents functions are both getters for their //respective variables and flags telling whether the image has been derived //and/or decomposed. template <class T> casacore::uInt ImageDecomposer<T>::numRegions() const { return itsNRegions; } template <class T> casacore::uInt ImageDecomposer<T>::numComponents() const { return itsNComponents; } template <class T> bool ImageDecomposer<T>::isDerived() const { return itsNRegions>0; } template <class T> bool ImageDecomposer<T>::isDecomposed() const { return itsNComponents>0; } template <class T> T ImageDecomposer<T>::getImageVal(casacore::Int x, casacore::Int y) const { return getImageVal(casacore::IPosition(2,x,y)); } template <class T> T ImageDecomposer<T>::getImageVal(casacore::Int x, casacore::Int y, casacore::Int z) const { return getImageVal(casacore::IPosition(3,x,y,z)); } template <class T> T ImageDecomposer<T>::getImageVal(casacore::IPosition coord) const { return itsImagePtr->getAt(coord); } template <class T> int ImageDecomposer<T>::getContourVal(casacore::Int x, casacore::Int y, const casacore::Vector<T>& clevels) const { return getContourVal(casacore::IPosition(2,x,y), clevels); } template <class T> int ImageDecomposer<T>::getContourVal(casacore::Int x, casacore::Int y, casacore::Int z, const casacore::Vector<T>& clevels) const { return getContourVal(casacore::IPosition(3,x,y,z), clevels); } template <class T> int ImageDecomposer<T>::getContourVal(casacore::IPosition coord, const casacore::Vector<T>& clevels) const { T val = itsImagePtr->getAt(coord); for (casacore::uInt c = 0; c < clevels.nelements(); c++) { if (val < clevels(c)) return c - 1; } return clevels.nelements()-1; } template <class T> int ImageDecomposer<T>::getContourVal(T val, const casacore::Vector<T>& clevels) const { for (casacore::uInt c = 0; c < clevels.nelements(); c++) { if (val < clevels(c)) return c - 1; } return clevels.nelements()-1; } template <class T> casacore::Vector<T> ImageDecomposer<T>::autoContour(T mincon, T maxcon, T inc) const { if (inc == T(0)) { throw(casacore::AipsError("Vector<T> ImageDecomposer<T>::autocontour" "T mincon, T maxcon, T inc) - inc cannot be zero")); } if ((maxcon - mincon) * inc < 0) inc = -inc; casacore::Int c = 0; for (T cl = mincon; cl <= maxcon; cl += inc) c++; casacore::Vector<T> contours(c); c = 0; for (T cl = mincon; cl <= maxcon; cl += inc) { contours(c++) = cl; } return contours; } template <class T> casacore::Vector<T> ImageDecomposer<T>::autoContour(casacore::Int nContours, T minValue) const { // IMPR: a noise estimate to determine default value of lowest contour // would be useful. casacore::Vector<T> contours(nContours); T maxValue; // maxValue = findAreaGlobalMax(casacore::IPosition(itsDim,0), shape()); maxValue -= (maxValue-minValue)/((nContours-1)*3); //cerr << "Autocontour: minvalue, maxvalue = " << minValue << ", " << maxValue << endl; // Make maximum contour ~1/3 contour increment less than max value of image for (casacore::Int i=0; i<nContours; i++) { contours(i) = minValue + (maxValue-minValue)*i/(nContours-1); } // return contours; } template <class T> casacore::Vector<T> ImageDecomposer<T>::autoContour(const casacore::Function1D<T>& fn, casacore::Int ncontours, T minvalue) const { // NOTE: This function has not been recently tested. casacore::Vector<T> contours(ncontours); T maxvalue; T calibzero, calibmax; // for (casacore::Int i=1; i<ncontours; i++) { if (fn(T(i-1))>fn(T(i))) { throw(casacore::AipsError("ImageDecomposer<T>::autoContour-" " fn must be nondecreasing in domain")); } } // maxvalue = findAreaGlobalMax(casacore::IPosition(itsDim,0), shape()); maxvalue -= (maxvalue-minvalue)/((ncontours-1)*10); //much closer to top calibzero = minvalue - fn(T(0)); calibmax = (maxvalue - calibzero) / fn(ncontours - 1); // for (casacore::Int i=0; i<ncontours; i++) { contours(i) = calibzero + calibmax*fn(i); } // return contours; } template <class T> casacore::Matrix<T> ImageDecomposer<T>::componentList() const { //IMPR: the pixel->world conversion shouldn't have to be done every time. casacore::Matrix<T> worldList; worldList = itsList; for (casacore::uInt g = 0; g < itsNComponents; g++) { casacore::Vector<casacore::Double> centercoords(itsDim); //casacore::CoordinateSystem uses casacore::Double only casacore::Vector<casacore::Double> compwidth(itsDim); for (casacore::uInt d = 0; d < itsDim; d++) { centercoords(d) = itsList(g,1+d); } for (casacore::uInt d = 0; d < itsDim; d++) { compwidth(d) = itsList(g,1+itsDim+d); } itsImagePtr->coordinates().toWorld(centercoords, centercoords); itsImagePtr->coordinates().toWorld(compwidth, compwidth); itsImagePtr->coordinates().makeWorldRelative(compwidth); for (casacore::uInt d = 0; d < itsDim; d++) { worldList(g,1+d) = centercoords(d); } for (casacore::uInt d = 0; d < itsDim; d++) { if (itsDim == 2 && d == 1) continue; // 2d: axis ratio, not x-width worldList(g,1+itsDim+d) = compwidth(d); } } return worldList; } template <class T> void ImageDecomposer<T>::componentMap() const { // does nothing yet. return; } // Maxima functions // ---------------- template <class T> T ImageDecomposer<T>::findAreaGlobalMax(casacore::IPosition blc, casacore::IPosition trc) const { T val; T maxval = 0.0; correctBlcTrc(blc,trc); // { casacore::IPosition pos(blc); decrement(pos); while (increment(pos,trc)) { val = getImageVal(pos); if (val > maxval) maxval = val; } } // return maxval; } template <class T> void ImageDecomposer<T>::findAreaGlobalMax(T& maxval, casacore::IPosition& maxvalpos, casacore::IPosition blc, casacore::IPosition trc) const { T val; maxvalpos = casacore::IPosition(itsDim,0); maxval = 0.0; correctBlcTrc (blc,trc); // { casacore::IPosition pos(blc); decrement(pos); while (increment(pos,trc)) { val = getImageVal(pos); if (val > maxval) {maxval = val; maxvalpos = pos;} } } } template <class T> casacore::Vector<T> ImageDecomposer<T>::findAreaLocalMax(casacore::IPosition blc, casacore::IPosition trc, casacore::Int naxis) const { casacore::uInt const blocksize = 10; casacore::uInt maxn = 0; casacore::Vector<T> maxvals; correctBlcTrc (blc, trc); // { casacore::IPosition pos(blc); decrement(pos); while (increment(pos,trc)) { if (isLocalMax(pos,naxis)) { if (maxn % blocksize == 0) { maxvals.resize(maxn+blocksize, true); } maxvals(maxn) = getImageVal(pos); maxn++; } } } maxvals.resize(maxn, true); return maxvals; } template <class T> void ImageDecomposer<T>::findAreaLocalMax(casacore::Vector<T>& maxvals, casacore::Block<casacore::IPosition>& maxvalpos, casacore::IPosition blc, casacore::IPosition trc, casacore::Int naxis) const { casacore::uInt const blocksize = 10; casacore::uInt maxn = 0; maxvals.resize(); maxvalpos.resize(0); correctBlcTrc(blc, trc); // { casacore::IPosition pos(blc); decrement(pos); while (increment(pos,trc)) { if (isLocalMax(pos,naxis)) { if (maxn % blocksize == 0) { maxvals.resize(maxn+blocksize, true); maxvalpos.resize(maxn+blocksize, false, true); } maxvals(maxn) = getImageVal(pos); maxvalpos[maxn] = pos; maxn++; } } } maxvals.resize(maxn, true); maxvalpos.resize(maxn, true, true); return; } template <class T> casacore::Vector<T> ImageDecomposer<T>::findRegionLocalMax(casacore::Int regionID, casacore::Int naxis) const { casacore::uInt const blocksize = 10; casacore::uInt maxn = 0; casacore::Vector<T> maxvals; { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if ((getCell(pos) == regionID) && isLocalMax(pos,naxis)) { if (maxn % blocksize == 0) { maxvals.resize(maxn+blocksize, true); } maxvals(maxn) = getImageVal(pos); maxn++; } } } maxvals.resize(maxn, true); return maxvals; } template <class T> void ImageDecomposer<T>::findRegionLocalMax(casacore::Vector<T>& maxvals, casacore::Block<casacore::IPosition>& maxvalpos, casacore::Int regionID, casacore::Int naxis) const { casacore::uInt const blocksize = 10; casacore::uInt maxn = 0; maxvals.resize(); maxvalpos.resize(0); // { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if ((getCell(pos) == regionID) && isLocalMax(pos,naxis)) { cout << "Local max at " << pos << endl; if (maxn % blocksize == 0) { maxvals.resize(maxn+blocksize, true); maxvalpos.resize(maxn+blocksize, false, true); } maxvals(maxn) = getImageVal(pos); maxvalpos[maxn] = pos; maxn++; } } } // maxvals.resize(maxn, true); maxvalpos.resize(maxn, true, true); // return; } template <class T> casacore::Vector<T> ImageDecomposer<T>::findAllRegionGlobalMax() const { //NOTE: while the regions are identified in the itsMapPtr with #s starting at //one, the array returned by this function begin with zero, so there is //an offset of one between itsMapPtr IDs and those used by this function. casacore::Int r; T val; casacore::Vector<T> maxval(itsNRegions); maxval = 0.0; // { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { r = getCell(pos); if (r > 0) { val = getImageVal(pos); if (val > maxval(r-1)) maxval(r-1) = val; } } } return maxval; } template <class T> void ImageDecomposer<T>::findAllRegionGlobalMax(casacore::Vector<T>& maxvals, casacore::Block<casacore::IPosition>& maxvalpos) const { //NOTE: while the regions are identified in the itsMapPtr with #s starting at //one, the arrays returned by this function begin with zero, so there is //an offset of one between itsMapPtr IDs and those used by this function. casacore::Int r; T val; maxvals.resize(itsNRegions); maxvalpos.resize(itsNRegions); maxvals = 0; //note: wholly negative images still return 0 { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { r = getCell(pos); if (r > 0) { val = getImageVal(pos); if (val > maxvals(r-1)) { maxvals(r-1) = val; maxvalpos[r-1] = pos; } } } } return; } template <class T> bool ImageDecomposer<T>::isLocalMax(const casacore::IPosition& pos, casacore::Int naxis) const { if (pos.nelements()==2) { return isLocalMax(pos(0), pos(1), naxis); } else if (pos.nelements()==3) { return isLocalMax(pos(0), pos(1), pos(2),naxis); } else { throw(casacore::AipsError("ImageDecomposer<T>::localmax(IPosition pos, Int naxis)" " - pos has wrong number of dimensions")); } return false; } template <class T> bool ImageDecomposer<T>::isLocalMax(casacore::Int x, casacore::Int y, casacore::Int naxis) const { T val = getImageVal(x,y); casacore::Int ximin = (x>0)? -1:0; casacore::Int yimin = (y>0)? -1:0; casacore::Int ximax = (x+1<shape(0))? 1:0; casacore::Int yimax = (y+1<shape(1))? 1:0; for (casacore::Int xi=ximin; xi<=ximax; xi++) { for (casacore::Int yi=yimin; yi<=yimax; yi++) { if ( ((naxis > 0) || !(xi || yi)) && ((naxis > 1) || !(xi && yi)) && (getImageVal(x+xi,y+yi) > val)) { return false; } } } // return true; } template <class T> bool ImageDecomposer<T>::isLocalMax(casacore::Int x, casacore::Int y, casacore::Int z, casacore::Int naxis) const { T maxval = getImageVal(x,y,z); casacore::Int ximin = (x>0)? -1:0; casacore::Int yimin = (y>0)? -1:0; casacore::Int zimin = (z>0)? -1:0; casacore::Int ximax = (x+1<shape(0))? 1:0; casacore::Int yimax = (y+1<shape(1))? 1:0; casacore::Int zimax = (z+1<shape(2))? 1:0; for (casacore::Int xi=ximin; xi<=ximax; xi++) { for (casacore::Int yi=yimin; yi<=yimax; yi++) { for (casacore::Int zi=zimin; zi<=zimax; zi++) { if ( ((naxis > 0) || !(xi || yi || zi)) && ((naxis > 1) || !((xi && yi) || (xi && zi) || (yi && zi) )) && ((naxis > 2) || !(xi && yi && zi)) && (getImageVal(x+xi,y+yi,z+zi) > maxval)) { return false; } } } } // return true; } // Estimation Functions // -------------------- template <class T> void ImageDecomposer<T>::estimateComponentWidths(casacore::Matrix<T>& width, const casacore::Block<casacore::IPosition>& maxvalpos) const { // Finds a rough estimate of the width of each component. // Requires the location of each component. // This function is now mostly obsolete; its functionality replaced by // calculateMoments() except on non-deblended images. width.resize(maxvalpos.nelements(), itsDim); casacore::Bool dblflag; // for (casacore::uInt r = 0; r < maxvalpos.nelements(); r++) { casacore::IPosition lpos(itsDim); casacore::IPosition rpos(itsDim); casacore::IPosition maxpos(itsDim); maxpos = maxvalpos[r]; T maxvalr = getImageVal(maxpos); T thrval = maxvalr*0.25; T val, prevval; for (casacore::uInt a = 0; a < itsDim; a++) { dblflag = 0; lpos = maxpos; val = maxvalr; prevval = val; while ((lpos(a) > 0) && (val >= thrval) && (val <= prevval)) { prevval = val; lpos(a) --; val = getImageVal(lpos); } if (val < thrval) { width(r,a) = T(maxpos(a)-lpos(a)) - (thrval-val) / (prevval-val); } else if (val > prevval) { width(r,a) = T(maxpos(a)-lpos(a)); } else { //lpos == 0 {width(r,a) = 0; dblflag = 1;} //can't find left limit; //use 2xright limit instead } rpos = maxpos; val = maxvalr; prevval = val; while ((rpos(a)<shape(a)-1) && (val >= thrval) && (val <= prevval)) { prevval = val; rpos(a) ++; val = getImageVal(rpos); } if (val < thrval) { width(r,a) += T(rpos(a)-maxpos(a)) - (thrval-val) / (prevval-val); } else if (val > prevval) { width(r,a) += T(rpos(a)-maxpos(a)); } else { if (!dblflag) { dblflag = 1; //use 2x left side instead } else { width(r,a) += T(rpos(a)-maxpos(a)) * (maxvalr-thrval)/(maxvalr-val); dblflag = 1; } } // if (width(r,a) <= 0.0) width(r,a) = shape(a);//gaussian bigger than image if (!dblflag) width(r,a) /= 2.0; if (casacore::isNaN(width(r,a))) { width(r,a) = 1.0; cerr << "WARNING: Nonphysical estimate, setting width to 1.0" << endl; } } } // return; } template <class T> casacore::Array<T> ImageDecomposer<T>::calculateMoments(casacore::Int region) const { // Calculates the moments of an image region up to second order. // The current implementation is inefficient because it must scan the entire // image for each region. It would be better to return an array of Arrays, // or a casacore::Matrix with the M array collapsed to 1D and the region # along the // other axis. casacore::IPosition pos(itsDim); casacore::IPosition start(itsDim,0); decrement(start); T I; if (itsDim == 2) { casacore::Matrix<T> M(3,3, 0.0); pos = start; while (increment(pos,shape())) { if (getCell(pos) == region) { I = getImageVal(pos); M(0,0) += I; M(1,0) += pos(0) * I; M(0,1) += pos(1) * I; } } T xc = M(1,0) / M(0,0); T yc = M(0,1) / M(0,0); pos = start; while (increment(pos,shape())) { if (getCell(pos) == region) { I = getImageVal(pos); T xd = pos(0) - xc; T yd = pos(1) - yc; M(1,1) += xd * yd * I; M(2,0) += xd * xd * I; M(0,2) += yd * yd * I; } } return M; //Does not calculate higher level components (2,1; 1,2; 2,2) } if (itsDim == 3) { casacore::Cube<T> M(3,3,3, 0.0); pos = start; while (increment(pos,shape())) { if (getCell(pos) == region) { I = getImageVal(pos); M(0,0,0) += I; M(1,0,0) += pos(0) * I; M(0,1,0) += pos(1) * I; M(0,0,1) += pos(2) * I; } } T xc = M(1,0,0) / M(0,0,0); T yc = M(0,1,0) / M(0,0,0); T zc = M(0,0,1) / M(0,0,0); pos = start; while (increment(pos,shape())) { if (getCell(pos) == region) { I = getImageVal(pos); T xd = pos(0) - xc; T yd = pos(1) - yc; T zd = pos(2) - zc; M(1,1,0) += xd * yd * I; M(1,0,1) += xd * zd * I; M(0,1,1) += yd * zd * I; M(2,0,0) += xd * xd * I; M(0,2,0) += yd * yd * I; M(0,0,2) += zd * zd * I; } } return M; } return casacore::Array<T>(); } // Region editing functions // ------------------------ template <class T> void ImageDecomposer<T>::boundRegions(casacore::Block<casacore::IPosition>& blc, casacore::Block<casacore::IPosition>& trc) { // Boxes each region in the componentmap: // blc is set to the lowest coordinate value in each region; // trc is set to one above the highest coordinate value in each region. DebugAssert(blc.nelements() == itsNRegions, casacore::AipsError); DebugAssert(trc.nelements() == itsNRegions, casacore::AipsError); for (casacore::uInt r=0; r<itsNRegions; r++) { blc[r] = itsShape; trc[r] = casacore::IPosition(itsDim,0); } { casacore::IPosition pos(itsDim,0); decrement(pos); casacore::Int r; while (increment(pos,shape())) { r = getCell(pos); if (r > 0) { for (casacore::uInt i = 0; i < itsDim; i++) { if (blc[r-1](i) > pos(i)) blc[r-1](i) = pos(i); if (trc[r-1](i) <= pos(i)) trc[r-1](i) = pos(i)+1; } } } } return; } template <class T> void ImageDecomposer<T>::zero() { // Set all elements in the component map to zero and clear the component list. itsMapPtr->set(0); itsNRegions = 0; itsNComponents = 0; itsList.resize(); return; } template <class T> void ImageDecomposer<T>::clear() { //Clear the component map casacore::LatticeIterator<casacore::Int> iter(*itsMapPtr); casacore::Bool deleteIt; casacore::Int* p = 0; for (iter.reset(); !iter.atEnd(); iter++) { casacore::Array<casacore::Int>& tmp = iter.rwCursor(); p = tmp.getStorage(deleteIt); for (casacore::uInt i=0; i<tmp.nelements(); i++) if (p[i] != MASKED) p[i] = 0; tmp.putStorage(p, deleteIt); } itsNRegions = 0; //Clear the component list itsNComponents = 0; itsList.resize(); return; } template <class T> void ImageDecomposer<T>::destroyRegions(const casacore::Vector<casacore::Bool>& killRegion) { //Wipes out any regions whose corresponding values in killRegion are true // by setting all pixel values in the componentmap set to that region to // zero. Zero-oriented; there is an offset of one between the index in // killRegion and the actual region in the componentmap. { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { casacore::Int reg = getCell(pos); if (reg > 0 && killRegion(reg-1)) setCell(pos,0); } } renumberRegions(); return; } template <class T> void ImageDecomposer<T>::renumberRegions() { //Eliminates redundant regions (components with no representative cells in //the component map) by renumbering higher-numbered regions to fill in //the gaps. For example.. // 011 011 // 113 becomes 112 // 113 112 casacore::Vector<casacore::Bool> regpresent(itsNRegions+1, 0); casacore::Vector<casacore::Int> renumregs(itsNRegions+1); casacore::uInt const ngpar = itsDim * 3; //any region that has any pixel members is flagged as 1, others left 0 { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { casacore::Int reg = getCell(pos); if (reg >= 0) regpresent(reg) = 1; } } //determine new # of regions and which regions will be renumbered to what casacore::uInt newnr = 0; for (casacore::uInt r = 1; r <= itsNRegions; r++) if (regpresent(r)) renumregs(r) = ++newnr; if (newnr < itsNRegions) { itsNRegions = newnr; //actually renumber the regions in the pmap { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { casacore::Int reg = getCell(pos); if (reg >= 0) setCell(pos, renumregs(reg)); } } if (isDecomposed()) { //eliminate componentlist entries of lost components casacore::Matrix<T> oldcomponentlist(itsList); itsList.resize(newnr, ngpar); for (casacore::Int c = 0; c < casacore::Int(itsNComponents); c++) if (regpresent(c+1) && (c+1 != renumregs(c+1))) for (casacore::uInt p = 0; p < 9; p++) itsList(renumregs(c+1)-1,p) = oldcomponentlist(c+1,p); itsNComponents = newnr; } } return; } template <class T> void ImageDecomposer<T>::synthesize(const ImageDecomposer<T>& subdecomposer, casacore::IPosition blc) { // Overlays a smaller map onto an empty region of a larger map, // and adds submap component list to main component list. // The user should exercise caution with this function and synthesize submaps // only into regions of the main map that are truly empty (0), because the // program does not perform any blending between components. // Otherwise, false detections are likely. casacore::uInt ngpar = 0; if (itsDim == 2) ngpar = 6; if (itsDim == 3) ngpar = 9; // Scan to the edge of the boundary of the host map or the submap, whichever // is closer. casacore::IPosition scanlimit(itsDim); //submap-indexed for (casacore::uInt i=0; i<itsDim; i++) { if (subdecomposer.shape(i) > shape(i) - blc(i)) { scanlimit(i) = shape(i) - blc(i); } else { scanlimit(i) = subdecomposer.shape(i); } } // Write pixels in sub- component map to main component map. { casacore::IPosition pos(itsDim,0); //submap-indexed decrement(pos); while (increment(pos,scanlimit)) { if (subdecomposer.getCell(pos) > 0) { setCell(pos + blc, itsNRegions + subdecomposer.getCell(pos)); } } } itsNRegions += subdecomposer.numRegions(); // Add components in subdecomposer to components in main decomposer. if (subdecomposer.isDecomposed()) { casacore::Matrix<T> oldList; oldList = itsList; itsList.resize(itsNComponents+subdecomposer.numComponents(),ngpar); for (casacore::uInt c = 0; c < itsNComponents; c++) { for (casacore::uInt p = 0; p < ngpar; p++) { itsList(c,p) = oldList(c,p); //copy after resize } } for (casacore::uInt subc = 0; subc < subdecomposer.numComponents(); subc++) { for (casacore::uInt p = 0; p < ngpar; p++) { itsList(itsNComponents+subc,p)=subdecomposer.itsList(subc,p); } // make adjustments to center values due to offset if (itsDim == 2) { itsList(itsNComponents+subc,1) += blc(0); itsList(itsNComponents+subc,2) += blc(1); } else if (itsDim == 3) { itsList(itsNComponents+subc,1) += blc(0); itsList(itsNComponents+subc,2) += blc(1); itsList(itsNComponents+subc,3) += blc(2); } } itsNComponents += subdecomposer.numComponents(); } // //renumberRegions(); //this should have no effect unless this function //was used to overwrite an entire region, which //should be avoided. } // Analysis functions // ------------------ template <class T> casacore::uInt ImageDecomposer<T>::identifyRegions(T thrval, casacore::Int naxis) { // Performs a single threshold scan on the image. In other words, // identifies all contigous blocks of pixels in the target image above the // threshold value thrval, assigning each unique block to an integer, // starting at one. All pixels with target image values below thrval are set // to zero. // NOTE: Formerly a specialization existed for 2D which may have been slightly // more efficient. However, it complicated the code excessively (this // program is already far too long.) It could be resurrected if necessary. casacore::Int const blocksize = 1024; //increment to grow size of anchor array casacore::Int const pageexpsize = 128; casacore::Int cnum = 0; //region number if (naxis > casacore::Int(itsDim)) naxis = itsDim; // The program first scans through the image until it finds any pixel in // any region. Once there, it immediately scans all 6 adjacent pixels, // registering any that fall within the region and setting them as anchors. // It then moves to the first of these anchors and repeats the process, // until no more anchors can be found and every existing anchor has already // been explored. It then scans until it locates a new region, and repeats // until every region has been similarly scanned. // This has the equivalent effect of a 'surface' of active anchors // sweeping across each region starting at a seed point until the // region is fully explored. // The naxis parameter must be 2 or greater to avoid spurious detections // along horizontal ridges. However, this slows down performance by a factor // of roughly two, so in instances where objects are clearly defined and // not closely blended and the image is very large naxis=1 may be better. casacore::IPosition scanpos(itsDim,0); //decrement(scanpos); while (true) { //First find any pixel in next region. //Stop scanning when an unassigned, unmasked pixel is found exceeding //the threshold value. while (getImageVal(scanpos) < thrval || getCell(scanpos)) { if (!increment(scanpos,shape())) { return itsNRegions = cnum; } //scanned w/out finding new region } // casacore::IPosition pos(scanpos); cnum++; // As many anchors will be required as pixels in the region (the volume) - // but only a small fraction of that at any one time (the active surface). // So the program allocates 'pages' of anchors as they become necessary, // but continually deletes pages of anchors that have already been used. casacore::Int seta = -1; //index of highest established anchor casacore::Int geta = -1; //index of highest analyzed anchor //the active surface is all anchors geta < a < seta casacore::PtrBlock<casacore::Matrix<casacore::Int> *> aindex(pageexpsize); //anchor structure casacore::Int setblock = -1; //index of page containing highest established anchor casacore::Int getblock = -1; //index of page containing highest analyzed anchor setCell(pos,cnum); do { //(geta < seta) //cout << geta << " / " << seta << ", " << pos << // " = " << getCell(pos) << endl; //Analyze the cell - //Scan the cells around the active cell as follows: //naxis = 1: scan 6 adjacent cells (axes only) //naxis = 2: scan 18 adjacent cells (axes and 2-axis diagonals) //naxis = 3: scan 26 adjacent cells (axes and 2/3-axis diagonals) casacore::Int ximin = (pos(0)>0)? -1:0; casacore::Int yimin = (pos(1)>0)? -1:0; casacore::Int zimin = ((itsDim>=3)&&(pos(2)>0))? -1:0; casacore::Int ximax = (pos(0)+1<shape(0))? 1:0; casacore::Int yimax = (pos(1)+1<shape(1))? 1:0; casacore::Int zimax = ((itsDim>=3)&&(pos(2)+1<shape(2)))? 1:0; //safe for 2D // for (casacore::Int xi=ximin; xi<=ximax; xi++) { for (casacore::Int yi=yimin; yi<=yimax; yi++) { for (casacore::Int zi=zimin; zi<=zimax; zi++) { if ( (xi || yi || zi) && ((naxis > 1) || !((xi && yi) || (xi && zi) || (yi && zi) )) && ((naxis > 2) || !(xi && yi && zi))) { casacore::IPosition ipos(pos); ipos(0) += xi; ipos(1) += yi; if (itsDim == 3) ipos(2) += zi; //if any contiguous pixel is if ((getImageVal(ipos) >= thrval) // above threshold and && getCell(ipos) != cnum) { // not yet scanned... //record its location as an anchor and come back later. seta++; if ((seta % blocksize) == 0) { //current block out of memory: allocate new memory block setblock++; if ((setblock % pageexpsize == 0) && setblock) { aindex.resize(((setblock/pageexpsize)+1)*pageexpsize); } aindex[setblock] = new casacore::Matrix<casacore::Int>(blocksize,itsDim); } //set new anchor for (casacore::uInt axis = 0; axis < itsDim; axis ++) { (*(aindex[setblock]))(seta%blocksize,axis) = ipos(axis); } //cout<<'A'<<seta<<'['<<x+xi<<','<<y+yi<<','<<z+zi<<']'<<endl; setCell(ipos, cnum); } } } } } geta++; if (((geta % blocksize) == 0) || (geta>seta)) { if (getblock>=0) { //memory block data obsolete: delete old memory block delete aindex[getblock]; } getblock++; } if (geta <= seta) { //go to lowest anchor for (casacore::uInt axis = 0; axis < itsDim; axis++) { pos(axis) = (*(aindex[getblock]))(geta%blocksize,axis); } //cout << ">>A" << geta << " " << endl; } } while (geta <= seta); } } template <class T> void ImageDecomposer<T>::deblendRegions(const casacore::Vector<T>& contours, casacore::Int minRange, casacore::Int naxis) { // Performs the contour decomposition on a blended image to generate a // component map that can detect components blended above any threshold(s), // by performing threshold scans at each contour level and recognizing // as individual any components that are distinct above any such level. casacore::Int const printIntermediates = 0; casacore::Int const blocksize = 3; casacore::Int ncontours = contours.nelements(); ImageDecomposer<T> contourMap(*this);//Component map thresholded at current //contour level; "lower" contour casacore::Block<casacore::IPosition> regcenter; //Coordinates of first pixel found in each //component (a rough estimate of the center) //Indexing for this array is offset by one. casacore::Vector<casacore::Int> originlevel; //first contour in which region was detected //Indexing for this array is offset by one. //If set to -1 the region is defunct. if (isDerived()) zero(); // Component decomposition: // This is the main deblending algorithm. The program starts by performing // a threshold scan at the top contour value, separating any regions // that are visibly distinct at that threshold. It continues downward though // the contour vector, forming a component map from each contour and comparing // that with the contour map that is gradually forming to distinguish new // regions, correspondent regions, and blending regions, and assigns the // pixels in the main itsMapPtr based on this information. for (casacore::Int c = ncontours-1; c >= 0; c--) { if (printIntermediates == 2) cout << endl << "CONTOUR " << c << endl; casacore::Int lowreg, highreg; //number of regions in lower contour, current pmap contourMap.clear(); //only necessary if region grows between contours lowreg = contourMap.identifyRegions(contours(c),naxis); highreg = itsNRegions; if (printIntermediates) { cout << "Now comparing current pmap to contour " << c << '.' << endl; display(); contourMap.display(); } casacore::Vector<casacore::Int> root(highreg+1, 0); //Indicates corresponding object // in lower contour casacore::Vector<casacore::Int> nOffspring(lowreg+1, 0); //Total number of different objects // above this region casacore::Block<casacore::Vector<casacore::Int>*> offspring(lowreg+1); //Region IDs of objects above // this region // Can't finish allocation until nOffspring is known. // Scan through current pmap ("higher contour") and find the root of all // regions as defined in the lower contour. Simultaneously mark all // regions in the lower contour that have "offspring" above. for (casacore::Int r = 1; r <= highreg; r++) { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape()) && !root(r)){ if (getCell(pos) == r) { root(r) = contourMap.getCell(pos); nOffspring(contourMap.getCell(pos)) += 1; } } } // Set up offspring array for (casacore::Int r = 1; r <= lowreg; r++) { offspring[r] = new casacore::Vector<casacore::Int>(nOffspring(r)); } for (casacore::Int lr = 1; lr <= lowreg; lr++) { casacore::Int f = 0; for (casacore::Int hr = 1; hr <= highreg; hr++) { if (root(hr) == lr) (*offspring[lr])(f++) = hr; } } if (printIntermediates == 2) { cout << "Contour Level " << c << endl; cout << highreg << ' ' << lowreg << endl; for (casacore::Int hr = 1; hr <= highreg; hr++) { cout << "root of " << hr << ':' << root(hr) << endl; } for (casacore::Int lr = 1; lr <= lowreg; lr++) { for (casacore::Int f = 0; f < nOffspring(lr); f++) { cout << "offspring of " << lr << ':' << (*offspring[lr])(f) << endl; } } } // If a newly discovered component merges with another too quickly // (within minRange contours) rescind its status and treat it as part // of the other contour. // "f" refers somewhat cryptically to offspring index and "fr" to its // region number in the pmap if (minRange > 1) { for (casacore::Int lr = 1; lr <= lowreg; lr++) { if (nOffspring(lr) > 1) { for (casacore::Int f = 0; f < nOffspring(lr); f++) { casacore::Int fr = (*offspring[lr])(f); if (originlevel(fr-1) - c < minRange) { //Find the closest offspring casacore::Int mindistsq = 1073741823; //maximum casacore::Int value casacore::Int frabs = 0; //closest region for (casacore::Int f2 = 0; f2 < nOffspring(lr); f2++) { if (f2 == f) continue; casacore::Int fr2 = (*offspring[lr])(f2); casacore::Int distsq = 0; if (originlevel(fr2-1) == -1) continue; for (casacore::uInt a = 0; a < itsDim; a++) { distsq += casacore::square(casacore::Int(regcenter[fr2-1](a)-regcenter[fr-1](a))); } if (distsq < mindistsq) { frabs = fr2; mindistsq = distsq; } } if (frabs == 0) { //No valid closest offspring - find biggest offspring for (casacore::Int f2 = 0; f2 < nOffspring(lr); f2++) { if (f2 == f) continue; casacore::Int fr2 = (*offspring[lr])(f2); casacore::Int maxlevel = 0; if (originlevel(fr2-1) == -1) continue; if (originlevel(fr2-1) > maxlevel) { frabs = fr2; maxlevel = originlevel(fr2-1); } } } if (frabs == 0) { //must be the only offspring left! Don't absorb. break; } if (printIntermediates == 2) { cout << "Absorbing region " << fr << " (origin at " << originlevel(fr-1) << ") into region " << frabs << endl; } casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())){ if (getCell(pos) == fr) setCell(pos,frabs); } originlevel(fr-1) = -1; } } } } } // Need to check if this works properly... // The object/region/component labeling is done in three steps to make the // order most closely match the order in which they were found, and the // order of the peak intensities. // First (and most complex) step is to deblend the large consolidated // regions (nOffspring >= 2). The algorithm scans all pixels that // exist in the new contour but not in the current pmap and belong to // a region with multiple offspring, then scans the surrounding vicinity // in the current pmap until it finds a pixel belonging to a region there, // to which the new pixel is assigned. casacore::Int cgindex = 1; //component index base, global for this contour ImageDecomposer<T> copyMap(*this); //The original data must be temporarily //preserved while the componentmap is //overwritten for comparisons between //nearby pixels for (casacore::Int lr = 1; lr <= lowreg; lr++) { if (nOffspring(lr) >= 2) { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { // Renumber pixels that already exist in the pmap if ((contourMap.getCell(pos)==lr)&&(copyMap.getCell(pos))) { for (casacore::Int f = 0; f < nOffspring(lr); f++) { // Translate old pmap id to new pmap id if ((*offspring[lr])(f) == copyMap.getCell(pos)) setCell(pos, cgindex+f); } } // Number new pixels. if ((contourMap.getCell(pos)==lr)&&(!copyMap.getCell(pos))) { casacore::Int pinh = 0; //region as defined in pmap pixel is set to casacore::Int srad = 1; //search radius casacore::uInt naxis = 1; // cout << "Searching for nearest cell to " << pos << endl; while(!pinh && srad < 250) { //search increasing naxis, srad // IMPR: an N-dimensional structure would be better here. casacore::Int xi, yi, zi; casacore::Int ximin = (pos(0)-srad < 0)? -pos(0) : -srad; casacore::Int ximax = (pos(0)+srad >= shape(0))? shape(0)-pos(0)-1 : srad; casacore::Int yimin = (pos(1)-srad < 0)? -pos(1) : -srad; casacore::Int yimax = (pos(1)+srad >= shape(1))? shape(1)-pos(1)-1 : srad; casacore::Int zimin = 0, zimax = 0; if (itsDim == 2) { zimin = 0; zimax = 0; } if (itsDim >= 3) { zimin = (pos(2)-srad < 0)? -pos(2) : -srad; zimax = (pos(2)+srad >= shape(2))? shape(2)-pos(2)-1 : srad; } while (!pinh && naxis <= itsDim) { for (xi = ximin; xi <= ximax; xi++) { for (yi = yimin; yi <= yimax; yi++) { for (zi = zimin; zi <= zimax; zi++) { casacore::IPosition ipos(pos); ipos(0) += xi; ipos(1) += yi; if (itsDim==3) ipos(2) += zi; if (abs(xi)<srad && abs(yi)<srad && abs(zi)<srad) { continue; //border of radius only } if ( ((naxis==1) && ((xi&&yi) || (xi&&zi) || (yi&&zi))) || ((naxis==2) && (xi && yi && zi))) { continue; } casacore::Int inh = copyMap.getCell(ipos); if (inh<=0) continue; if (!pinh) { pinh = inh; for (casacore::Int f = 0; f < nOffspring(lr); f++) { // Translate old pmap id to new pmap id if ((*offspring[lr])(f) == inh) { setCell(pos, cgindex+f); } //reassign pixel to new component } } else if (pinh!=inh) { //equidistant to different objects: // temporarily flag as nonexistant object setCell(pos, INDETERMINATE); } } } } naxis++; } naxis = 1; srad++; } } } cgindex += nOffspring(lr); } } // Now scan nonforked regions that exist in both contours. // This is as simple as just renumbering the region. for (casacore::Int lr = 1; lr <= lowreg; lr++) { if (nOffspring(lr) == 1) { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if (contourMap.getCell(pos) == lr) setCell(pos, cgindex); } cgindex++; } } //IMPR: probably can make this work with single scan //similar to above algorithm // Finally, scan regions that only exist in lower contour. Same as above, // but since these are new regions, add their initial positions to the seed // arrays and increment the region count. for (casacore::Int lr = 1; lr <= lowreg; lr++) { if (nOffspring(lr) == 0) { casacore::IPosition newregioncenter(itsDim,0); casacore::uInt topcells = 0; { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { //cout << pos << endl; if (contourMap.getCell(pos) == lr) { setCell(pos, cgindex); newregioncenter += pos; topcells++; } } } newregioncenter /= topcells; //note: integer division. may or may not //want to keep this cgindex++; if ((itsNRegions % blocksize) == 0) { regcenter.resize(itsNRegions+blocksize, true); originlevel.resize(itsNRegions+blocksize, true); } // Add to region center array regcenter[itsNRegions] = newregioncenter; originlevel(itsNRegions) = c; itsNRegions++; } } } // At end of scan, assign all flagged pixels to region containing the // nearest "seed" - the first point identified in each region. if (printIntermediates == 2) { cout << "Located the following seeds:" << endl; for (casacore::uInt s = 0; s < itsNRegions; s++) cout << s << " at " << regcenter[s] << " in component #" << getCell(regcenter[s]) << endl; } { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if (getCell(pos) == INDETERMINATE) { casacore::Int mindistsq = 1073741823; //maximum casacore::Int value for (casacore::uInt s = 0; s < itsNRegions; s++) { casacore::Int distsq = 0; if (originlevel(s) == -1) continue; //defunct region for (casacore::uInt a = 0; a < itsDim; a++) { distsq += (pos(a) - regcenter[s](a)) * (pos(a) - regcenter[s](a)); } if (distsq < mindistsq) { setCell(pos, getCell(regcenter[s]) ); mindistsq = distsq; } } } } } if (minRange > 1) renumberRegions(); return; } template <class T> void ImageDecomposer<T>::decomposeImage() { if (!itsDeblendIt) { // Find contiguous regions via thresholding casacore::uInt nRegions = identifyRegions(itsThresholdVal,itsNAxis); cout << "Found " << nRegions << " regions" << endl; // Fit each region. A further pass is done through each region // to ascertain whether there are multiple components to be // fit within it. if (itsFitIt) fitRegions(); } else { const casacore::Bool showProcess = false; casacore::Bool varyContours = false; //this is always false now... // Make a local decomposer ImageDecomposer<T> thresholdMap(*itsImagePtr); // Generate global contours casacore::Vector<T> mainContours(itsNContour); if (!varyContours) { mainContours = autoContour(itsNContour, itsThresholdVal); //cerr << "Main contours = " << mainContours << endl; if (showProcess) displayContourMap(mainContours); } // Find contiguous regions via thresholding casacore::uInt nRegions = thresholdMap.identifyRegions(itsThresholdVal, itsNAxis); casacore::uInt nBadRegions = 0; if (itsMinRange > 1) { // Eliminate weak regions casacore::Vector<T> maxvals; maxvals = thresholdMap.findAllRegionGlobalMax(); casacore::Vector<casacore::Bool> killRegion(nRegions,0); for (casacore::uInt r = 0; r < nRegions; r++) { if (thresholdMap.getContourVal(maxvals(r),mainContours)+1 <itsMinRange) { killRegion(r) = 1; nBadRegions++; } } thresholdMap.destroyRegions(killRegion); } nRegions -= nBadRegions; if (showProcess) { cout << "Located " << nRegions << " regions above threshold " << itsThresholdVal << "." << endl; thresholdMap.display(); } // Find a blc and a trc for each region casacore::Block<casacore::IPosition> blc(nRegions); casacore::Block<casacore::IPosition> trc(nRegions); thresholdMap.boundRegions(blc, trc); if (isDerived()) zero(); if (showProcess) { cout << "Bounded " << nRegions<<" regions for decomposition and fitting:" << endl; for (casacore::uInt r = 0; r < nRegions; r++) { cout << r+1 <<": " << blc[r] << trc[r] << endl; } } // For each distinct region above the threshold, form a componentmap // (subcomponentmap) and perform the fitting. Regions are treated as // independent entities by the fitter - even if one region contains a // very high-sigma component that may extend into another region, this // is not taken into account by the other region for (casacore::uInt r=0; r<nRegions; r++) { // Make a decomposer for this region casacore::Slicer sl(blc[r], trc[r]-blc[r], casacore::Slicer::endIsLength); casacore::SubImage<T> subIm(*itsImagePtr, sl); ImageDecomposer<T> subpmap(subIm); subpmap.copyOptions(*this); // Flag pixels outside the target region (this makes sure that other // regions that happen to overlap part of the target region's bounding // rectangle are not counted twice, and that only the target region // pixels are used in fitting.) { casacore::IPosition pos(subpmap.itsDim,0); decrement(pos); while (increment(pos,subpmap.shape())) { if (thresholdMap.getCell(blc[r] + pos) != casacore::Int(r+1)) { subpmap.setCell(pos, MASKED); } } } casacore::Vector<T> subContours(itsNContour); casacore::Vector<T> *contourPtr; // Generate contours for this region or use global // ones for entire image if (varyContours) { subContours = subpmap.autoContour(itsNContour, itsThresholdVal); contourPtr = &subContours; } else { contourPtr = &mainContours; } if (showProcess) { cout << "-----------------------------------------" << endl; cout << "Subimage " << r << endl; cout << "Contour Map:" << endl; subpmap.displayContourMap(*contourPtr); } // Deblend components in this region subpmap.deblendRegions(*contourPtr, itsMinRange, itsNAxis); if (showProcess) { cout << "Component Map:" << endl; subpmap.display(); } if (itsFitIt) { // Fit gaussians to region subpmap.fitComponents(); } else { // Just estimate subpmap.itsNComponents = subpmap.itsNRegions; subpmap.itsList.resize(); subpmap.itsList = subpmap.estimateComponents(); } if (showProcess) { cout << "Object " << r+1 << " subcomponents: " << endl; subpmap.printComponents(); cout << endl; } // Add this region back into the main component map synthesize(subpmap, blc[r]); } } return; } // Fitting functions // ----------------- template <class T> casacore::Matrix<T> ImageDecomposer<T>::fitRegion(casacore::Int nregion) { cout << "Fit Region " << nregion << endl; // Fits multiple gaussians to a single region. First performs a local // maximum scan to estimate the number of components in the region. casacore::uInt nGaussians = 0; casacore::uInt npoints = 0; casacore::uInt ngpar = 0; if (itsDim == 2) ngpar = 6; if (itsDim == 3) ngpar = 9; if (!isDerived()) nregion = 0; //fit to all data. // Determine number of data points in the region { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if (getCell(pos) == nregion) npoints++; } } // Fill data and positions arrays casacore::Matrix<T> positions(npoints,itsDim); casacore::Vector<T> dataValues(npoints); casacore::Vector<T> sigma(npoints); sigma = 1.0; casacore::uInt k = 0; { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if (getCell(pos) == nregion) { for (casacore::uInt i = 0; i<itsDim; i++) { positions(k,i) = T(pos(i)); } dataValues(k) = getImageVal(pos); k++; } } } // Estimate the initial parameters. casacore::Matrix<T> width; casacore::Vector<T> maxval; casacore::Block<casacore::IPosition> maxvalpos; // This estimates whether there are multiple components // in the current region or not. findRegionLocalMax(maxval, maxvalpos, nregion, 2); estimateComponentWidths(width, maxvalpos); nGaussians = maxval.nelements(); cout << "Found " << nGaussians << " components" << endl; casacore::Matrix<T> initestimate(nGaussians, ngpar); casacore::Matrix<T> solution(nGaussians, ngpar); if (itsDim == 2) { for (casacore::uInt r = 0; r < nGaussians; r++) { initestimate(r,0) = maxval(r); initestimate(r,1) = maxvalpos[r](0); initestimate(r,2) = maxvalpos[r](1); initestimate(r,3) = width(r,1); initestimate(r,4) = width(r,0)/width(r,1); initestimate(r,5) = 0; } } if (itsDim == 3) { for (casacore::uInt r = 0; r < nGaussians; r++) { initestimate(r,0) = maxval(r); initestimate(r,1) = maxvalpos[r](0); initestimate(r,2) = maxvalpos[r](1); initestimate(r,3) = maxvalpos[r](2); initestimate(r,4) = width(r,0); initestimate(r,5) = width(r,1); initestimate(r,6) = width(r,2); initestimate(r,7) = (0.0); initestimate(r,8) = (0.0); } } // Fit for nGaussians simultaneously solution = fitGauss(positions, dataValues, initestimate); return solution; } template <class T> void ImageDecomposer<T>::fitRegions() { // Fits gaussians to an image; multiple gaussians per region in the pmap. // The regions are fit sequentially and independently, so this function // can be used on the main image. // If the map is not yet thresholded, will fit to the entire image as if it // were a single composite object, which will be very slow. casacore::uInt ngpar = 0; if (itsDim == 2) ngpar = 6; if (itsDim == 3) ngpar = 9; if (itsNRegions == 0) { //not deblended. itsList = fitRegion(0); return; } // for (casacore::uInt r = 1; r <= itsNRegions; r++) { casacore::Matrix<T> subitsList; casacore::Matrix<T> olditsList; subitsList = fitRegion(r); olditsList = itsList; itsList.resize(itsNComponents + subitsList.nrow(), ngpar); // for (casacore::uInt c = 0; c < itsNComponents; c++) { for (casacore::uInt p = 0; p < ngpar; p++) { itsList(c,p) = olditsList(c,p); } } for (casacore::uInt subc = 0; subc < subitsList.nrow(); subc++) { for (casacore::uInt p = 0; p < ngpar; p++) { itsList(itsNComponents+subc, p) = subitsList(subc, p); } } itsNComponents += subitsList.nrow(); } return; } template <class T> void ImageDecomposer<T>::fitComponents() { // Fits gaussians to an image; one gaussian per region in the pmap. // This function is intended to be used only by ImageDecomposer on its // intermediary subimages; using it at higher level will execute a full // gaussian fit on the main image and will be extremely slow. Every // nonflagged object pixel in the image is used in fitting. // If the deblended flag is true, the function will treat each region as // an individual component and will fit that many gaussians to the image casacore::uInt ngpar = itsDim * 3; casacore::uInt npoints = 0; if (!isDerived()) { throw(casacore::AipsError("Cannot fit until components are deblended" " - use identifyRegions() or deblendRegions()")); } // Determine number of data points in all the regions // and get data and position vectors { casacore::IPosition pos(itsDim,0); decrement(pos); while (increment(pos,shape())) { if (getCell(pos) > 0) npoints++; } } casacore::Matrix<T> positions(npoints,itsDim); casacore::Vector<T> dataValues(npoints); { casacore::IPosition pos(itsDim,0); decrement(pos); casacore::uInt p = 0; while (increment(pos,shape())) { if (getCell(pos) > 0) { for (casacore::uInt i=0; i<itsDim; i++) { positions(p,i) = T(pos(i)); } dataValues(p) = getImageVal(pos); p++; } } } // Estimate the initial parameters. casacore::Matrix<T> initestimate(itsNRegions, ngpar); casacore::Matrix<T> solution(itsNRegions, ngpar); initestimate = estimateComponents(); solution = fitGauss(positions, dataValues, initestimate); itsNComponents = itsNRegions; itsList.resize(solution.shape()); itsList = solution; return; } template <class T> casacore::Matrix<T> ImageDecomposer<T>::estimateComponents() { casacore::uInt ngaussians = itsNRegions; casacore::uInt ngpar = 0; if (itsDim == 2) ngpar = 6; if (itsDim == 3) ngpar = 9; if (!isDerived()) { throw(casacore::AipsError("Cannot estimate until components are deblended" " - use identifyRegions() or deblendRegions()")); } casacore::Matrix<T> estimate(ngaussians, ngpar); casacore::Matrix<T> width; casacore::Vector<T> maxval; casacore::Block<casacore::IPosition> maxvalpos; ngaussians = itsNRegions; findAllRegionGlobalMax(maxval, maxvalpos); // This is based on the moment analysis methods given in Jarvis & Tyson 1981, // though they have been generalized to 3D. The formula to estimate phi // (3D parameter 8) was not derived rigorously and may not be correct, though // it works very well overall. for (casacore::uInt r = 0; r < ngaussians; r++) { if (itsDim == 2) { casacore::Matrix<T> M; M = calculateMoments(r+1); estimate(r,0) = maxval[r]; estimate(r,1) = M(1,0) / M(0,0); estimate(r,2) = M(0,1) / M(0,0); estimate(r,3) = 2.84 * sqrt(M(0,2)/M(0,0)); estimate(r,4) = sqrt(M(2,0)/M(0,2)); estimate(r,5) = 0.5 * atan(2 * M(1,1) / (M(2,0) - M(0,2))); if (estimate(r,3) < 1.0) estimate(r,3) = 1.0; if (estimate(r,4)*estimate(r,3) < 1.0) estimate(r,4) = 1.0/estimate(r,3); if (casacore::isNaN(estimate(r,4))) estimate(r,4) = 1.0/estimate(r,3); if (casacore::isNaN(estimate(r,5))) estimate(r,5) = 0; } else if (itsDim == 3) { casacore::Cube<T> M; M = calculateMoments(r+1); estimate(r,0) = maxval[r]; estimate(r,1) = M(1,0,0) / M(0,0,0); estimate(r,2) = M(0,1,0) / M(0,0,0); estimate(r,3) = M(0,0,1) / M(0,0,0); estimate(r,4) = 2.84 * sqrt(M(2,0,0) / M(0,0,0)); estimate(r,5) = 2.84 * sqrt(M(0,2,0) / M(0,0,0)); estimate(r,6) = 2.84 * sqrt(M(0,0,2) / M(0,0,0)); estimate(r,7) = 0.5 * atan(2 * M(1,1,0) / (M(2,0,0) - M(0,2,0))); estimate(r,8) = -0.5 * atan(2 * M(1,0,1) / ((M(2,0,0) - M(0,0,2))*cos(estimate(r,7)) + (M(0,2,0) - M(0,0,2))*sin(estimate(r,7)))); if (estimate(r,4) < 1.0) estimate(r,4) = 1.0; if (estimate(r,5) < 1.0) estimate(r,5) = 1.0; if (estimate(r,6) < 1.0) estimate(r,6) = 1.0; if (casacore::isNaN(estimate(r,8))) estimate(r,8) = 0; } } return estimate; } template <class T> casacore::Matrix<T> ImageDecomposer<T>::fitGauss(const casacore::Matrix<T>& positions, const casacore::Vector<T>& dataValues, const casacore::Matrix<T>& initestimate) const { // Fits the specified number of 3D gaussians to the data, and returns // solution in image (world) coordinates. //casacore::uInt ngpar = 0; casacore::uInt ngaussians = initestimate.nrow(); //if (itsDim == 2) ngpar = 6; //if (itsDim == 3) ngpar = 9; casacore::Matrix<T> solution; //T chisquare; // Might be useful to send to screen in AIPS++ //cout << "Primary estimation matrix:" << endl; //for (casacore::uInt r = 0; r < ngaussians; r++) // cout << initestimate.row(r) << endl; casacore::FitGaussian<T> fitter(itsDim,ngaussians); fitter.setFirstEstimate(initestimate); if (itsMaxRetries < 0) { fitter.setMaxRetries(itsDim * ngaussians); } else { fitter.setMaxRetries(itsMaxRetries); } try{ solution = fitter.fit(positions, dataValues, itsMaximumRMS, itsMaxIter, itsConvCriteria); } catch (casacore::AipsError fiterr) { cout << fiterr.getMesg() << endl; cout << "Fitting failed." << endl; solution = 0; //chisquare = -1.0; return solution; } if (fitter.converged()) { //chisquare = fitter.chisquared(); } else { cout << "Fitting did not converge to a reasonable result - using estimate." << endl; solution = initestimate; //chisquare = -1.0; return solution; } return solution; } template <class T> void ImageDecomposer<T>::display() const { // Displays the componentmap in a terminal environment as an array of // characters on screen. casacore::Int windowwidth = 80; casacore::Int const spacing = 4; casacore::Int const cposinc = shape(0) * 2 + spacing; if (cposinc > windowwidth) windowwidth = cposinc; casacore::Int cpos = 0; // casacore::Int z = 0; casacore::Int benchz = 0; // //cerr << "shape = " << shape() << endl; while (z < shape(2)) { for (casacore::Int y = 0; y < shape(1); y++) { z = benchz; while ((cpos += cposinc) < windowwidth && z < shape(2)) { for (casacore::Int x = 0; x < itsShape(0); x++) { if (getCell(x,y,z) >= 0) { cout << getCell(x,y,z); } else if (getCell(x,y,z) == INDETERMINATE) { cout << '*'; } else if (getCell(x,y,z) == MASKED) { cout << '-'; } if (getCell(x,y,z) < 10) { cout << ' '; } } z++; cout << " "; } cpos = 0; cout << endl; } benchz = z; cout << endl; } return; } // Console display functions // ------------------------- template <class T> void ImageDecomposer<T>::displayContourMap(const casacore::Vector<T>& clevels) const { // Displays the target image as a contourmap in a terminal environment as // an array of characters on screen. casacore::Int windowwidth = 80; casacore::Int const spacing = 4; casacore::Int const cposinc = shape(0) * 2 + spacing; if (cposinc > windowwidth) windowwidth = cposinc; casacore::Int cpos = 0; casacore::Int z = 0; casacore::Int benchz = 0; cout << "Contour levels:" << clevels << endl; if (itsDim == 2) { for (casacore::Int y = 0; y < shape(1); y++) { for (casacore::Int x = 0; x < shape(0); x++) { if (getContourVal(x,y,clevels) >= 0) { cout << getContourVal(x,y,clevels); } else if (getContourVal(x,y,clevels) <= -1) { cout << '-'; } if (getContourVal(x,y,clevels) < 10) { cout << ' '; } } cout << endl; } cout << endl; } if (itsDim == 3){ //this actually works in 2 dimensions on a casacore::TempImage, but not on a //casacore::SubImage, where there is a failure inside the getImageVal command //on a failed assertion involving a casacore::LatticeRegion object. As a result //the above specialization was written, but it would be nice if 3-D //IPositions worked on 2-D images in casacore::SubImage as well as TempImage. while (z < shape(2)) { for (casacore::Int y = 0; y < shape(1); y++) { z = benchz; while ((cpos += cposinc) < windowwidth && (z < shape(2))) { for (casacore::Int x = 0; x < shape(0); x++) { if (getContourVal(x,y,z,clevels) >= 0) { cout << getContourVal(x,y,z,clevels); } else if (getContourVal(x,y,z,clevels) <= -1) { cout << '-'; } if (getContourVal(x,y,z,clevels) < 10) { cout << ' '; } } z++; cout << " "; } cpos = 0; cout << endl; } benchz = z; cout << endl; } cout << endl; } return; } template <class T> void ImageDecomposer<T>::printComponents() const { //Prints the components as formatted output on screen. //IMPR: Probably could be modified as an ostream output function. casacore::Matrix<T> clist; clist = componentList(); for (casacore::uInt g = 0; g < clist.nrow(); g++) { cout << g+1 << ": "; if (itsList(g,0) == T(0)) { cout << "Failed"; } else { cout << "Peak: " << clist(g,0) << " "; if (itsDim == 2) { cout << "Mu: [" << clist(g,1) << ", " << clist(g,2) << "] "; cout << "Axes: [" << clist(g,3) << ", " << clist(g,3) * clist(g,4) << "] "; cout << "Rotation: " << clist(g,5) /* * 180 / C::pi */; } if (itsDim == 3) { cout << "Mu: [" << clist(g,1) << ", " << clist(g,2) << ", " << clist(g,3) << "] "; cout << "Axes: [" << clist(g,4) << ", " << clist(g,5) << ", " << clist(g,6) << "] "; cout << "Rotation: [" << clist(g,7)/* *180/C::pi */ << ", " << clist(g,8) /* *180/C::pi */ << "]"; } } cout << endl; } return; } // Functions associated only with other classes // -------------------------------------------- template <class T> void ImageDecomposer<T>::correctBlcTrc(casacore::IPosition& blc, casacore::IPosition& trc) const { //Ensures blc and trc correctly describe the limits of a rectangular block. casacore::Int t; for (casacore::uInt i = 0; i<itsDim; i++) { if (blc(i)<0) blc(i) = 0; if (trc(i)>shape(i)) trc(i) = shape(i); if (trc(i)<0) trc(i) = 0; // if (blc(i)>shape(i)) blc(i) = shape(i); if (blc(i)>trc(i)) { // nebk - ERROR should be trc(a) not trc(0) ?? t = blc(i); blc(i) = trc(i); trc(i) = t; } } return; } template <class T> inline casacore::Bool ImageDecomposer<T>::increment(casacore::IPosition& pos, const casacore::IPosition& limit) const { // N-Dimensional looping function: use in place of nested for loops // Returns false when pos reaches limit. // Use as follows: while(increment(pos,limit)) // IMPR: this function probably should be global or in IPosition. Or even // better, omitted completely by using LatticeIterators. pos(itsDim-1)++; for (casacore::uInt i = itsDim-1; i>0; i--) { if (pos(i) == limit(i)) { pos(i) = 0; pos(i-1)++; } else { return true; } } // if (pos(0) == limit(0)) { return false; } else { return true; } } template <class T> inline void ImageDecomposer<T>::decrement(casacore::IPosition& pos) const { //To ensure while loop starts at 0,0,0..., decrement before first increment pos(itsDim-1)--; } /* casacore::RO_LatticeIterator<casacore::Int> otherIter(*(other.itsMapPtr)); casacore::Bool deleteOther, deleteThis; const casacore::Int* pOther = 0; casacore::Int* pThis = 0; for (otherIter.reset(); !otherIter.atEnd(); otherIter++) { const casacore::Array<casacore::Int>& otherArray = otherIter.cursor(); casacore::Array<casacore::Int> thisArray = itsMapPtr->getSlice(otherIter.position(), otherIter.cursorShape()); // pOther = otherArray.getStorage(deleteOther); pThis = thisArray.getStorage(deleteThis); // for (casacore::uInt i=0; i<otherIter.cursor().nelements(); i++) { if (pOther[i] != regionID) pThis[i] = MASKED; } // otherArray.freeStorage(pOther, deleteOther); thisArray.putStorage(pThis, deleteThis); } */ } //# NAMESPACE CASA - END