LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
SdssShape.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 
24 #include "lsst/utils/ieee.h"
25 #include "lsst/utils/PowFast.h"
26 
27 #include "boost/tuple/tuple.hpp"
28 #include "Eigen/LU"
29 #include "lsst/pex/logging/Trace.h"
30 #include "lsst/afw/image.h"
31 #include "lsst/afw/detection/Psf.h"
32 #include "lsst/afw/geom/Angle.h"
33 #include "lsst/afw/geom/ellipses.h"
34 #include "lsst/afw/table/Source.h"
35 
39 
40 namespace pexPolicy = lsst::pex::policy;
41 namespace pexExceptions = lsst::pex::exceptions;
42 namespace pexLogging = lsst::pex::logging;
43 namespace afwDet = lsst::afw::detection;
44 namespace afwImage = lsst::afw::image;
45 namespace afwGeom = lsst::afw::geom;
46 
47 namespace lsst {
48 /*
49  * The exponential function that we use, which may be only an approximation to the true value of e^x
50  */
51 #define USE_APPROXIMATE_EXP 1
52 #if USE_APPROXIMATE_EXP
53  lsst::utils::PowFast const& powFast = lsst::utils::getPowFast<11>();
54 #endif
55 
56 inline float
57 approxExp(float x)
58 {
59 #if USE_APPROXIMATE_EXP
60  return powFast.exp(x);
61 #else
62  return std::exp(x);
63 #endif
64 }
65 
66 namespace meas {
67 namespace base {
68 
69 namespace { // anonymous
70 
71 lsst::afw::geom::BoxI set_amom_bbox(int width, int height, float xcen, float ycen,
72  double sigma11_w, double , double sigma22_w, float maxRad);
73 
74 /*****************************************************************************/
75 /*
76  * Error analysis, courtesy of David Johnston, University of Chicago
77  */
78 /*
79  * This function takes the 4 Gaussian parameters A, sigmaXXW and the
80  * sky variance and fills in the Fisher matrix from the least squares fit.
81  *
82  * Following "Numerical Recipes in C" section 15.5, it ignores the 2nd
83  * derivative parts and so the fisher matrix is just a function of these
84  * best fit model parameters. The components are calculated analytically.
85  */
87 calc_fisher(detail::SdssShapeImpl const& shape, // the Shape that we want the the Fisher matrix for
88  float bkgd_var // background variance level for object
89  )
90 {
91  float const A = shape.getI0(); // amplitude
92  float const sigma11W = shape.getIxx();
93  float const sigma12W = shape.getIxy();
94  float const sigma22W = shape.getIyy();
95 
96  double const D = sigma11W*sigma22W - sigma12W*sigma12W;
97 
98  if (D <= std::numeric_limits<double>::epsilon()) {
99  throw LSST_EXCEPT(lsst::pex::exceptions::DomainError,
100  "Determinant is too small calculating Fisher matrix");
101  }
102 /*
103  * a normalization factor
104  */
105  if (bkgd_var <= 0.0) {
106  throw LSST_EXCEPT(lsst::pex::exceptions::DomainError,
107  (boost::format("Background variance must be positive (saw %g)") % bkgd_var).str());
108  }
109  double const F = afwGeom::PI*sqrt(D)/bkgd_var;
110 /*
111  * Calculate the 10 independent elements of the 4x4 Fisher matrix
112  */
114 
115  double fac = F*A/(4.0*D);
116  fisher(0, 0) = F;
117  fisher(0, 1) = fac*sigma22W;
118  fisher(1, 0) = fisher(0, 1);
119  fisher(0, 2) = fac*sigma11W;
120  fisher(2, 0) = fisher(0, 2);
121  fisher(0, 3) = -fac*2*sigma12W;
122  fisher(3, 0) = fisher(0, 3);
123 
124  fac = 3.0*F*A*A/(16.0*D*D);
125  fisher(1, 1) = fac*sigma22W*sigma22W;
126  fisher(2, 2) = fac*sigma11W*sigma11W;
127  fisher(3, 3) = fac*4.0*(sigma12W*sigma12W + D/3.0);
128 
129  fisher(1, 2) = fisher(3, 3)/4.0;
130  fisher(2, 1) = fisher(1, 2);
131  fisher(1, 3) = fac*(-2*sigma22W*sigma12W);
132  fisher(3, 1) = fisher(1, 3);
133  fisher(2, 3) = fac*(-2*sigma11W*sigma12W);
134  fisher(3, 2) = fisher(2, 3);
135 
136  return fisher;
137 }
138 //
139 // Here's a class to allow us to get the Image and variance from an Image or MaskedImage
140 //
141 template<typename ImageT> // general case
142 struct ImageAdaptor {
143  typedef ImageT Image;
144 
145  Image const& getImage(ImageT const& image) const {
146  return image;
147  }
148 
149  double getVariance(ImageT const&, int, int) {
150  return std::numeric_limits<double>::quiet_NaN();
151  }
152 };
153 
154 template<typename T> // specialise to a MaskedImage
155 struct ImageAdaptor<afwImage::MaskedImage<T> > {
156  typedef typename afwImage::MaskedImage<T>::Image Image;
157 
158  Image const& getImage(afwImage::MaskedImage<T> const& mimage) const {
159  return *mimage.getImage();
160  }
161 
162  double getVariance(afwImage::MaskedImage<T> const& mimage, int ix, int iy) {
163  return mimage.at(ix, iy).variance();
164  }
165 };
166 
168 boost::tuple<std::pair<bool, double>, double, double, double>
169 getWeights(double sigma11, double sigma12, double sigma22,
170  bool careful=true
171  ) {
172  double const NaN = std::numeric_limits<double>::quiet_NaN();
173  if (lsst::utils::isnan(sigma11) || lsst::utils::isnan(sigma12) || lsst::utils::isnan(sigma22)) {
174  return boost::make_tuple(std::make_pair(false, NaN), NaN, NaN, NaN);
175  }
176  double const det = sigma11*sigma22 - sigma12*sigma12; // determinant of sigmaXX matrix
177  if (lsst::utils::isnan(det) || det < std::numeric_limits<float>::epsilon()) {
178  double const nan = std::numeric_limits<double>::quiet_NaN();
179  return boost::make_tuple(std::make_pair(false, nan), nan, nan, nan);
180  }
181 
182  if (lsst::utils::isnan(det) || det < std::numeric_limits<float>::epsilon()) { // a suitably small number
183 
184  /*
185  * We have to be a little careful here. For some degenerate cases (e.g. an object that it zero
186  * except on a line) the moments matrix can be singular. We deal with this by adding 1/12 in
187  * quadrature to the principal axes.
188  *
189  * Why bother? Because we use the shape code for e.g. 2nd moment based star selection, and it
190  * needs to be robust.
191  */
192  double const iMin = 1/12.0; // 2nd moment of single pixel
193 
194  if (!careful) {
195  // Don't want to be careful --- report bad determinant
196  return boost::make_tuple(std::make_pair(false, det), NaN, NaN, NaN);
197  }
198 
199  lsst::afw::geom::ellipses::Quadrupole const q(sigma11, sigma22, sigma12); // Ixx, Iyy, Ixy
200  lsst::afw::geom::ellipses::Axes axes(q); // convert to (a, b, theta)
201 
202  axes.setA(::sqrt(::pow(axes.getA(), 2) + iMin));
203  axes.setB(::sqrt(::pow(axes.getB(), 2) + iMin));
204 
205  lsst::afw::geom::ellipses::Quadrupole const q2(axes); // back to Ixx etc.
206 
207  lsst::afw::geom::ellipses::Quadrupole::Matrix const mat = q2.getMatrix().inverse();
208 
209  return boost::make_tuple(std::make_pair(true, q2.getDeterminant()), mat(0, 0), mat(1, 0), mat(1, 1));
210  }
211 
212  assert(sigma11*sigma22 >= sigma12*sigma12 - std::numeric_limits<float>::epsilon());
213 
214  return boost::make_tuple(std::make_pair(true, det), sigma22/det, -sigma12/det, sigma11/det);
215 }
216 
218 bool shouldInterp(double sigma11, double sigma22, double det) {
219  float const xinterp = 0.25; // I.e. 0.5*0.5
220  return (sigma11 < xinterp || sigma22 < xinterp || det < xinterp*xinterp);
221 }
222 
223 
224 /************************************************************************************************************/
225 /*
226  * Decide on the bounding box for the region to examine while calculating
227  * the adaptive moments
228  */
229 lsst::afw::geom::BoxI set_amom_bbox(int width, int height, // size of region
230  float xcen, float ycen, // centre of object
231  double sigma11_w, // quadratic moments of the
232  double , // weighting function
233  double sigma22_w, // xx, xy, and yy
234  float maxRad = 1000 // Maximum radius of area to use
235  )
236 {
237  float rad = 4*sqrt(((sigma11_w > sigma22_w) ? sigma11_w : sigma22_w));
238 
239  if (rad > maxRad) {
240  rad = maxRad;
241  }
242 
243  int ix0 = static_cast<int>(xcen - rad - 0.5);
244  ix0 = (ix0 < 0) ? 0 : ix0;
245  int iy0 = static_cast<int>(ycen - rad - 0.5);
246  iy0 = (iy0 < 0) ? 0 : iy0;
247  lsst::afw::geom::Point2I llc(ix0, iy0); // Desired lower left corner
248 
249  int ix1 = static_cast<int>(xcen + rad + 0.5);
250  if (ix1 >= width) {
251  ix1 = width - 1;
252  }
253  int iy1 = static_cast<int>(ycen + rad + 0.5);
254  if (iy1 >= height) {
255  iy1 = height - 1;
256  }
257  lsst::afw::geom::Point2I urc(ix1, iy1); // Desired upper right corner
258 
259  return lsst::afw::geom::BoxI(llc, urc);
260 }
261 
262 /*****************************************************************************/
263 /*
264  * Calculate weighted moments of an object up to 2nd order
265  */
266 template<bool fluxOnly, typename ImageT>
267 static int
268 calcmom(ImageT const& image, // the image data
269  float xcen, float ycen, // centre of object
270  lsst::afw::geom::BoxI bbox, // bounding box to consider
271  float bkgd, // data's background level
272  bool interpflag, // interpolate within pixels?
273  double w11, double w12, double w22, // weights
274  double *pI0, // amplitude of fit
275  double *psum, // sum w*I (if !NULL)
276  double *psumx, double *psumy, // sum [xy]*w*I (if !fluxOnly)
277  double *psumxx, double *psumxy, double *psumyy, // sum [xy]^2*w*I (if !fluxOnly)
278  double *psums4 // sum w*I*weight^2 (if !fluxOnly && !NULL)
279  )
280 {
281 
282  float tmod, ymod;
283  float X, Y; // sub-pixel interpolated [xy]
284  float weight;
285  float tmp;
286  double sum, sumx, sumy, sumxx, sumyy, sumxy, sums4;
287 #define RECALC_W 0 // estimate sigmaXX_w within BBox?
288 #if RECALC_W
289  double wsum, wsumxx, wsumxy, wsumyy;
290 
291  wsum = wsumxx = wsumxy = wsumyy = 0;
292 #endif
293 
294  assert(w11 >= 0); // i.e. it was set
295  if (fabs(w11) > 1e6 || fabs(w12) > 1e6 || fabs(w22) > 1e6) {
296  return(-1);
297  }
298 
299  sum = sumx = sumy = sumxx = sumxy = sumyy = sums4 = 0;
300 
301  int const ix0 = bbox.getMinX(); // corners of the box being analyzed
302  int const ix1 = bbox.getMaxX();
303  int const iy0 = bbox.getMinY(); // corners of the box being analyzed
304  int const iy1 = bbox.getMaxY();
305 
306  if (ix0 < 0 || ix1 >= image.getWidth() || iy0 < 0 || iy1 >= image.getHeight()) {
307  return -1;
308  }
309 
310  for (int i = iy0; i <= iy1; ++i) {
311  typename ImageT::x_iterator ptr = image.x_at(ix0, i);
312  float const y = i - ycen;
313  float const y2 = y*y;
314  float const yl = y - 0.375;
315  float const yh = y + 0.375;
316  for (int j = ix0; j <= ix1; ++j, ++ptr) {
317  float x = j - xcen;
318  if (interpflag) {
319  float const xl = x - 0.375;
320  float const xh = x + 0.375;
321 
322  float expon = xl*xl*w11 + yl*yl*w22 + 2.0*xl*yl*w12;
323  tmp = xh*xh*w11 + yh*yh*w22 + 2.0*xh*yh*w12;
324  expon = (expon > tmp) ? expon : tmp;
325  tmp = xl*xl*w11 + yh*yh*w22 + 2.0*xl*yh*w12;
326  expon = (expon > tmp) ? expon : tmp;
327  tmp = xh*xh*w11 + yl*yl*w22 + 2.0*xh*yl*w12;
328  expon = (expon > tmp) ? expon : tmp;
329 
330  if (expon <= 9.0) {
331  tmod = *ptr - bkgd;
332  for (Y = yl; Y <= yh; Y += 0.25) {
333  double const interpY2 = Y*Y;
334  for (X = xl; X <= xh; X += 0.25) {
335  double const interpX2 = X*X;
336  double const interpXy = X*Y;
337  expon = interpX2*w11 + 2*interpXy*w12 + interpY2*w22;
338  weight = approxExp(-0.5*expon);
339 
340  ymod = tmod*weight;
341  sum += ymod;
342  if (!fluxOnly) {
343  sumx += ymod*(X + xcen);
344  sumy += ymod*(Y + ycen);
345 #if RECALC_W
346  wsum += weight;
347 
348  tmp = interpX2*weight;
349  wsumxx += tmp;
350  sumxx += tmod*tmp;
351 
352  tmp = interpXy*weight;
353  wsumxy += tmp;
354  sumxy += tmod*tmp;
355 
356  tmp = interpY2*weight;
357  wsumyy += tmp;
358  sumyy += tmod*tmp;
359 #else
360  sumxx += interpX2*ymod;
361  sumxy += interpXy*ymod;
362  sumyy += interpY2*ymod;
363 #endif
364  sums4 += expon*expon*ymod;
365  }
366  }
367  }
368  }
369  } else {
370  float x2 = x*x;
371  float xy = x*y;
372  float expon = x2*w11 + 2*xy*w12 + y2*w22;
373 
374  if (expon <= 14.0) {
375  weight = approxExp(-0.5*expon);
376  tmod = *ptr - bkgd;
377  ymod = tmod*weight;
378  sum += ymod;
379  if (!fluxOnly) {
380  sumx += ymod*j;
381  sumy += ymod*i;
382 #if RECALC_W
383  wsum += weight;
384 
385  tmp = x2*weight;
386  wsumxx += tmp;
387  sumxx += tmod*tmp;
388 
389  tmp = xy*weight;
390  wsumxy += tmp;
391  sumxy += tmod*tmp;
392 
393  tmp = y2*weight;
394  wsumyy += tmp;
395  sumyy += tmod*tmp;
396 #else
397  sumxx += x2*ymod;
398  sumxy += xy*ymod;
399  sumyy += y2*ymod;
400 #endif
401  sums4 += expon*expon*ymod;
402  }
403  }
404  }
405  }
406  }
407 
408 
409  boost::tuple<std::pair<bool, double>, double, double, double> const weights = getWeights(w11, w12, w22);
410  double const detW = weights.get<1>()*weights.get<3>() - std::pow(weights.get<2>(), 2);
411  *pI0 = sum/(afwGeom::PI*sqrt(detW));
412  if (psum) {
413  *psum = sum;
414  }
415  if (!fluxOnly) {
416  *psumx = sumx;
417  *psumy = sumy;
418  *psumxx = sumxx;
419  *psumxy = sumxy;
420  *psumyy = sumyy;
421  if (psums4 != NULL) {
422  *psums4 = sums4;
423  }
424  }
425 
426 #if RECALC_W
427  if (wsum > 0 && !fluxOnly) {
428  double det = w11*w22 - w12*w12;
429  wsumxx /= wsum;
430  wsumxy /= wsum;
431  wsumyy /= wsum;
432  printf("%g %g %g %g %g %g\n", w22/det, -w12/det, w11/det, wsumxx, wsumxy, wsumyy);
433  }
434 #endif
435 
436  return (fluxOnly || (sum > 0 && sumxx > 0 && sumyy > 0)) ? 0 : -1;
437 }
438 
439 } // anonymous namespace
440 
441 /************************************************************************************************************/
442 
443 namespace detail {
447 template<typename ImageT>
448 bool getAdaptiveMoments(ImageT const& mimage, double bkgd, double xcen, double ycen, double shiftmax,
449  SdssShapeImpl *shape, int maxIter, float tol1, float tol2)
450 {
451  double I0 = 0; // amplitude of best-fit Gaussian
452  double sum; // sum of intensity*weight
453  double sumx, sumy; // sum ((int)[xy])*intensity*weight
454  double sumxx, sumxy, sumyy; // sum {x^2,xy,y^2}*intensity*weight
455  double sums4; // sum intensity*weight*exponent^2
456  float const xcen0 = xcen; // initial centre
457  float const ycen0 = ycen; // of object
458 
459  double sigma11W = 1.5; // quadratic moments of the
460  double sigma12W = 0.0; // weighting fcn;
461  double sigma22W = 1.5; // xx, xy, and yy
462 
463  double w11 = -1, w12 = -1, w22 = -1; // current weights for moments; always set when iter == 0
464  float e1_old = 1e6, e2_old = 1e6; // old values of shape parameters e1 and e2
465  float sigma11_ow_old = 1e6; // previous version of sigma11_ow
466 
467  typename ImageAdaptor<ImageT>::Image const &image = ImageAdaptor<ImageT>().getImage(mimage);
468 
469  if (lsst::utils::isnan(xcen) || lsst::utils::isnan(ycen)) {
470  // Can't do anything
472  return false;
473  }
474 
475  bool interpflag = false; // interpolate finer than a pixel?
477  int iter = 0; // iteration number
478  for (; iter < maxIter; iter++) {
479  bbox = set_amom_bbox(image.getWidth(), image.getHeight(), xcen, ycen, sigma11W, sigma12W, sigma22W);
480  boost::tuple<std::pair<bool, double>, double, double, double> weights =
481  getWeights(sigma11W, sigma12W, sigma22W);
482  if (!weights.get<0>().first) {
484  break;
485  }
486 
487  double const detW = weights.get<0>().second;
488 
489 #if 0 // this form was numerically unstable on my G4 powerbook
490  assert(detW >= 0.0);
491 #else
492  assert(sigma11W*sigma22W >= sigma12W*sigma12W - std::numeric_limits<float>::epsilon());
493 #endif
494 
495  {
496  const double ow11 = w11; // old
497  const double ow12 = w12; // values
498  const double ow22 = w22; // of w11, w12, w22
499 
500  w11 = weights.get<1>();
501  w12 = weights.get<2>();
502  w22 = weights.get<3>();
503 
504  if (shouldInterp(sigma11W, sigma22W, detW)) {
505  if (!interpflag) {
506  interpflag = true; // N.b.: stays set for this object
507  if (iter > 0) {
508  sigma11_ow_old = 1.e6; // force at least one more iteration
509  w11 = ow11;
510  w12 = ow12;
511  w22 = ow22;
512  iter--; // we didn't update wXX
513  }
514  }
515  }
516  }
517 
518  if (calcmom<false>(image, xcen, ycen, bbox, bkgd, interpflag, w11, w12, w22,
519  &I0, &sum, &sumx, &sumy, &sumxx, &sumxy, &sumyy, &sums4) < 0) {
521  break;
522  }
523 #if 0
524 /*
525  * Find new centre
526  *
527  * This is only needed if we update the centre; if we use the input position we've already done the work
528  */
529  xcen = sumx/sum;
530  ycen = sumy/sum;
531 #endif
532  shape->setX(sumx/sum); // update centroid. N.b. we're not setting errors here
533  shape->setY(sumy/sum);
534 
535  if (fabs(shape->getX() - xcen0) > shiftmax || fabs(shape->getY() - ycen0) > shiftmax) {
537  }
538 /*
539  * OK, we have the centre. Proceed to find the second moments.
540  */
541  float const sigma11_ow = sumxx/sum; // quadratic moments of
542  float const sigma22_ow = sumyy/sum; // weight*object
543  float const sigma12_ow = sumxy/sum; // xx, xy, and yy
544 
545  if (sigma11_ow <= 0 || sigma22_ow <= 0) {
547  break;
548  }
549 
550  float const d = sigma11_ow + sigma22_ow; // current values of shape parameters
551  float const e1 = (sigma11_ow - sigma22_ow)/d;
552  float const e2 = 2.0*sigma12_ow/d;
553 /*
554  * Did we converge?
555  */
556  if (iter > 0 &&
557  fabs(e1 - e1_old) < tol1 && fabs(e2 - e2_old) < tol1 &&
558  fabs(sigma11_ow/sigma11_ow_old - 1.0) < tol2 ) {
559  break; // yes; we converged
560  }
561 
562  e1_old = e1;
563  e2_old = e2;
564  sigma11_ow_old = sigma11_ow;
565 /*
566  * Didn't converge, calculate new values for weighting function
567  *
568  * The product of two Gaussians is a Gaussian:
569  * <x^2 exp(-a x^2 - 2bxy - cy^2) exp(-Ax^2 - 2Bxy - Cy^2)> =
570  * <x^2 exp(-(a + A) x^2 - 2(b + B)xy - (c + C)y^2)>
571  * i.e. the inverses of the covariances matrices add.
572  *
573  * We know sigmaXX_ow and sigmaXXW, the covariances of the weighted object
574  * and of the weights themselves. We can estimate the object's covariance as
575  * sigmaXX_ow^-1 - sigmaXXW^-1
576  * and, as we want to find a set of weights with the _same_ covariance as the
577  * object we take this to be the an estimate of our correct weights.
578  *
579  * N.b. This assumes that the object is roughly Gaussian.
580  * Consider the object:
581  * O == delta(x + p) + delta(x - p)
582  * the covariance of the weighted object is equal to that of the unweighted
583  * object, and this prescription fails badly. If we detect this, we set
584  * the UNWEIGHTED flag, and calculate the UNweighted moments
585  * instead.
586  */
587  {
588  float n11, n12, n22; // elements of inverse of next guess at weighting function
589  float ow11, ow12, ow22; // elements of inverse of sigmaXX_ow
590 
591  boost::tuple<std::pair<bool, double>, double, double, double> weights =
592  getWeights(sigma11_ow, sigma12_ow, sigma22_ow);
593  if (!weights.get<0>().first) {
595  break;
596  }
597 
598  ow11 = weights.get<1>();
599  ow12 = weights.get<2>();
600  ow22 = weights.get<3>();
601 
602  n11 = ow11 - w11;
603  n12 = ow12 - w12;
604  n22 = ow22 - w22;
605 
606  weights = getWeights(n11, n12, n22, false);
607  if (!weights.get<0>().first) {
608  // product-of-Gaussians assumption failed
610  break;
611  }
612 
613  sigma11W = weights.get<1>();
614  sigma12W = weights.get<2>();
615  sigma22W = weights.get<3>();
616  }
617 
618  if (sigma11W <= 0 || sigma22W <= 0) {
620  break;
621  }
622  }
623 
624  if (iter == maxIter) {
627  }
628 
629  if (sumxx + sumyy == 0.0) {
631  }
632 /*
633  * Problems; try calculating the un-weighted moments
634  */
635  if (shape->getFlag(SdssShapeImpl::UNWEIGHTED)) {
636  w11 = w22 = w12 = 0;
637  if (calcmom<false>(image, xcen, ycen, bbox, bkgd, interpflag, w11, w12, w22,
638  &I0, &sum, &sumx, &sumy, &sumxx, &sumxy, &sumyy, NULL) < 0 || sum <= 0) {
641 
642  if (sum > 0) {
643  shape->setIxx(1/12.0); // a single pixel
644  shape->setIxy(0.0);
645  shape->setIyy(1/12.0);
646  }
647 
648  return false;
649  }
650 
651  sigma11W = sumxx/sum; // estimate of object moments
652  sigma12W = sumxy/sum; // usually, object == weight
653  sigma22W = sumyy/sum; // at this point
654  }
655 
656  shape->setI0(I0);
657  shape->setIxx(sigma11W);
658  shape->setIxy(sigma12W);
659  shape->setIyy(sigma22W);
660  shape->setIxy4(sums4/sum);
661 
662  if (shape->getIxx() + shape->getIyy() != 0.0) {
663  int const ix = lsst::afw::image::positionToIndex(xcen);
664  int const iy = lsst::afw::image::positionToIndex(ycen);
665 
666  if (ix >= 0 && ix < mimage.getWidth() && iy >= 0 && iy < mimage.getHeight()) {
667  float const bkgd_var =
668  ImageAdaptor<ImageT>().getVariance(mimage, ix, iy); // XXX Overestimate as it includes object
669 
670  if (bkgd_var > 0.0) { // NaN is not > 0.0
671  if (!(shape->getFlag(SdssShapeImpl::UNWEIGHTED))) {
672  SdssShapeImpl::Matrix4 fisher = calc_fisher(*shape, bkgd_var); // Fisher matrix
673  shape->setCovar(fisher.inverse());
674  }
675  }
676  }
677  }
678 
679  return true;
680 }
681 
690 template<typename ImageT>
691 std::pair<double, double>
692 getFixedMomentsFlux(ImageT const& image,
693  double bkgd,
694  double xcen,
695  double ycen,
696  SdssShapeImpl const& shape_
697  )
698 {
699  SdssShapeImpl shape = shape_; // we need a mutable copy
700 
701  afwGeom::BoxI const& bbox = set_amom_bbox(image.getWidth(), image.getHeight(), xcen, ycen,
702  shape.getIxx(), shape.getIxy(), shape.getIyy());
703 
704  boost::tuple<std::pair<bool, double>, double, double, double> weights =
705  getWeights(shape.getIxx(), shape.getIxy(), shape.getIyy());
706  double const NaN = std::numeric_limits<double>::quiet_NaN();
707  if (!weights.get<0>().first) {
708  return std::make_pair(NaN, NaN);
709  }
710 
711  double const w11 = weights.get<1>();
712  double const w12 = weights.get<2>();
713  double const w22 = weights.get<3>();
714  bool const interp = shouldInterp(shape.getIxx(), shape.getIyy(), weights.get<0>().second);
715 
716  double I0 = 0; // amplitude of Gaussian
717  calcmom<true>(ImageAdaptor<ImageT>().getImage(image), xcen - image.getX0(), ycen - image.getY0(),
718  bbox, bkgd, interp, w11, w12, w22,
719  &I0, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
720  /*
721  * We have enerything we need, but it isn't quite packaged right; we need an initialised SdssShapeImpl
722  */
723  shape.setI0(I0);
724 
725  {
726  int ix = static_cast<int>(xcen - image.getX0());
727  int iy = static_cast<int>(ycen - image.getY0());
728  float bkgd_var =
729  ImageAdaptor<ImageT>().getVariance(image, ix, iy); // XXX Overestimate as it includes object
730 
731  SdssShapeImpl::Matrix4 fisher = calc_fisher(shape, bkgd_var); // Fisher matrix
732 
733  shape.setCovar(fisher.inverse());
734  }
735 
736  double const scale = shape.getFluxScale();
737  return std::make_pair(shape.getI0()*scale, shape.getI0Err()*scale);
738 }
739 
740 } // end detail namespace
741 
742 
744  xy4(std::numeric_limits<ShapeElement>::quiet_NaN()),
745  xy4Sigma(std::numeric_limits<ShapeElement>::quiet_NaN()),
746  flux_xx_Cov(std::numeric_limits<ErrElement>::quiet_NaN()),
747  flux_yy_Cov(std::numeric_limits<ErrElement>::quiet_NaN()),
748  flux_xy_Cov(std::numeric_limits<ErrElement>::quiet_NaN())
749 {}
750 
751 static boost::array<FlagDefinition,SdssShapeAlgorithm::N_FLAGS> const flagDefs = {{
752  {"flag", "general failure flag, set if anything went wrong"},
753  {"flag_unweightedBad", "Both weighted and unweighted moments were invalid"},
754  {"flag_unweighted", "Weighted moments converged to an invalid value; using unweighted moments"},
755  {"flag_shift", "centroid shifted by more than the maximum allowed amount"},
756  {"flag_maxIter", "Too many iterations in adaptive moments"}
757  }};
758 
761  std::string const & name
762 ) {
764  r._shapeResult = ShapeResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments",
765  SIGMA_ONLY);
766  r._centroidResult = CentroidResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments",
767  SIGMA_ONLY);
768  r._fluxResult = FluxResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments");
769  r._xy4 = schema.addField<ShapeElement>(
770  // TODO: get more mathematically precise documentation on this from RHL
771  schema.join(name, "xy4"), "4th moment used in certain shear-estimation algorithms", "pixels^4"
772  );
773  r._xy4Sigma = schema.addField<ErrElement>(
774  schema.join(name, "xy4Sigma"),
775  "uncertainty on %s" + schema.join(name, "xy4"), "pixels^4"
776  );
777  r._flux_xx_Cov = schema.addField<ErrElement>(
778  schema.join(name, "flux", "xx", "Cov"),
779  (boost::format("uncertainty covariance between %s and %s")
780  % schema.join(name, "flux") % schema.join(name, "xx")).str(),
781  "dn*pixels^2"
782  );
783  r._flux_yy_Cov = schema.addField<ErrElement>(
784  schema.join(name, "flux", "yy", "Cov"),
785  (boost::format("uncertainty covariance between %s and %s")
786  % schema.join(name, "flux") % schema.join(name, "yy")).str(),
787  "dn*pixels^2"
788  );
789  r._flux_xy_Cov = schema.addField<ErrElement>(
790  schema.join(name, "flux", "xy", "Cov"),
791  (boost::format("uncertainty covariance between %s and %s")
792  % schema.join(name, "flux") % schema.join(name, "xy")).str(),
793  "dn*pixels^2"
794  );
795  r._flagHandler = FlagHandler::addFields(schema, name, flagDefs.begin(), flagDefs.end());
796  return r;
797 }
798 
800  _shapeResult(s),
801  _centroidResult(s),
802  _fluxResult(s),
803  _xy4(s["xy4"]),
804  _xy4Sigma(s["xy4Sigma"]),
805  _flux_xx_Cov(s["flux"]["xx"]["Cov"]),
806  _flux_yy_Cov(s["flux"]["yy"]["Cov"]),
807  _flux_xy_Cov(s["flux"]["xy"]["Cov"]),
808  _flagHandler(s, flagDefs.begin(), flagDefs.end())
809 {}
810 
812  SdssShapeResult result;
813  static_cast<ShapeResult&>(result) = record.get(_shapeResult);
814  static_cast<CentroidResult&>(result) = record.get(_centroidResult);
815  static_cast<FluxResult&>(result) = record.get(_fluxResult);
816  result.xy4 = record.get(_xy4);
817  result.xy4Sigma = record.get(_xy4Sigma);
818  result.flux_xx_Cov = record.get(_flux_xx_Cov);
819  result.flux_yy_Cov = record.get(_flux_yy_Cov);
820  result.flux_xy_Cov = record.get(_flux_xy_Cov);
821  for (int n = 0; n < SdssShapeAlgorithm::N_FLAGS; ++n) {
822  result.flags[n] = _flagHandler.getValue(record, n);
823  }
824  return result;
825 }
826 
828  record.set(_shapeResult, value);
829  record.set(_centroidResult, value);
830  record.set(_fluxResult, value);
831  record.set(_xy4, value.xy4);
832  record.set(_xy4Sigma, value.xy4Sigma);
833  record.set(_flux_xx_Cov, value.flux_xx_Cov);
834  record.set(_flux_yy_Cov, value.flux_yy_Cov);
835  record.set(_flux_xy_Cov, value.flux_xy_Cov);
836  for (int n = 0; n < SdssShapeAlgorithm::N_FLAGS; ++n) {
837  _flagHandler.setValue(record, n, value.flags[n]);
838  }
839 }
840 
842  return _shapeResult == other._shapeResult &&
843  _centroidResult == other._centroidResult &&
844  _fluxResult == other._fluxResult &&
845  _xy4 == other._xy4 &&
846  _xy4Sigma == other._xy4Sigma &&
847  _flux_xx_Cov == other._flux_xx_Cov &&
848  _flux_yy_Cov == other._flux_yy_Cov &&
849  _flux_xy_Cov == other._flux_xy_Cov;
850  // don't bother with flags - if we've gotten this far, it's basically impossible the flags don't match
851 }
852 
854  return _shapeResult.isValid() &&
856  _fluxResult.isValid() &&
857  _xy4.isValid() &&
858  _xy4Sigma.isValid() &&
859  _flux_xx_Cov.isValid() &&
860  _flux_yy_Cov.isValid() &&
862  // don't bother with flags - if we've gotten this far, it's basically impossible the flags don't match
863 }
864 
866  Control const & ctrl,
867  std::string const & name,
869 )
870  : _resultKey(ResultKey::addFields(schema, name)),
871  _centroidExtractor(schema, name)
872 {}
873 
874 template <typename T>
876  afw::image::Image<T> const & exposure,
877  afw::detection::Footprint const & footprint,
878  afw::geom::Point2D const & center,
879  Control const & control
880 ) {
881  throw LSST_EXCEPT(
882  pex::exceptions::LogicError,
883  "Not implemented"
884  );
885 }
886 
887 template <typename T>
889  afw::image::MaskedImage<T> const & mimage,
890  afw::detection::Footprint const & footprint,
891  afw::geom::Point2D const & center,
892  Control const & control
893 ) {
894  typedef typename afw::image::MaskedImage<T> MaskedImageT;
895  double xcen = center.getX(); // object's column position
896  double ycen = center.getY(); // object's row position
897 
898  xcen -= mimage.getX0(); // work in image Pixel coordinates
899  ycen -= mimage.getY0();
900 
901  float shiftmax = control.maxShift; // Max allowed centroid shift
902  if (shiftmax < 2) {
903  shiftmax = 2;
904  } else if (shiftmax > 10) {
905  shiftmax = 10;
906  }
907 
908  SdssShapeResult result;
909  detail::SdssShapeImpl shapeImpl;
910  try {
911  detail::getAdaptiveMoments(mimage, control.background, xcen, ycen, shiftmax, &shapeImpl,
912  control.maxIter, control.tol1, control.tol2);
913  } catch (pex::exceptions::Exception & err) {
914  result.flags[FAILURE] = true;
915  }
916 
917  result.x = shapeImpl.getX() + mimage.getX0();
918  result.y = shapeImpl.getY() + mimage.getY0();
919  // FIXME: should do off-diagonal covariance elements too
920  result.xSigma = shapeImpl.getXErr();
921  result.ySigma = shapeImpl.getYErr();
922  result.xx = shapeImpl.getIxx();
923  result.yy = shapeImpl.getIyy();
924  result.xy = shapeImpl.getIxy();
925  // FIXME: should do off-diagonal covariance elements too
926  result.xxSigma = shapeImpl.getIxxErr();
927  result.yySigma = shapeImpl.getIyyErr();
928  result.xySigma = shapeImpl.getIxyErr();
929 
930  // Now set the flags from SdssShapeImpl
931  for (int n = 0; n < detail::SdssShapeImpl::N_FLAGS; ++n) {
932  if (shapeImpl.getFlag(detail::SdssShapeImpl::Flag(n))) {
933  result.flags[n + 1] = true;
934  result.flags[FAILURE] = true;
935  }
936  }
937  return result;
938 }
939 
941  afw::table::SourceRecord & measRecord,
942  afw::image::Exposure<float> const & exposure
943 ) const {
944  if (!measRecord.getFootprint()) {
945  throw LSST_EXCEPT(
946  pex::exceptions::RuntimeError,
947  "No Footprint attached to SourceRecord"
948  );
949  }
950  SdssShapeResult result = apply(
951  exposure.getMaskedImage(), *measRecord.getFootprint(),
953  _ctrl
954  );
955  measRecord.set(_resultKey, result);
956 }
957 
959  afw::table::SourceRecord & measRecord,
960  MeasurementError * error
961 ) const {
962  _resultKey.getFlagHandler().handleFailure(measRecord, error);
963 }
964 
965 
966 
967 #define INSTANTIATE_IMAGE(IMAGE) \
968  template bool detail::getAdaptiveMoments<IMAGE>( \
969  IMAGE const&, double, double, double, double, SdssShapeImpl*, int, float, float); \
970  template std::pair<double, double> detail::getFixedMomentsFlux<IMAGE>( \
971  IMAGE const&, double, double, double, detail::SdssShapeImpl const&); \
972 
973 #define INSTANTIATE_PIXEL(PIXEL) \
974  INSTANTIATE_IMAGE(lsst::afw::image::Image<PIXEL>); \
975  INSTANTIATE_IMAGE(lsst::afw::image::MaskedImage<PIXEL>);
976 
977 INSTANTIATE_PIXEL(int);
978 INSTANTIATE_PIXEL(float);
979 INSTANTIATE_PIXEL(double);
980 
981 #define INSTANTIATE(T) \
982  template SdssShapeResult SdssShapeAlgorithm::apply( \
983  afw::image::MaskedImage<T> const & exposure, \
984  afw::detection::Footprint const & footprint, \
985  afw::geom::Point2D const & position, \
986  Control const & ctrl \
987  ); \
988  template SdssShapeResult SdssShapeAlgorithm::apply( \
989  afw::image::Image<T> const & exposure, \
990  afw::detection::Footprint const & footprint, \
991  afw::geom::Point2D const & position, \
992  Control const & ctrl \
993  )
994 
995 INSTANTIATE(float);
996 INSTANTIATE(double);
997 
998 }}} // end namespace lsst::meas::base
999 
bool isValid() const
Return true if the key was initialized to valid offset.
Definition: Key.h:83
An ellipse core with quadrupole moments as parameters.
Definition: Quadrupole.h:45
Defines the fields and offsets for a table.
Definition: Schema.h:46
ShapeElement xy4
A fourth moment used in lensing (RHL needs to clarify; not in the old docs)
Definition: SdssShape.h:200
static SdssShapeResultKey addFields(afw::table::Schema &schema, std::string const &name)
Add the appropriate fields to a Schema, and return a SdssShapeResultKey that manages them...
Definition: SdssShape.cc:759
bool getValue(afw::table::BaseRecord const &record, int i) const
Definition: FlagHandler.h:68
int getMaxY() const
Definition: Box.h:129
afw::table::Key< ErrElement > _xy4Sigma
Definition: SdssShape.h:117
Eigen::Matrix< double, 2, 2, Eigen::DontAlign > Matrix
Matrix type for the matrix representation of Quadrupole parameters.
Definition: Quadrupole.h:54
A proxy type for name lookups in a Schema.
Definition: Schema.h:370
ErrElement ySigma
1-Sigma uncertainty on y (sqrt of variance)
afw::table::Key< ErrElement > _flux_yy_Cov
Definition: SdssShape.h:119
static Result apply(afw::image::MaskedImage< T > const &image, afw::detection::Footprint const &footprint, afw::geom::Point2D const &position, Control const &ctrl=Control())
SafeCentroidExtractor _centroidExtractor
Definition: SdssShape.h:183
Public header class for ellipse library.
#define INSTANTIATE_PIXEL(PIXEL)
Definition: SdssShape.cc:973
afw::table::Key< ShapeElement > _xy4
Definition: SdssShape.h:116
float tol2
&quot;Convergence tolerance for FWHM&quot; ;
Definition: SdssShape.h:52
int positionToIndex(double pos)
Convert image position to nearest integer index.
Definition: ImageUtils.h:69
FlagHandler const & getFlagHandler() const
Definition: SdssShape.h:110
int iter
int y
Box2I BoxI
Definition: Box.h:479
A class to contain the data, WCS, and other information needed to describe an image of the sky...
Definition: Exposure.h:48
Only the diagonal elements of the covariance matrix are provided.
Definition: constants.h:43
A reusable struct for centroid measurements.
iterator at(int const x, int const y) const
Return an iterator at the point (x, y)
Definition: MaskedImage.cc:663
ErrElement xy4Sigma
1-Sigma uncertainty on xy4
Definition: SdssShape.h:201
double background
&quot;Additional value to add to background&quot; ;
Definition: SdssShape.h:48
definition of the Trace messaging facilities
ErrElement xSigma
1-Sigma uncertainty on x (sqrt of variance)
CentroidElement x
x (column) coordinate of the measured position
boost::shared_ptr< Footprint > getFootprint() const
Definition: Source.h:89
ImagePtr getImage(bool const noThrow=false) const
Return a (Ptr to) the MaskedImage&#39;s image.
Definition: MaskedImage.h:869
int maxIter
&quot;Maximum number of iterations&quot; ;
Definition: SdssShape.h:49
bool isValid() const
Return True if the centroid key is valid.
Key< T > addField(Field< T > const &field, bool doReplace=false)
Add a new field to the Schema, and return the associated Key.
Exception to be thrown when a measurement algorithm experiences a known failure mode.
Definition: exceptions.h:48
ErrElement xySigma
1-Sigma uncertainty on xy (sqrt of variance)
static FluxResultKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
int isnan(T t)
Definition: ieee.h:110
float tol1
&quot;Convergence tolerance for e1,e2&quot; ;
Definition: SdssShape.h:51
double maxShift
&quot;Maximum centroid shift, limited to 2-10&quot; ;
Definition: SdssShape.h:50
bool isValid() const
Return True if the key is valid.
Definition: SdssShape.cc:853
float ErrElement
Definition: constants.h:51
ErrElement flux_yy_Cov
flux, yy term in the uncertainty covariance matrix
Definition: SdssShape.h:203
bool operator==(SdssShapeResultKey const &other) const
Compare the FunctorKey for equality with another, using the underlying Keys.
Definition: SdssShape.cc:841
An integer coordinate rectangle.
Definition: Box.h:53
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
int d
Definition: KDTree.cc:89
virtual void measure(afw::table::SourceRecord &measRecord, afw::image::Exposure< float > const &exposure) const
Definition: SdssShape.cc:940
float exp(float x) const
Evaluate exp(x). (x must be in (-87.3ish, 88.7ish))
Definition: PowFast.cc:151
An include file to include the header files for lsst::afw::image.
A reusable struct for moments-based shape measurements.
float approxExp(float x)
Definition: SdssShape.cc:57
double const PI
The ratio of a circle&#39;s circumference to diameter.
Definition: Angle.h:18
MaskedImageT getMaskedImage()
Return the MaskedImage.
Definition: Exposure.h:150
Result object SdssShapeAlgorithm.
Definition: SdssShape.h:198
tbl::Schema schema
Definition: CoaddPsf.cc:324
double getFluxScale() const
Return multiplier that transforms I0 to a total flux.
Definition: SdssShapeImpl.h:89
Eigen::Matrix< double, 4, 4, Eigen::DontAlign > Matrix4
Definition: SdssShapeImpl.h:18
int x
int getMinY() const
Definition: Box.h:125
SdssShapeResult()
Constructor; initializes everything to NaN.
Definition: SdssShape.cc:743
bool isValid() const
Return True if both the flux and fluxSigma Keys are valid.
SdssShapeAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema)
Definition: SdssShape.cc:865
bool isValid() const
Return True if the shape key is valid.
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:77
int getMinX() const
Definition: Box.h:124
static CentroidResultKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc, UncertaintyEnum uncertainty)
Add the appropriate fields to a Schema, and return a CentroidResultKey that manages them...
void setValue(afw::table::BaseRecord &record, int i, bool value) const
Definition: FlagHandler.h:72
A C++ control class to handle SdssShapeAlgorithm&#39;s configuration.
Definition: SdssShape.h:46
double sum
Definition: NaiveFlux.cc:137
A set of pixels in an Image.
Definition: Footprint.h:73
std::pair< double, double > getFixedMomentsFlux(ImageT const &image, double bkgd, double xcen, double ycen, SdssShapeImpl const &shape_)
Return the flux of an object, using the aperture described by the SdssShape object.
Definition: SdssShape.cc:692
virtual void set(afw::table::BaseRecord &record, SdssShapeResult const &value) const
Set a CentroidResult in the given record.
Definition: SdssShape.cc:827
std::bitset< SdssShapeAlgorithm::N_FLAGS > flags
Status flags (see SdssShapeAlgorithm).
Definition: SdssShape.h:207
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
An ellipse core for the semimajor/semiminor axis and position angle parametrization (a...
Definition: Axes.h:45
virtual SdssShapeResult get(afw::table::BaseRecord const &record) const
Get a CentroidResult from the given record.
Definition: SdssShape.cc:811
virtual void fail(afw::table::SourceRecord &measRecord, MeasurementError *error=NULL) const
Definition: SdssShape.cc:958
Base class for all records.
Definition: BaseRecord.h:27
#define INSTANTIATE(T)
Definition: SdssShape.cc:981
lsst::utils::PowFast const & powFast
Definition: SdssShape.cc:53
A FunctorKey that maps SdssShapeResult to afw::table Records.
Definition: SdssShape.h:66
std::string join(std::string const &a, std::string const &b) const
Join strings using the field delimiter appropriate for this Schema&#39;s version.
bool getAdaptiveMoments(ImageT const &mimage, double bkgd, double xcen, double ycen, double shiftmax, SdssShapeImpl *shape, int maxIter, float tol1, float tol2)
Definition: SdssShape.cc:448
ErrElement yySigma
1-Sigma uncertainty on yy (sqrt of variance)
void set(Key< T > const &key, U const &value)
Set value of a field for the given key.
Definition: BaseRecord.h:136
double ShapeElement
Definition: constants.h:53
CentroidElement y
y (row) coordinate of the measured position
Field< T >::Value get(Key< T > const &key) const
Return the value of a field for the given key.
Definition: BaseRecord.h:123
void handleFailure(afw::table::BaseRecord &record, MeasurementError const *error=NULL) const
Definition: FlagHandler.cc:59
Record class that contains measurements made on a single exposure.
Definition: Source.h:81
ErrElement xxSigma
1-Sigma uncertainty on xx (sqrt of variance)
int getMaxX() const
Definition: Box.h:128
static ShapeResultKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc, UncertaintyEnum uncertainty)
Add the appropriate fields to a Schema, and return a ShapeResultKey that manages them.
afw::table::Key< ErrElement > _flux_xy_Cov
Definition: SdssShape.h:120
SdssShapeResultKey()
Default constructor; instance will not be usuable unless subsequently assigned to.
Definition: SdssShape.h:82
afw::table::Key< ErrElement > _flux_xx_Cov
Definition: SdssShape.h:118
ErrElement flux_xx_Cov
flux, xx term in the uncertainty covariance matrix
Definition: SdssShape.h:202
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:415
static FlagHandler addFields(afw::table::Schema &schema, std::string const &prefix, FlagDefinition const *begin, FlagDefinition const *end)
Definition: FlagHandler.cc:28
A reusable result struct for flux measurements.
Definition: FluxUtilities.h:36
ErrElement flux_xy_Cov
flux, xy term in the uncertainty covariance matrix
Definition: SdssShape.h:204
afw::table::SourceRecord * record