lsst.meas.algorithms  14.0-21-ge7d40960+4
SpatialModelPsf.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008, 2009, 2010 LSST Corporation.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <http://www.lsstcorp.org/LegalNotices/>.
23  */
24 
32 #include <numeric>
33 
34 #if !defined(DOXYGEN)
35 # include "Minuit2/FCNBase.h"
36 # include "Minuit2/FunctionMinimum.h"
37 # include "Minuit2/MnMigrad.h"
38 # include "Minuit2/MnMinos.h"
39 # include "Minuit2/MnPrint.h"
40 #endif
41 
42 #include "Eigen/Core"
43 #include "Eigen/Cholesky"
44 #include "Eigen/SVD"
45 
49 #include "lsst/afw/geom/Point.h"
50 #include "lsst/afw/geom/Box.h"
54 
56 namespace afwGeom = lsst::afw::geom;
57 namespace afwImage = lsst::afw::image;
58 namespace afwMath = lsst::afw::math;
59 
60 namespace lsst {
61 namespace meas {
62 namespace algorithms {
63 
64 namespace {
65 
66 int const WARP_BUFFER(1); // Buffer (border) around kernel to prevent warp issues
67 std::string const WARP_ALGORITHM("lanczos5"); // Name of warping algorithm to use
68 
69 
70 // A class to pass around to all our PsfCandidates which builds the PcaImageSet
71 template<typename PixelT>
72 class SetPcaImageVisitor : public afwMath::CandidateVisitor {
73  typedef afwImage::Image<PixelT> ImageT;
74  typedef afwImage::MaskedImage<PixelT> MaskedImageT;
75  typedef afwImage::Exposure<PixelT> ExposureT;
76 public:
77  explicit SetPcaImageVisitor(
78  PsfImagePca<MaskedImageT> *imagePca, // Set of Images to initialise
79  unsigned int const mask=0x0 // Ignore pixels with any of these bits set
80  ) :
81  afwMath::CandidateVisitor(),
82  _imagePca(imagePca)
83  {
84  ;
85  }
86 
87  // Called by SpatialCellSet::visitCandidates for each Candidate
88  void processCandidate(afwMath::SpatialCellCandidate *candidate) {
89  PsfCandidate<PixelT> *imCandidate = dynamic_cast<PsfCandidate<PixelT> *>(candidate);
90  if (imCandidate == NULL) {
92  "Failed to cast SpatialCellCandidate to PsfCandidate");
93  }
94 
95  try {
96  std::shared_ptr<MaskedImageT> im = imCandidate->getOffsetImage(WARP_ALGORITHM,
97  WARP_BUFFER);
98 
99 
100  //static int count = 0;
101  //im->writeFits(str(boost::format("cand%03d.fits") % count));
102  //count += 1;
103 
105  sctrl.setNanSafe(false);
106 
107  if (!std::isfinite(afwMath::makeStatistics(*im->getImage(),
108  afwMath::MAX, sctrl).getValue())) {
110  str(boost::format("Image at %d, %d contains NaN")
111  % imCandidate->getXCenter() % imCandidate->getYCenter()));
112 
113  }
114  if (!std::isfinite(afwMath::makeStatistics(*im->getVariance(),
115  afwMath::MAX, sctrl).getValue())) {
117  str(boost::format("Variance of Image at %d, %d contains NaN")
118  % imCandidate->getXCenter() % imCandidate->getYCenter()));
119  }
120 
121  _imagePca->addImage(im, imCandidate->getSource()->getPsfFlux());
123  return;
124  }
125  }
126 private:
127  PsfImagePca<MaskedImageT> *_imagePca; // the ImagePca we're building
128 };
129 
130 /************************************************************************************************************/
132 template<typename PixelT>
133 class countVisitor : public afwMath::CandidateVisitor {
134  typedef afwImage::MaskedImage<PixelT> MaskedImage;
135  typedef afwImage::Exposure<PixelT> Exposure;
136 public:
137  explicit countVisitor() : afwMath::CandidateVisitor(), _n(0) {}
138 
139  void reset() {
140  _n = 0;
141  }
142 
143  // Called by SpatialCellSet::visitCandidates for each Candidate
144  void processCandidate(afwMath::SpatialCellCandidate *candidate) {
145  PsfCandidate<PixelT> *imCandidate = dynamic_cast<PsfCandidate<PixelT> *>(candidate);
146  if (imCandidate == NULL) {
148  "Failed to cast SpatialCellCandidate to PsfCandidate");
149  }
150 
151  try {
152  imCandidate->getMaskedImage();
154  return;
155  }
156 
157  ++_n;
158  }
159 
160  // Return the number
161  double getN() const { return _n; }
162 
163 private:
164  int mutable _n; // the desired number
165 };
166 
167 
172 template<typename ImageT>
174  afwMath::LinearCombinationKernel const& kernel,
175  float dx, float dy
176  )
177 {
178  afwMath::KernelList kernels = kernel.getKernelList(); // The Kernels that kernel adds together
179  unsigned int const nKernel = kernels.size(); // Number of kernel components
180  std::vector<std::shared_ptr<ImageT>> kernelImages(nKernel); // Images of each Kernel in kernels
181  if (nKernel == 0) {
183  "Kernel has no components");
184  }
185 
186  ImageT scratch(kernel.getDimensions()); // Buffered scratch space
187  for (unsigned int i = 0; i != nKernel; ++i) {
188  kernels[i]->computeImage(scratch, false);
189  kernelImages[i] = afwMath::offsetImage(scratch, dx, dy, WARP_ALGORITHM, WARP_BUFFER);
190  }
191 
192  return kernelImages;
193 }
194 
195 } // Anonymous namespace
196 
197 /************************************************************************************************************/
205 template<typename PixelT>
207  afwMath::SpatialCellSet const& psfCells,
208  lsst::afw::geom::Extent2I const& dims,
209  lsst::afw::geom::Point2I const& xy0,
210  int const nEigenComponents,
211  int const spatialOrder,
212  int const ksize,
213  int const nStarPerCell,
214  bool const constantWeight,
215  int const border
216  )
217 {
218  typedef typename afwImage::Image<PixelT> ImageT;
219  typedef typename afwImage::MaskedImage<PixelT> MaskedImageT;
220 
221  //
222  // Set the sizes for PsfCandidates made from either Images or MaskedImages
223  //
224  //lsst::meas::algorithms::PsfCandidate<ImageT>::setWidth(ksize);
225  //lsst::meas::algorithms::PsfCandidate<ImageT>::setHeight(ksize);
226  //lsst::meas::algorithms::PsfCandidate<MaskedImageT>::setWidth(ksize);
227  //lsst::meas::algorithms::PsfCandidate<MaskedImageT>::setHeight(ksize);
230 
231 
232  PsfImagePca<MaskedImageT> imagePca(constantWeight, border); // Here's the set of images we'll analyze
233 
234  {
235  SetPcaImageVisitor<PixelT> importStarVisitor(&imagePca);
236  bool const ignoreExceptions = true;
237  psfCells.visitCandidates(&importStarVisitor, nStarPerCell, ignoreExceptions);
238  }
239 
240  //
241  // Do a PCA decomposition of those PSF candidates.
242  //
243  // We have "gappy" data; in other words we don't want to include any pixels with INTRP set
244  //
245  int niter = 10; // number of iterations of updateBadPixels
246  double deltaLim = 10.0; // acceptable value of delta, the max change due to updateBadPixels
250 
251  for (int i = 0; i != niter; ++i) {
252  int const ncomp = (i == 0) ? 0 :
253  ((nEigenComponents == 0) ? imagePca.getEigenImages().size() : nEigenComponents);
254  double delta = imagePca.updateBadPixels(BAD | CR | INTRP, ncomp);
255  if (i > 0 && delta < deltaLim) {
256  break;
257  }
258 
259  imagePca.analyze();
260  }
261 
263  std::vector<double> eigenValues = imagePca.getEigenValues();
264  int const nEigen = static_cast<int>(eigenValues.size());
265 
266  int const ncomp = (nEigenComponents <= 0 || nEigen < nEigenComponents) ? nEigen : nEigenComponents;
267  //
268  // Set the background level of the components to 0.0 to avoid coupling variable background
269  // levels to the form of the Psf. More precisely, we calculate the mean of an outer "annulus"
270  // of width bkg_border
271  //
272  for (int k = 0; k != ncomp; ++k) {
273  ImageT const& im = *eigenImages[k]->getImage();
274 
275  int bkg_border = 2;
276  if (bkg_border > im.getWidth()) {
277  bkg_border = im.getWidth() / 2;
278  }
279  if (bkg_border > im.getHeight()) {
280  bkg_border = im.getHeight() / 2;
281  }
282 
283  double sum = 0;
284  // Bottom and Top borders
285  for (int i = 0; i != bkg_border; ++i) {
286  typename ImageT::const_x_iterator
287  ptrB = im.row_begin(i), ptrT = im.row_begin(im.getHeight() - i - 1);
288  for (int j = 0; j != im.getWidth(); ++j, ++ptrB, ++ptrT) {
289  sum += *ptrB + *ptrT;
290  }
291  }
292  for (int i = bkg_border; i < im.getHeight() - bkg_border; ++i) {
293  // Left and Right borders
294  typename ImageT::const_x_iterator
295  ptrL = im.row_begin(i), ptrR = im.row_begin(i) + im.getWidth() - bkg_border;
296  for (int j = 0; j != bkg_border; ++j, ++ptrL, ++ptrR) {
297  sum += *ptrL + *ptrR;
298  }
299  }
300  sum /= 2*(bkg_border*im.getWidth() + bkg_border*(im.getHeight() - 2*bkg_border));
301 
302  *eigenImages[k] -= sum;
303  }
304  //
305  // Now build our LinearCombinationKernel; build the lists of basis functions
306  // and spatial variation, then assemble the Kernel
307  //
308  afwMath::KernelList kernelList;
311 
312  for (int i = 0; i != ncomp; ++i) {
313  {
314  // Enforce unit sum for kernel by construction
315  // Zeroth component has unit sum
316  // Other components have zero sum by normalising and then subtracting the zeroth component
317  ImageT& image = *eigenImages[i]->getImage();
318  double sum = std::accumulate(image.begin(true), image.end(true), 0.0);
319  if (i == 0) {
320  image /= sum;
321  } else {
322  for (typename ImageT::fast_iterator ptr0 = eigenImages[0]->getImage()->begin(true),
323  ptr1 = image.begin(true), end = image.end(true); ptr1 != end; ++ptr0, ++ptr1) {
324  *ptr1 = *ptr1 / sum - *ptr0;
325  }
326  }
327  }
328 
330  afwImage::Image<afwMath::Kernel::Pixel>(*eigenImages[i]->getImage(),true)
331  )));
332 
334 // spatialFunction(new afwMath::PolynomialFunction2<double>(spatialOrder));
335  spatialFunction(new afwMath::Chebyshev1Function2<double>(spatialOrder, range));
336  spatialFunction->setParameter(0, 1.0); // the constant term; all others are 0
337  spatialFunctionList.push_back(spatialFunction);
338  }
339 
341  psf(new afwMath::LinearCombinationKernel(kernelList, spatialFunctionList));
342 
343  return std::make_pair(psf, eigenValues);
344 }
345 
346 /************************************************************************************************************/
350 template<typename PixelT>
352  int const nStarPerCell)
353 {
354  countVisitor<PixelT> counter;
355  psfCells.visitCandidates(&counter, nStarPerCell);
356 
357  return counter.getN();
358 }
359 
360 /************************************************************************************************************/
361 namespace {
367 template<typename ModelImageT, typename DataImageT>
369 fitKernel(ModelImageT const& mImage, // The model image at this point
370  DataImageT const& data, // the data to fit
371  double lambda = 0.0, // floor for variance is lambda*data
372  bool detected = true, // only fit DETECTED pixels?
373  int const id=-1 // ID for this object; useful in debugging
374  ) {
375  assert(data.getDimensions() == mImage.getDimensions());
376  assert(id == id);
377  int const DETECTED = afwImage::Mask<>::getPlaneBitMask("DETECTED");
379 
380  double sumMM = 0.0, sumMD = 0.0, sumDD = 0.0; // sums of model*model/variance etc.
381  int npix = 0; // number of pixels used to evaluate chi^2
382  for (int y = 0; y != data.getHeight(); ++y) {
383  typename ModelImageT::x_iterator mptr = mImage.row_begin(y);
384  for (typename DataImageT::x_iterator ptr = data.row_begin(y), end = data.row_end(y);
385  ptr != end; ++ptr, ++mptr) {
386  double const m = (*mptr)[0]; // value of model
387  double const d = ptr.image(); // value of data
388  double const var = ptr.variance() + lambda*d; // data's variance
389  if (detected && !(ptr.mask() & DETECTED)) {
390  continue;
391  }
392  if (ptr.mask() & BAD) {
393  continue;
394  }
395  if (var != 0.0) { // assume variance == 0 => infinity XXX
396  double const iVar = 1.0/var;
397  npix++;
398  sumMM += m*m*iVar;
399  sumMD += m*d*iVar;
400  sumDD += d*d*iVar;
401  }
402  }
403  }
404 
405  if (npix == 0) {
406  throw LSST_EXCEPT(lsst::pex::exceptions::RangeError, "No good pixels");
407  }
408  if (sumMM == 0.0) {
409  throw LSST_EXCEPT(lsst::pex::exceptions::RangeError, "sum(data*data)/var == 0");
410  }
411 
412  double const amp = sumMD/sumMM; // estimate of amplitude of model at this point
413  double const chi2 = (sumDD - 2*amp*sumMD + amp*amp*sumMM)/(npix - 1);
414 
415 #if 0
416  bool show = false; // Display the centre of the image; set from gdb
417 
418  if (show) {
419  show = true; // you can jump here in gdb to set show if direct attempts fail
420  int y = data.getHeight()/2;
421  int x = data.getWidth()/2;
422  int hsize = 2;
423  printf("\ndata ");
424  for (int ii = -hsize; ii <= hsize; ++ii) {
425  for (int jj = -hsize; jj <= hsize; ++jj) {
426  printf("%7.1f ", data.at(x + jj, y - ii).image());
427  }
428  printf(" model ");
429  for (int jj = -hsize; jj <= hsize; ++jj) {
430  printf("%7.1f ", amp*(*(mImage.at(x + jj, y - ii)))[0]);
431  }
432  printf("\n ");
433  }
434  printf("%g %.1f\n", amp, chi2);
435  }
436 #endif
437 
438  return std::make_pair(chi2, amp);
439 }
440 }
441 
442 /************************************************************************************************************/
443 /*
444  * Fit for the spatial variation of the PSF parameters over the field
445  */
447 template<typename PixelT>
450  typedef afwImage::MaskedImage<PixelT> MaskedImage;
451  typedef afwImage::Exposure<PixelT> Exposure;
452 
454 public:
455  explicit evalChi2Visitor(afwMath::Kernel const& kernel,
456  double lambda
457  ) :
458  afwMath::CandidateVisitor(),
459  _chi2(0.0), _kernel(kernel), _lambda(lambda),
460  _kImage(std::shared_ptr<KImage>(new KImage(kernel.getDimensions()))) {
461  }
462 
463  void reset() {
464  _chi2 = 0.0;
465  }
466 
467  // Called by SpatialCellSet::visitCandidates for each Candidate
469  PsfCandidate<PixelT> *imCandidate = dynamic_cast<PsfCandidate<PixelT> *>(candidate);
470  if (imCandidate == NULL) {
472  "Failed to cast SpatialCellCandidate to PsfCandidate");
473  }
474 
475  double const xcen = imCandidate->getSource()->getX();
476  double const ycen = imCandidate->getSource()->getY();
477 
478  _kernel.computeImage(*_kImage, true, xcen, ycen);
480  try {
481  data = imCandidate->getOffsetImage(WARP_ALGORITHM, WARP_BUFFER);
483  return;
484  }
485 
486  try {
487  std::pair<double, double> result = fitKernel(*_kImage, *data, _lambda, false,
488  imCandidate->getSource()->getId());
489 
490  double dchi2 = result.first; // chi^2 from this object
491  double const amp = result.second; // estimate of amplitude of model at this point
492 
493  imCandidate->setChi2(dchi2);
494  imCandidate->setAmplitude(amp);
495 
496  _chi2 += dchi2;
501  }
502  }
503 
504  // Return the computed chi^2
505  double getValue() const { return _chi2; }
506 
507 private:
508  double mutable _chi2; // the desired chi^2
509  afwMath::Kernel const& _kernel; // the kernel
510  double _lambda; // floor for variance is _lambda*data
511  std::shared_ptr<KImage> mutable _kImage; // The Kernel at this point; a scratch copy
512 };
513 
514 /********************************************************************************************************/
518 // Set the Kernel's spatial parameters from a vector
520  std::vector<double> const& coeffs
521  )
522 {
523  int const nComponents = kernel->getNKernelParameters();
524  int const nSpatialParams = kernel->getNSpatialParameters();
525 
526  assert (nComponents*nSpatialParams == static_cast<long>(coeffs.size()));
527 
528  std::vector<std::vector<double> > kCoeffs; // coefficients rearranged for Kernel
529  kCoeffs.reserve(nComponents);
530  for (int i = 0; i != nComponents; ++i) {
531  kCoeffs.push_back(std::vector<double>(nSpatialParams));
532  std::copy(coeffs.begin() + i*nSpatialParams,
533  coeffs.begin() + (i + 1)*nSpatialParams, kCoeffs[i].begin());
534  }
535 
536  kernel->setSpatialParameters(kCoeffs);
537 }
538 
542 // Set the Kernel's spatial parameters from an Eigen::VectorXd
544  Eigen::VectorXd const& vec
545  )
546 {
547  int const nComponents = kernel->getNKernelParameters();
548  int const nSpatialParams = kernel->getNSpatialParameters();
549 
550  assert (nComponents*nSpatialParams == vec.size());
551 
552  std::vector<std::vector<double> > kCoeffs; // coefficients rearranged for Kernel
553  kCoeffs.reserve(nComponents);
554  for (int i = 0; i != nComponents; ++i) {
555  std::vector<double> spatialCoeffs(nSpatialParams);
556  for (int j = 0; j != nSpatialParams; ++j) {
557  spatialCoeffs[j] = vec[i*nSpatialParams + j];
558  }
559  kCoeffs.push_back(spatialCoeffs);
560  }
561 
562  kernel->setSpatialParameters(kCoeffs);
563 }
564 
565 //
566 // The object that minuit minimises
567 //
568 template<typename PixelT>
569 class MinimizeChi2 : public ROOT::Minuit2::FCNBase {
570 public:
571  explicit MinimizeChi2(evalChi2Visitor<PixelT> & chi2Visitor,
572  afwMath::Kernel *kernel,
573  afwMath::SpatialCellSet const& psfCells,
574  int nStarPerCell,
575  int nComponents,
576  int nSpatialParams
577  ) : _errorDef(1.0),
578  _chi2Visitor(chi2Visitor),
579  _kernel(kernel),
580  _psfCells(psfCells),
581  _nStarPerCell(nStarPerCell),
582  _nComponents(nComponents),
583  _nSpatialParams(nSpatialParams) {}
584 
591  double Up() const { return _errorDef; }
592 
593  // Evaluate our cost function (in this case chi^2)
594  double operator()(const std::vector<double>& coeffs) const {
595  setSpatialParameters(_kernel, coeffs);
596 
597  _psfCells.visitCandidates(&_chi2Visitor, _nStarPerCell);
598 
599  return _chi2Visitor.getValue();
600  }
601 
602  void setErrorDef(double def) { _errorDef = def; }
603 private:
604  double _errorDef; // how much cost function has changed at the +- 1 error points
605 
606  evalChi2Visitor<PixelT>& _chi2Visitor;
607  afwMath::Kernel *_kernel;
608  afwMath::SpatialCellSet const& _psfCells;
609  int _nStarPerCell;
610  int _nComponents;
611  int _nSpatialParams;
612 };
613 
614 /************************************************************************************************************/
618 template<typename PixelT>
621  afwMath::Kernel *kernel,
622  afwMath::SpatialCellSet const& psfCells,
623  int const nStarPerCell,
624  double const tolerance,
625  double const lambda
626  ) {
627  int const nComponents = kernel->getNKernelParameters();
628  int const nSpatialParams = kernel->getNSpatialParameters();
629  //
630  // visitor that evaluates the chi^2 of the current fit
631  //
632  evalChi2Visitor<PixelT> getChi2(*kernel, lambda);
633  //
634  // We have to unpack the Kernel coefficients into a linear array, coeffs
635  //
636  std::vector<double> coeffs; // The coefficients we want to fit
637  coeffs.assign(nComponents*nSpatialParams, 0.0);
638 
639  std::vector<double> stepSize; // step sizes
640  stepSize.assign(nComponents*nSpatialParams, 100);
641  //
642  // Translate that into minuit's language
643  //
644  ROOT::Minuit2::MnUserParameters fitPar;
645  std::vector<std::string> paramNames;
646  paramNames.reserve(nComponents*nSpatialParams);
647 
648  for (int i = 0, c = 0; c != nComponents; ++c) {
649  coeffs[i] = 1; // the constant part of each spatial order
650  for (int s = 0; s != nSpatialParams; ++s, ++i) {
651  paramNames.push_back((boost::format("C%d:%d") % c % s).str());
652  fitPar.Add(paramNames[i].c_str(), coeffs[i], stepSize[i]);
653  }
654  }
655  fitPar.Fix("C0:0");
656  //
657  // Create the minuit object that knows how to minimise our functor
658  //
659  MinimizeChi2<PixelT> minimizerFunc(getChi2, kernel, psfCells, nStarPerCell, nComponents, nSpatialParams);
660 
661  double const errorDef = 1.0; // use +- 1sigma errors
662  minimizerFunc.setErrorDef(errorDef);
663  //
664  // tell minuit about it
665  //
666  ROOT::Minuit2::MnMigrad migrad(minimizerFunc, fitPar);
667  //
668  // And let it loose
669  //
670  int maxFnCalls = 0; // i.e. unlimited
671  ROOT::Minuit2::FunctionMinimum min =
672  migrad(maxFnCalls, tolerance/(1e-4*errorDef)); // minuit uses 0.1*1e-3*tolerance*errorDef
673 
674  float minChi2 = min.Fval();
675  bool const isValid = min.IsValid() && std::isfinite(minChi2);
676 
677  if (true || isValid) { // calculate coeffs even in minuit is unhappy
678  for (int i = 0; i != nComponents*nSpatialParams; ++i) {
679  coeffs[i] = min.UserState().Value(i);
680  }
681 
682  setSpatialParameters(kernel, coeffs);
683  }
684 
685 #if 0 // Estimate errors; we don't really need this
686  ROOT::Minuit2::MnMinos minos(minimizerFunc, min);
687  for (int i = 0, c = 0; c != nComponents; ++c) {
688  for (int s = 0; s != nSpatialParams; ++s, ++i) {
689  char const *name = paramNames[i].c_str();
690  printf("%s %g", name, min.UserState().Value(name));
691  if (isValid && !fitPar.Parameter(fitPar.Index(name)).IsFixed()) {
692  printf(" (%g+%g)\n", minos(i).first, minos(i).second);
693  }
694  printf("\n");
695  }
696  }
697 #endif
698  //
699  // One time more through the Candidates setting their chi^2 values. We'll
700  // do all the candidates this time, not just the first nStarPerCell
701  //
702  psfCells.visitAllCandidates(&getChi2, true);
703 
704  return std::make_pair(isValid, minChi2);
705 }
706 
707 /************************************************************************************************************/
711 namespace {
748 template<typename PixelT>
749 class FillABVisitor : public afwMath::CandidateVisitor {
750  typedef afwImage::Image<PixelT> Image;
751  typedef afwImage::MaskedImage<PixelT> MaskedImage;
752  typedef afwImage::Exposure<PixelT> Exposure;
753 
755 public:
756  explicit FillABVisitor(afwMath::LinearCombinationKernel const& kernel, // the Kernel we're fitting
757  double tau2=0.0 // floor to the per-candidate variance
758  ) :
760  _kernel(kernel),
761  _tau2(tau2),
762  _nSpatialParams(_kernel.getNSpatialParameters()),
763  _nComponents(_kernel.getNKernelParameters()),
764  _basisImgs(),
765  _A((_nComponents-1)*_nSpatialParams, (_nComponents-1)*_nSpatialParams),
766  _b((_nComponents-1)*_nSpatialParams),
767  _basisDotBasis(_nComponents, _nComponents)
768  {
769  _basisImgs.resize(_nComponents);
770 
771  _A.setZero();
772  _b.setZero();
773  //
774  // Get all the Kernel's components as Images
775  //
776  afwMath::KernelList const& kernels = _kernel.getKernelList(); // Kernel's components
777  for (int i = 0; i != _nComponents; ++i) {
778  _basisImgs[i] = std::shared_ptr<KImage>(new KImage(kernels[i]->getDimensions()));
779  kernels[i]->computeImage(*_basisImgs[i], false);
780  }
781 
782  //
783  // Calculate the inner products of the Kernel components once and for all
784  //
785  for (int i = 1; i != _nComponents; ++i) { // Don't need 0th component
786  for (int j = i; j != _nComponents; ++j) {
787  _basisDotBasis(i, j) = _basisDotBasis(j, i) =
788  afwImage::innerProduct(*_basisImgs[i], *_basisImgs[j],
790  }
791  }
792  }
793 
794  void reset() {}
795 
796  // Called by SpatialCellSet::visitCandidates for each Candidate
797  void processCandidate(afwMath::SpatialCellCandidate *candidate) {
798  PsfCandidate<PixelT> *imCandidate = dynamic_cast<PsfCandidate<PixelT> *>(candidate);
799  if (imCandidate == NULL) {
801  "Failed to cast SpatialCellCandidate to PsfCandidate");
802  }
803 
804  CONST_PTR(MaskedImage) data;
805  try {
806  data = imCandidate->getMaskedImage(_kernel.getWidth(), _kernel.getHeight());
808  return;
809  }
810  double const xcen = imCandidate->getXCenter();
811  double const ycen = imCandidate->getYCenter();
812  double const dx = afwImage::positionToIndex(xcen, true).second;
813  double const dy = afwImage::positionToIndex(ycen, true).second;
814 
815 #if 0
816  double const amp = imCandidate->getAmplitude();
817 #else
818  /*
819  * Estimate the amplitude based on the current basis functions.
820  *
821  * N.b. you have to be a little careful here. Consider a PSF that is phi == (N0 + b*y*N1)/(1 + b*y)
822  * where the amplitude of N0 and N1 is 1.0, so a star has profile I = A*(N0 + b*y*N1)/(1 + b*y)
823  *
824  * If we set the amplitude to be A = I(0)/phi(0) (i.e. the central value of the data and best-fit phi)
825  * then the coefficient of N0 becomes 1/(1 + b*y) which makes the model non-linear in y.
826  */
828  fitKernelToImage(_kernel, *data, afwGeom::Point2D(xcen, ycen));
829  double const amp = ret.second.first;
830 #endif
831 
832  double const var = imCandidate->getVar();
833  double const ivar = 1/(var + _tau2); // Allow for floor on variance
834 
835  // Spatial params of all the components
836  std::vector<std::vector<double> > params(_nComponents);
837  for (int ic = 1; ic != _nComponents; ++ic) { // Don't need params[0]
838  params[ic] = _kernel.getSpatialFunction(ic)->getDFuncDParameters(xcen, ycen);
839  }
840 
841  std::vector<std::shared_ptr<KImage>> basisImages = offsetKernel<KImage>(_kernel, dx, dy);
842 
843  // Prepare values for basis dot data
844  // Scale data and subtract 0th component as part of unit kernel sum construction
845  std::shared_ptr<Image> dataImage(new Image(*data->getImage(), true));
846  typename KImage::fast_iterator bPtr = basisImages[0]->begin(true);
847  for (typename Image::fast_iterator dPtr = dataImage->begin(true), end = dataImage->end(true);
848  dPtr != end; ++dPtr, ++bPtr) {
849  *dPtr = *dPtr / amp - *bPtr;
850  }
851 
852  for (int i = 0, ic = 1; ic != _nComponents; ++ic) { // Don't need 0th component now
853  double const basisDotData = afwImage::innerProduct(*basisImages[ic], *dataImage,
855  for (int is = 0; is != _nSpatialParams; ++is, ++i) {
856  _b(i) += ivar*params[ic][is]*basisDotData;
857 
858  for (int j = i, jc = ic; jc != _nComponents; ++jc) {
859  for (int js = (i == j) ? is : 0; js != _nSpatialParams; ++js, ++j) {
860  _A(i, j) += ivar*params[ic][is]*params[jc][js]*_basisDotBasis(ic, jc);
861  _A(j, i) = _A(i, j); // could do this after _A is fully calculated
862  }
863  }
864  }
865  }
866  }
867 
868  Eigen::MatrixXd const& getA() const { return _A; }
869  Eigen::VectorXd const& getB() const { return _b; }
870 
871 private:
872  afwMath::LinearCombinationKernel const& _kernel; // the kernel
873  double _tau2; // variance floor added in quadrature to true candidate variance
874  int const _nSpatialParams; // number of spatial parameters
875  int const _nComponents; // number of basis functions
876  std::vector<std::shared_ptr<KImage>> _basisImgs; // basis function images from _kernel
877  Eigen::MatrixXd _A; // We'll solve the matrix equation A x = b for the Kernel's coefficients
878  Eigen::VectorXd _b;
879  Eigen::MatrixXd _basisDotBasis; // the inner products of the Kernel components
880 };
881 
882 
884 template<typename PixelT>
885 class setAmplitudeVisitor : public afwMath::CandidateVisitor {
886  typedef afwImage::MaskedImage<PixelT> MaskedImage;
887  typedef afwImage::Exposure<PixelT> Exposure;
888 public:
889  // Called by SpatialCellSet::visitCandidates for each Candidate
890  void processCandidate(afwMath::SpatialCellCandidate *candidate) {
891  PsfCandidate<PixelT> *imCandidate = dynamic_cast<PsfCandidate<PixelT> *>(candidate);
892  if (imCandidate == NULL) {
894  "Failed to cast SpatialCellCandidate to PsfCandidate");
895  }
896  imCandidate->setAmplitude(afwMath::makeStatistics(*imCandidate->getMaskedImage()->getImage(),
897  afwMath::MAX).getValue());
898  }
899 };
900 
901 }
902 
903 
904 template<typename PixelT>
907  afwMath::Kernel *kernel,
908  afwMath::SpatialCellSet const& psfCells,
909  bool const doNonLinearFit,
910  int const nStarPerCell,
911  double const tolerance,
912  double const lambda
913  )
914 {
915  if (doNonLinearFit) {
916  return fitSpatialKernelFromPsfCandidates<PixelT>(kernel, psfCells, nStarPerCell, tolerance);
917  }
918 
919  double const tau = 0; // softening for errors
920 
921  afwMath::LinearCombinationKernel const* lcKernel =
922  dynamic_cast<afwMath::LinearCombinationKernel const*>(kernel);
923  if (!lcKernel) {
925  "Failed to cast Kernel to LinearCombinationKernel while building spatial PSF model");
926  }
927 #if 1
928  //
929  // Set the initial amplitudes of all our candidates
930  //
931  setAmplitudeVisitor<PixelT> setAmplitude;
932  psfCells.visitAllCandidates(&setAmplitude, true);
933 #endif
934  //
935  // visitor that fills out the A and b matrices (we'll solve A x = b for the coeffs, x)
936  //
937  FillABVisitor<PixelT> getAB(*lcKernel, tau);
938  //
939  // Actually visit all our candidates
940  //
941  psfCells.visitCandidates(&getAB, nStarPerCell, true);
942  //
943  // Extract A and b, and solve Ax = b
944  //
945  Eigen::MatrixXd const& A = getAB.getA();
946  Eigen::VectorXd const& b = getAB.getB();
947  Eigen::VectorXd x0(b.size()); // Solution to matrix problem
948 
949  switch (b.size()) {
950  case 0: // One candidate, no spatial variability
951  break;
952  case 1: // eigen can't/won't handle 1x1 matrices
953  x0(0) = b(0)/A(0, 0);
954  break;
955  default:
956  x0 = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
957  break;
958  }
959 #if 0
960  std::cout << "A " << A << std::endl;
961  std::cout << "b " << b.transpose() << std::endl;
962  std::cout << "x " << x.transpose() << std::endl;
963 
964  afwImage::Image<double> img(b.size(), b.size());
965  for (int j = 0; j < b.size(); ++j) {
966  for (int i = 0; i < b.size(); ++i) {
967  img(i, j) = A(i, j);
968  }
969  }
970  img.writeFits("a.fits");
971 
972  if (x.cols() >= 6) {
973  for (int i = 0; i != 6; ++i) {
974  double xcen = 25; double ycen = 35 + 35*i;
975  std::cout << "x, y " << xcen << " , " << ycen << " b "
976  << (x[3] + xcen*x[4] + ycen*x[5])/(x[0] + xcen*x[1] + ycen*x[2]) << std::endl;
977  }
978  }
979 #endif
980 
981  // Generate kernel parameters (including 0th component) from matrix solution
982  Eigen::VectorXd x(kernel->getNKernelParameters() * kernel->getNSpatialParameters()); // Kernel parameters
983  x(0) = 1.0;
984  std::fill(x.data() + 1, x.data() + kernel->getNSpatialParameters(), 0.0);
985  std::copy(x0.data(), x0.data() + x0.size(), x.data() + kernel->getNSpatialParameters());
986 
987  setSpatialParameters(kernel, x);
988  //
989  // One time more through the Candidates setting their chi^2 values. We'll
990  // do all the candidates this time, not just the first nStarPerCell
991  //
992  // visitor that evaluates the chi^2 of the current fit
993  //
994  evalChi2Visitor<PixelT> getChi2(*kernel, lambda);
995 
996  psfCells.visitAllCandidates(&getChi2, true);
997 
998  return std::make_pair(true, getChi2.getValue());
999 }
1000 
1001 /************************************************************************************************************/
1005 template<typename MaskedImageT>
1006 double subtractPsf(afwDetection::Psf const& psf,
1007  MaskedImageT *data,
1008  double x,
1009  double y,
1010  double psfFlux
1011  )
1012 {
1013  if (std::isnan(x + y)) {
1015  }
1016 
1017  //
1018  // Get Psf candidate
1019  //
1021 
1022  //
1023  // Now find the proper sub-Image
1024  //
1025  afwGeom::BoxI bbox = kImage->getBBox();
1026 
1027  std::shared_ptr<MaskedImageT> subData(new MaskedImageT(*data, bbox, afwImage::PARENT, false)); // shallow copy
1028  //
1029  // Now we've got both; find the PSF's amplitude
1030  //
1031  double lambda = 0.0; // floor for variance is lambda*data
1032  try {
1033  double chi2; // chi^2 for fit
1034  double amp; // estimate of amplitude of model at this point
1035 
1036  if (std::isnan(psfFlux)) {
1037  std::pair<double, double> result = fitKernel(*kImage, *subData, lambda, true);
1038  chi2 = result.first; // chi^2 for fit
1039  amp = result.second; // estimate of amplitude of model at this point
1040  } else {
1042  amp = psfFlux/afwMath::makeStatistics(*kImage, afwMath::SUM).getValue();
1043  }
1044  //
1045  // Convert kImage to the proper type so that I can subtract it.
1046  //
1048  kImageF(new typename MaskedImageT::Image(*kImage, true)); // of data's type
1049 
1050  *kImageF *= amp;
1051  *subData->getImage() -= *kImageF;
1052 
1053  return chi2;
1054  } catch(lsst::pex::exceptions::RangeError &e) {
1055  LSST_EXCEPT_ADD(e, (boost::format("Object at (%.2f, %.2f)") % x % y).str());
1056  throw e;
1057  }
1058 }
1059 
1060 /************************************************************************************************************/
1066 template<typename Image>
1069  afwMath::LinearCombinationKernel const& kernel,
1070  Image const& image,
1071  afwGeom::Point2D const& pos
1072  )
1073 {
1075 
1076  afwMath::KernelList kernels = kernel.getKernelList(); // the Kernels that kernel adds together
1077  int const nKernel = kernels.size();
1078 
1079  if (nKernel == 0) {
1081  "Your kernel must have at least one component");
1082  }
1083 
1084  /*
1085  * Go through all the kernels, get a copy centered at the desired sub-pixel position, and then
1086  * extract a subImage from the parent image at the same place
1087  */
1088  std::vector<std::shared_ptr<KernelT>> kernelImages = offsetKernel<KernelT>(kernel, pos[0], pos[1]);
1089  afwGeom::BoxI bbox(kernelImages[0]->getBBox());
1090  Image const& subImage(Image(image, bbox, afwImage::PARENT, false)); // shallow copy
1091 
1092  /*
1093  * Solve the linear problem subImage = sum x_i K_i + epsilon; we solve this for x_i by constructing the
1094  * normal equations, A x = b
1095  */
1096  Eigen::MatrixXd A(nKernel, nKernel);
1097  Eigen::VectorXd b(nKernel);
1098 
1099  for (int i = 0; i != nKernel; ++i) {
1100  b(i) = afwImage::innerProduct(*kernelImages[i], *subImage.getImage());
1101 
1102  for (int j = i; j != nKernel; ++j) {
1103  A(i, j) = A(j, i) = afwImage::innerProduct(*kernelImages[i], *kernelImages[j]);
1104  }
1105  }
1106  Eigen::VectorXd x(nKernel);
1107 
1108  if (nKernel == 1) {
1109  x(0) = b(0)/A(0, 0);
1110  } else {
1111  x = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
1112  }
1113 
1114  // the XY0() point of the shifted Kernel basis functions
1115  int const x0 = kernelImages[0]->getX0(), y0 = kernelImages[0]->getY0();
1116 
1117  afwMath::KernelList newKernels(nKernel);
1118  std::vector<double> params(nKernel);
1119  for (int i = 0; i != nKernel; ++i) {
1120  std::shared_ptr<afwMath::Kernel> newKernel(new afwMath::FixedKernel(*kernelImages[i]));
1121  newKernel->setCtrX(x0 + static_cast<int>(newKernel->getWidth()/2));
1122  newKernel->setCtrY(y0 + static_cast<int>(newKernel->getHeight()/2));
1123 
1124  params[i] = x[i];
1125  newKernels[i] = newKernel;
1126  }
1127 
1128  return std::make_pair(params, newKernels);
1129 }
1130 
1131 
1132 /************************************************************************************************************/
1138 template<typename Image>
1141  afwMath::LinearCombinationKernel const& kernel,
1142  Image const& image,
1143  afwGeom::Point2D const& pos
1144  )
1145 {
1147  fitKernelParamsToImage(kernel, image, pos);
1148  std::vector<double> params = fit.first;
1149  afwMath::KernelList kernels = fit.second;
1150  int const nKernel = params.size();
1151  assert(kernels.size() == static_cast<unsigned int>(nKernel));
1152 
1153  double amp = 0.0;
1154  for (int i = 0; i != nKernel; ++i) {
1155  std::shared_ptr<afwMath::Kernel> base = kernels[i];
1157  amp += params[i] * k->getSum();
1158  }
1159 
1160  std::shared_ptr<afwMath::Kernel> outputKernel(new afwMath::LinearCombinationKernel(kernels, params));
1161  double chisq = 0.0;
1162  outputKernel->setCtrX(kernels[0]->getCtrX());
1163  outputKernel->setCtrY(kernels[0]->getCtrY());
1164 
1165  return std::make_pair(outputKernel, std::make_pair(amp, chisq));
1166 }
1167 
1168 
1169 /************************************************************************************************************/
1170 //
1171 // Explicit instantiations
1172 //
1174  typedef float Pixel;
1175 
1176  template
1178  createKernelFromPsfCandidates<Pixel>(afwMath::SpatialCellSet const&, afwGeom::Extent2I const&,
1179  afwGeom::Point2I const&, int const, int const, int const,
1180  int const, bool const, int const);
1181  template
1182  int countPsfCandidates<Pixel>(afwMath::SpatialCellSet const&, int const);
1183 
1184  template
1186  fitSpatialKernelFromPsfCandidates<Pixel>(afwMath::Kernel *, afwMath::SpatialCellSet const&,
1187  int const, double const, double const);
1188  template
1190  fitSpatialKernelFromPsfCandidates<Pixel>(afwMath::Kernel *, afwMath::SpatialCellSet const&, bool const,
1191  int const, double const, double const);
1192 
1193  template
1194  double subtractPsf(afwDetection::Psf const&, afwImage::MaskedImage<float> *, double, double, double);
1195 
1196  template
1200 
1201  template
1206 }}}
Class used by SpatialCell for spatial PSF fittig.
boost::shared_ptr< afw::image::MaskedImage< PixelT > > getOffsetImage(std::string const algorithm, unsigned int buffer) const
Return an offset version of the image of the source.
T copy(T... args)
double operator()(const std::vector< double > &coeffs) const
void setAmplitude(double amplitude)
Set the best-fit amplitude.
Definition: PsfCandidate.h:116
afw::geom::Box2D bbox
std::pair< bool, double > fitSpatialKernelFromPsfCandidates(lsst::afw::math::Kernel *kernel, lsst::afw::math::SpatialCellSet const &psfCells, int const nStarPerCell=-1, double const tolerance=1e-5, double const lambda=0.0)
Fit spatial kernel using full-nonlinear optimization to estimate candidate amplitudes.
std::vector< double > const & getEigenValues() const
void processCandidate(afwMath::SpatialCellCandidate *candidate)
boost::shared_ptr< afw::image::MaskedImage< PixelT > const > getMaskedImage() const
Return the image at the position of the Source, without any sub-pixel shifts to put the centre of the...
double subtractPsf(lsst::afw::detection::Psf const &psf, ImageT *data, double x, double y, double psfFlux=std::numeric_limits< double >::quiet_NaN())
MinimizeChi2(evalChi2Visitor< PixelT > &chi2Visitor, afwMath::Kernel *kernel, afwMath::SpatialCellSet const &psfCells, int nStarPerCell, int nComponents, int nSpatialParams)
_const_view_t::x_iterator const_x_iterator
T endl(T... args)
int min
std::shared_ptr< lsst::afw::math::Function2< double > > SpatialFunctionPtr
STL namespace.
virtual void analyze()
Generate eigenimages that are normalised and background-subtracted.
Definition: ImagePca.cc:41
evalChi2Visitor(afwMath::Kernel const &kernel, double lambda)
std::pair< std::vector< double >, lsst::afw::math::KernelList > fitKernelParamsToImage(lsst::afw::math::LinearCombinationKernel const &kernel, Image const &image, lsst::afw::geom::Point2D const &pos)
Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary...
#define CONST_PTR(...)
string name
boost::shared_ptr< afw::table::SourceRecord > getSource() const
Return the original Source.
Definition: PsfCandidate.h:110
STL class.
T push_back(T... args)
double getVar() const
Return the variance in use when fitting this object.
Definition: PsfCandidate.h:119
first
def show(frame=None)
double getAmplitude() const
Return the best-fit amplitude.
Definition: PsfCandidate.h:113
A class to pass around to all our PsfCandidates to evaluate the PSF fit&#39;s X^2.
std::pair< std::shared_ptr< lsst::afw::math::LinearCombinationKernel >, std::vector< double > > createKernelFromPsfCandidates(lsst::afw::math::SpatialCellSet const &psfCells, lsst::afw::geom::Extent2I const &dims, lsst::afw::geom::Point2I const &xy0, int const nEigenComponents, int const spatialOrder, int const ksize, int const nStarPerCell=-1, bool const constantWeight=true, int const border=3)
Return a Kernel pointer and a list of eigenvalues resulting from analysing the provided SpatialCellSe...
Class used by SpatialCell for spatial PSF fittig.
int end
T make_pair(T... args)
T isfinite(T... args)
Class for doing PCA on PSF stars.
SpatialFunctionPtr getSpatialFunction(unsigned int index) const
double computeImage(lsst::afw::image::Image< Pixel > &image, bool doNormalize, double x=0.0, double y=0.0) const
T static_pointer_cast(T... args)
static MaskPixelT getPlaneBitMask(const std::vector< std::string > &names)
double x
double getValue(Property const prop=NOTHING) const
void setSpatialParameters(afwMath::Kernel *kernel, std::vector< double > const &coeffs)
Fit a Kernel&#39;s spatial variability from a set of stars.
unsigned int getNKernelParameters() const
void setSpatialParameters(const std::vector< std::vector< double >> params)
second
T size(T... args)
#define LSST_EXCEPT(type,...)
T assign(T... args)
STL class.
void setNanSafe(bool isNanSafe)
std::shared_ptr< ImageT > offsetImage(ImageT const &image, float dx, float dy, std::string const &algorithmName="lanczos5", unsigned int buffer=0)
T begin(T... args)
double Up() const
Error definition of the function.
int getNSpatialParameters() const
T isnan(T... args)
virtual KernelList const & getKernelList() const
afw::table::Key< double > b
ImageList const & getEigenImages() const
void visitAllCandidates(CandidateVisitor *visitor, bool const ignoreExceptions=false)
std::shared_ptr< Image > computeImage(geom::Point2D position=makeNullPoint(), image::Color color=image::Color(), ImageOwnerEnum owner=COPY) const
T quiet_NaN(T... args)
lsst::afw::image::Image< ImagePixelT > Image
m
T fill(T... args)
T accumulate(T... args)
std::pair< std::shared_ptr< lsst::afw::math::Kernel >, std::pair< double, double > > fitKernelToImage(lsst::afw::math::LinearCombinationKernel const &kernel, Image const &image, lsst::afw::geom::Point2D const &pos)
Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary...
#define LSST_EXCEPT_ADD(e, m)
geom::Extent2I const getDimensions() const
void visitCandidates(CandidateVisitor *visitor, int const nMaxPerCell=-1, bool const ignoreExceptions=false)
Class stored in SpatialCells for spatial Psf fitting.
Definition: PsfCandidate.h:56
virtual double updateBadPixels(unsigned long mask, int const ncomp)
T reserve(T... args)
int countPsfCandidates(lsst::afw::math::SpatialCellSet const &psfCells, int const nStarPerCell=-1)
Count the number of candidates in use.