lsst.meas.base  14.0-9-g876143c+4
GaussianCentroid.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 /*
3  * LSST Data Management System
4  * Copyright 2008-2013 LSST Corporation.
5  *
6  * This product includes software developed by the
7  * LSST Project (http://www.lsst.org/).
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the LSST License Statement and
20  * the GNU General Public License along with this program. If not,
21  * see <http://www.lsstcorp.org/LegalNotices/>.
22  */
23 #include "ndarray/eigen.h"
24 #include "lsst/afw/table/Source.h"
26 
27 #include <algorithm>
28 #include "Eigen/Core"
29 #include "Eigen/LU"
30 
31 
32 namespace lsst { namespace meas { namespace base {
33 namespace {
34 FlagDefinitionList flagDefinitions;
35 } // end anonymous
36 
37 FlagDefinition const GaussianCentroidAlgorithm::FAILURE = flagDefinitions.addFailureFlag();
38 FlagDefinition const GaussianCentroidAlgorithm::NO_PEAK = flagDefinitions.add("flag_noPeak", "Fitted Centroid has a negative peak");
39 
41  return flagDefinitions;
42 }
43 
44 namespace {
45 #define USE_WEIGHT 0 // zweight is only set, not used. It isn't even set if this is false
46 struct Raster {
47  int x, y; // x, y index of rastered pixel
48  double zo; // pixel's value
49 #if USE_WEIGHT
50  double zweight; // weight for the pixel
51 #endif
52 };
53 
54 struct Fit2d {
55  typedef Eigen::Matrix<double, FittedModel::NPARAM, FittedModel::NPARAM> Matrix;
56  typedef Eigen::Matrix<double, FittedModel::NPARAM, 1> Vector;
57 
58  template<typename PixelT>
59  explicit Fit2d(afw::image::Image<PixelT> const& im) : wide(32), rast(wide*wide) {
60  ncols = im.getWidth();
61  nrows = im.getHeight();
62 
63  dc2zmin = 0.3;
64  dc2zmax = 3.5;
65  tooclose = 3;
66  toosmall = 1.5;
67  nitmax = 15;
68  flamdok = 1.0e-7;
69  ratiomin = -1.0e-7;
70  lost = 3.5;
71  xlamd = 5.0;
72 
73  iter = 0;
74  flamd = 1.0;
75  }
76 
77  int wide; // patch of image being fitted is wide*wide
78  std::vector<Raster> rast; // The pixels being fitted, thought of as a raster scan
79  int nin;
80  int nref;
81  int iter;
82  int tooclose;
83  Vector param;
84  Matrix alpha;
85  Vector beta;
86  Vector elold;
87  Vector elnew;
88  Vector sgold;
89  Vector sgnew;
90  Matrix alold;
91  Matrix alnew;
92  Vector beold;
93  Vector benew;
94  double chisq;
95  double chold;
96  double stold;
97  double chnew;
98  double stnew;
99  double flamd;
100  double flamdok;
101  double xlamd;
102  double ratiomin;
103  int nitmax;
104  double dc2zmin;
105  double dc2zmax;
106  double lost;
107  double toosmall; // minimum acceptable width
108  int ncols; // number of columns in image
109  int nrows; // number of rows in image
110  int status; // status of processing
111 };
112 
113 /************************************************************************************************************/
118 static void dcalc2(Fit2d *fit, // the struct carrying all the fit information
119  Fit2d::Vector const &el, // current parameters of the fit
120  Fit2d::Matrix *alpha, // desired alpha matrix
121  Fit2d::Vector *beta // desired beta vector
122  ) {
123 /*
124  * Pass arguments and initialize
125  */
126  double const a = el[FittedModel::PEAK];
127  double const b = el[FittedModel::SKY];
128  double const x0 = el[FittedModel::X0];
129  double const y0 = el[FittedModel::Y0];
130  double const s = el[FittedModel::SIGMA];
131 
132  if (a <= 0.0) {
133  fit->status = FittedModel::BAD_A;
134  return;
135  } else if (s <= 0.0) {
136  fit->status = FittedModel::BAD_WIDTH;
137  return;
138  } else {
139  fit->status = 0;
140  }
141 
142  beta->setZero();
143  alpha->setZero();
144 
145  fit->nin = 0;
146  fit->chnew = 0.0;
147 /*
148  * Examine all pixels in raster
149  */
150  for (int i = 0, nrast = fit->rast.size(); i != nrast; i++) {
151  double const dx = (fit->rast[i].x - x0)/s;
152  double const dy = (fit->rast[i].y - y0)/s;
153  double const d = hypot(dx, dy);
154 
155  if (d >= fit->dc2zmin && d <= fit->dc2zmax) {
156  double const arg = exp(-d*d/2.0);
157  double const funct = a*arg + b;
158 
159  Fit2d::Vector df;
160  df << arg, 1.0, a*arg*dx/s, a*arg*dy/s, a*arg*(dx*dx/s + dy*dy/s);
161 
162  double const r = fit->rast[i].zo - funct; // residual
163  fit->chnew += r*r;
164  *beta += r*df;
165  *alpha += df*df.transpose(); // outer product
166 
167  fit->nin++;
168  }
169  }
170  int nu = fit->nin - (FittedModel::NPARAM + 1); // number of degrees of freedom
171  if (nu <= 0) {
172  fit->status = FittedModel::TOO_FEW;
173  return;
174  }
175  fit->stnew = sqrt(fit->chnew/nu);
176 }
177 
178 /************************************************************************************************************/
179 /*
180  * From Bevington's CURFIT
181  */
182 static void curf2(Fit2d *fit) {
183 /*
184  * Initialization
185  */
186  fit->status = 0;
187  if (fit->iter == 0) {
188  dcalc2(fit, fit->elnew, &fit->alnew, &fit->benew);
189  if (fit->status != 0) {
190  return;
191  }
192  fit->nref = fit->nin;
193  fit->stnew = sqrt(fit->chnew/(fit->nref - (FittedModel::NPARAM + 1)));
194  }
195 /*
196  * Save Current Parameters
197  */
198  fit->chold = fit->chnew;
199  fit->stold = fit->stnew;
200  fit->elold = fit->elnew;
201  fit->sgold = fit->sgnew;
202  fit->beold = fit->benew;
203  fit->alold = fit->alnew;
204 /*
205  * Previous Call To DCALC Used To Fill Current
206  */
207  int const NPLIM = 4;
208  for (int poor = 0; poor != NPLIM; ++poor) {
209  for (int j = 0; j != FittedModel::NPARAM; ++j) {
210  if (fit->alnew(j, j) == 0.0) {
211  fit->status = FittedModel::DIAGONAL;
212  return;
213  }
214  for (int k = 0; k != FittedModel::NPARAM; ++k) {
215  fit->alpha(j, k) = fit->alnew(j, k)/sqrt(fit->alnew(j, j)*fit->alnew(k, k));
216  }
217  fit->alpha(j, j) = 1.0 + fit->flamd;
218  }
219 /*
220  * Inversion.
221  */
222 #if 0
223  fit->alpha.computeInverse(); // has no way to check if inverse succeeded
224 #else
225  Eigen::FullPivLU<Fit2d::Matrix> alphaLU(fit->alpha);
226  if (!alphaLU.isInvertible()) {
227  fit->status = -1;
228  return;
229  }
230  fit->alpha = alphaLU.inverse();
231 #endif
232 
233 /*
234  * New Elements
235  */
236  for (int j = 0; j != FittedModel::NPARAM; ++j) {
237  for (int k = 0; k != FittedModel::NPARAM; ++k) {
238  fit->elnew[j] += fit->benew[k]*fit->alpha(j,k)/sqrt(fit->alnew(j, j)*fit->alnew(k, k));
239  }
240  }
241 /*
242  * Compute Current Chi-Squared And Next ALPHA+BETA
243  */
244  dcalc2(fit, fit->elnew, &fit->alnew, &fit->benew);
245  if (fit->status == FittedModel::TOO_FEW) {
246  return;
247  }
248  fit->stnew = sqrt(fit->chnew/(fit->nref - (FittedModel::NPARAM + 1)));
249 
250  for (int j = 0; j != FittedModel::NPARAM; ++j) {
251  fit->sgnew[j] = fit->stnew*sqrt(fabs(fit->alpha(j, j)/fit->alnew(j, j)));
252  }
253 /*
254  * Quick Return If Solution Got Better
255  */
256  if (fit->status == 0.0 && fit->stnew <= fit->stold) {
257  fit->flamd = fit->flamd/fit->xlamd;
258  return;
259  }
260 /*
261  * Undo Solution And Try Again
262  */
263  fit->flamd = 3.0*fit->flamd;
264  fit->chnew = fit->chold;
265  fit->stnew = fit->stold;
266  fit->elnew = fit->elold;
267  fit->sgnew = fit->sgold;
268  fit->benew = fit->beold;
269  fit->alnew = fit->alold;
270  }
271 }
272 
273 /************************************************************************************************************/
274 /*
275  * Test of convergence or fatal errors
276  */
277 static void cond2(Fit2d *fit) {
278 /*
279  * Ignore CURF errors for these conditions
280  */
281  if (fit->iter <= 3) {
282  fit->status = 0;
283  return;
284  }
285  if (fit->flamd < fit->flamdok) {
286  fit->status = FittedModel::ALMOST;
287  return;
288  }
289 /*
290  * Catch Fatal Errors
291  */
292  if (fit->status < 0) {
293  return;
294  }
295  if (fit->nin < FittedModel::NPARAM + 1) {
296  fit->status = FittedModel::TOO_FEW;
297  return;
298  }
299  if (fit->chnew <= 0.0) {
300  fit->status = FittedModel::CHI_SQUARED;
301  return;
302  }
303  if (fit->elnew[FittedModel::X0] < 0.0 || fit->elnew[FittedModel::X0] > fit->ncols ||
304  fit->elnew[FittedModel::Y0] < 0.0 || fit->elnew[FittedModel::Y0] > fit->nrows) {
305  fit->status = FittedModel::RANGE;
306  return;
307  }
308  if (fit->elnew[FittedModel::SIGMA] < 0.0) {
309  fit->status = FittedModel::BAD_WIDTH;
310  return;
311  }
312 
313  double const ex = fabs(fit->param[FittedModel::X0] - fit->elnew[FittedModel::X0]);
314  double const ey = fabs(fit->param[FittedModel::Y0] - fit->elnew[FittedModel::Y0]);
315  if (ex > fit->lost || ey > fit->lost) {
316  fit->status = FittedModel::LOST;
317  return;
318  }
319 /*
320  * Test for convergence
321  */
322  double const ratio = (fit->stnew - fit->stold)/fit->stnew;
323  if (ratio > fit->ratiomin) {
324  fit->status = (fit->flamd < 0.001) ? FittedModel::CONVERGE : FittedModel::POOR;
325  } else if (fit->iter > fit->nitmax) {
326  fit->status = FittedModel::ITERATE;
327  } else {
328  fit->status = 0;
329  }
330 }
331 
332 /************************************************************************************************************/
333 /*
334  * First guess for 2-D Gaussian
335  */
336 template<typename PixelT>
337 static void fg2(afw::image::Image<PixelT> const& im,
338  double x0, double y0,
339  Fit2d *fit
340  ) {
341 /*
342  * Sanity Check
343  */
344  int ix0 = static_cast<int>(x0 + 0.5);
345  int iy0 = static_cast<int>(y0 + 0.5);
346  if(ix0 < fit->tooclose || im.getWidth() - ix0 < fit->tooclose ||
347  iy0 < fit->tooclose || im.getHeight() - iy0 < fit->tooclose) {
348  fit->status = FittedModel::BAD_GUESS;
349  return;
350  }
351  int jx0 = ix0;
352  int jy0 = iy0;
353  double peak = im(jx0, jy0);
354 /*
355  * Extract interesting portion
356  */
357  double const w = fit->wide/2;
358  int xl = static_cast<int>(ix0 - w + 0.5);
359  if (xl < 0) {
360  xl = 0;
361  }
362  int xh = static_cast<int>(xl + 2*w + 0.5);
363  if (xh > im.getWidth()) {
364  xh = im.getWidth();
365  }
366  int yl = static_cast<int>(iy0 - w + 0.5);
367  if (yl < 0) {
368  yl = 0;
369  }
370  int yh = static_cast<int>(yl + 2*w + 0.5);
371  if (yh > im.getHeight()) {
372  yh = im.getHeight();
373  }
374 
375  double sky = im(xl, yl);
376  int put = 0;
377  for (int y = yl; y != yh; ++y) {
378  for (int x = xl; x != xh; ++x) {
379  fit->rast[put].x = x;
380  fit->rast[put].y = y;
381  fit->rast[put].zo = im(x, y);
382 #if USE_WEIGHT
383  fit->rast[put].zweight = 1.0;
384 #endif
385  if (im(x, y) < sky) {
386  sky = im(x, y);
387  }
388  double const ex = x - ix0;
389  double const ey = y - iy0;
390  double const er = hypot(ex, ey);
391  if (er <= fit->lost) {
392  if (im(x, y) > peak) {
393  jx0 = x;
394  jy0 = y;
395  peak = im(x, y);
396  }
397  }
398  put++;
399  }
400  }
401  fit->rast.resize(put);
402  ix0 = jx0;
403  iy0 = jy0;
404 /*
405  * Look For FWHM
406  */
407  double xmin = xl;
408  double xmax = xh - 1;
409  double ymin = yl;
410  double ymax = yh - 1;
411  double const test = 0.5*(im(ix0, iy0) + sky); // pixel value of half-maximum above sky
412  for (int x = ix0; x < xh - 1; ++x) {
413  if (im(x + 1, iy0) <= test) {
414  double a = test - im(x, iy0);
415  double b = im(x + 1, iy0) - im(x, iy0);
416 
417  xmax = x + ((b == 0.0) ? 0.5 : a/b);
418  break;
419  }
420  }
421  for (int x = ix0; x > 0; --x) {
422  if (im(x - 1, iy0) <= test) {
423  double a = test - im(x, iy0);
424  double b = im(x - 1, iy0) - im(x, iy0);
425 
426  xmin = x - ((b == 0.0) ? 0.5 : a/b);
427  break;
428  }
429  }
430  for (int y = iy0; y < yh - 1; ++y) {
431  if (im(ix0, y + 1) <= test) {
432  double a = test - im(ix0, y);
433  double b = im(ix0, y + 1) - im(ix0, y);
434 
435  ymax = y + ((b == 0.0) ? 0.5 : a/b);
436  break;
437  }
438  }
439  for (int y = iy0; y > 0; --y) {
440  if (im(ix0, y - 1) <= test) {
441  double a = test - im(ix0, y);
442  double b = im(ix0, y - 1) - im(ix0, y);
443 
444  ymin = y - ((b == 0.0) ? 0.5 : a/b);
445  break;
446  }
447  }
448 /*
449  * Assemble the fitting vector
450  */
451  fit->param[FittedModel::PEAK] = im(ix0, iy0) - sky;
452  fit->param[FittedModel::SKY] = sky;
453  fit->param[FittedModel::X0] = x0;
454  fit->param[FittedModel::Y0] = y0;
455  fit->param[FittedModel::SIGMA] = 0.5*((xmax - xmin)+(ymax - ymin))/2.354;
456  if (fit->param[FittedModel::SIGMA] < fit->toosmall) {
457  fit->param[FittedModel::SIGMA] = fit->toosmall;
458  }
459  fit->elnew = fit->param;
460 
461  fit->status = 0;
462 }
463 
464 /************************************************************************************************************/
468 template<typename PixelT>
469 FittedModel twodg(afw::image::Image<PixelT> const& im,
470  double x0,
471  double y0
472  ) {
473 /*
474  * Initialize the fitting structure
475  */
476  Fit2d fit(im);
477 /*
478  * Initialization
479  */
480  fg2(im, x0, y0, &fit);
481  if (fit.status != 0) {
483  std::copy(&fit.elnew[0], &fit.elnew[0] + fit.elnew.size(), params.begin());
484 
485  return FittedModel(fit.status, params);
486  }
487 /*
488  * Find best-fit model
489  */
490  for (fit.iter = 0; fit.status == 0; ++fit.iter) {
491  curf2(&fit);
492  cond2(&fit);
493  }
494 
496  std::copy(&fit.elnew[0], &fit.elnew[0] + fit.elnew.size(), params.begin());
497 
498  return FittedModel(fit.status, params, fit.iter, fit.flamd, fit.chnew);
499 }
500 //
501 // Explicit instantiations
502 //
503 #define MAKE_TWODG(IMAGE_T) \
504  template FittedModel twodg(IMAGE_T const& im, double x0, double y0)
505 
506 MAKE_TWODG(afw::image::Image<float>);
507 MAKE_TWODG(afw::image::Image<double>);
508 MAKE_TWODG(afw::image::Image<int>);
509 
510 
511 } // end anonymous namespace
512 
514  Control const & ctrl,
515  std::string const & name,
516  afw::table::Schema & schema
517 ) : _ctrl(ctrl),
518  _centroidKey(
519  CentroidResultKey::addFields(schema, name, "centroid from Gaussian Centroid algorithm", NO_UNCERTAINTY)
520  ),
521  _flagHandler(FlagHandler::addFields(schema, name,
522  getFlagDefinitions())),
523  _centroidExtractor(schema, name, true),
524  _centroidChecker(schema, name, ctrl.doFootprintCheck, ctrl.maxDistToPeak)
525 {
526 }
527 
528 
529 template<typename PixelT>
531  double x,
532  double y
533 ) {
534  base::FittedModel fit = base::twodg(image, x, y);
535  double const xCen = fit.params[base::FittedModel::X0];
536  double const yCen = fit.params[base::FittedModel::Y0];
537  return afw::geom::Point2D(xCen, yCen);
538 }
539 
540 
542  afw::table::SourceRecord & measRecord,
543  afw::image::Exposure<float> const & exposure
544 ) const {
545  // get our current best guess about the centroid: either a centroider measurement or peak.
546  afw::geom::Point2D center = _centroidExtractor(measRecord, _flagHandler);
547  CentroidResult result;
548  result.x = center.getX();
549  result.y = center.getY();
550  measRecord.set(_centroidKey, result); // better than NaN
551  typedef afw::image::Image<float> ImageT;
552  ImageT const& image = *exposure.getMaskedImage().getImage();
553 
554  int x = static_cast<int>(center.getX() + 0.5);
555  int y = static_cast<int>(center.getY() + 0.5);
556 
557  x -= image.getX0(); // work in image Pixel coordinates
558  y -= image.getY0();
559 
560  FittedModel fit = twodg(image, x, y); // here's the fitter
561  if (fit.params[FittedModel::PEAK] <= 0) {
562  throw LSST_EXCEPT(
564  NO_PEAK.doc,
566  );
567  }
568  result.x = lsst::afw::image::indexToPosition(image.getX0()) + fit.params[FittedModel::X0];
569  result.y = lsst::afw::image::indexToPosition(image.getY0()) + fit.params[FittedModel::Y0];
570  _flagHandler.setValue(measRecord, FAILURE.number, false); // if we had a suspect flag, we'd set that instead
571  measRecord.set(_centroidKey, result);
572  _centroidChecker(measRecord);
573 }
574 
575 
577  _flagHandler.handleFailure(measRecord, error);
578 }
579 
580 //
581 // Explicit instantiations
582 //
583 #define MAKE_FIT_CENTROID(IMAGE_T) \
584  template afw::geom::Point2D GaussianCentroidAlgorithm::fitCentroid(IMAGE_T const &, double, double)
585 
586 MAKE_FIT_CENTROID(afw::image::Image<float>);
587 MAKE_FIT_CENTROID(afw::image::Image<double>);
588 
590  Control const & ctrl,
591  std::string const & name,
592  afw::table::SchemaMapper & mapper
593 ) :
594  CentroidTransform{name, mapper}
595 {
598  if (flag == GaussianCentroidAlgorithm::FAILURE) continue;
599  if (mapper.getInputSchema().getNames().count(
600  mapper.getInputSchema().join(name, flag.name)) == 0) continue;
601  afw::table::Key<afw::table::Flag> key = mapper.getInputSchema().find<afw::table::Flag>(
602  name + "_" + flag.name).key;
603  mapper.addMapping(key);
604  }
605 }
606 
607 }}} // namespace lsst::meas::base
608 
609 
int y
int iter
std::size_t size() const
return the current size (number of defined elements) of the collection
Definition: FlagHandler.h:133
int tooclose
GaussianCentroidAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema)
int nref
int ncols
T copy(T... args)
std::vector< Raster > rast
double xlamd
afw::table::Key< afw::table::Array< ImagePixelT > > image
Matrix alold
double indexToPosition(double ind)
Vector beold
Simple class used to define and document flags The name and doc constitute the identity of the FlagDe...
Definition: FlagHandler.h:38
A reusable struct for centroid measurements.
Vector elold
Vector sgold
static afw::geom::Point2D fitCentroid(afw::image::Image< PixelT > const &im, double x0, double y0)
Compute centroids with 2-D Gaussian fitter.
CentroidElement x
x (column) coordinate of the measured position
void setValue(afw::table::BaseRecord &record, std::size_t i, bool value) const
Set the flag field corresponding to the given flag index.
Definition: FlagHandler.h:276
Exception to be thrown when a measurement algorithm experiences a known failure mode.
Definition: exceptions.h:48
Vector benew
virtual void fail(afw::table::SourceRecord &measRecord, MeasurementError *error=nullptr) const
Handle an exception thrown by the current algorithm by setting flags in the given record...
int nrows
string name
Matrix alpha
Vector param
STL class.
Vector sgnew
double stnew
Utility class for handling flag fields that indicate the failure modes of an algorithm.
Definition: FlagHandler.h:156
Matrix alnew
double toosmall
MaskedImageT getMaskedImage()
std::vector< double > params
Point< double, 2 > Point2D
double dc2zmin
double flamd
A C++ control class to handle GaussianCentroidAlgorithm&#39;s configuration.
int wide
double dc2zmax
double chold
double stold
int x
int nin
double chisq
#define LSST_EXCEPT(type,...)
GaussianCentroidTransform(Control const &ctrl, std::string const &name, afw::table::SchemaMapper &mapper)
double flamdok
STL class.
int nitmax
double zo
#define MAKE_TWODG(IMAGE_T)
T begin(T... args)
int status
double chnew
Algorithm provides no uncertainy information at all.
Definition: constants.h:42
virtual void measure(afw::table::SourceRecord &measRecord, afw::image::Exposure< float > const &exposure) const
Called to measure a single child source in an image.
static FlagDefinitionList const & getFlagDefinitions()
Vector elnew
void handleFailure(afw::table::BaseRecord &record, MeasurementError const *error=nullptr) const
Handle an expected or unexpected Exception thrown by a measurement algorithm.
Definition: FlagHandler.cc:93
A FunctorKey for CentroidResult.
void set(Key< T > const &key, U const &value)
CentroidElement y
y (row) coordinate of the measured position
#define MAKE_FIT_CENTROID(IMAGE_T)
double ratiomin
vector-type utility class to build a collection of FlagDefinitions
Definition: FlagHandler.h:63
double lost
Base for centroid measurement transformations.
Vector beta