LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
SdssShape.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 /*
3  * LSST Data Management System
4  * Copyright 2008-2016 AURA/LSST.
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 <https://www.lsstcorp.org/LegalNotices/>.
22  */
23 
24 #include <cmath>
25 #include <tuple>
26 
27 #include "boost/tuple/tuple.hpp"
28 #include "Eigen/LU"
29 #include "lsst/afw/image.h"
30 #include "lsst/afw/detection/Psf.h"
31 #include "lsst/afw/geom/Angle.h"
32 #include "lsst/afw/geom/ellipses.h"
33 #include "lsst/afw/table/Source.h"
34 
37 
38 namespace pexPolicy = lsst::pex::policy;
39 namespace pexExceptions = lsst::pex::exceptions;
40 namespace afwDet = lsst::afw::detection;
41 namespace afwImage = lsst::afw::image;
42 namespace afwGeom = lsst::afw::geom;
43 namespace afwTable = lsst::afw::table;
44 
45 namespace lsst {
46 namespace meas {
47 namespace base {
48 
49 namespace { // anonymous
50 
51 typedef Eigen::Matrix<double,4,4,Eigen::DontAlign> Matrix4d;
52 
53 // Return multiplier that transforms I0 to a total flux
54 double computeFluxScale(SdssShapeResult const & result) {
55  /*
56  * The shape is an ellipse that's axis-aligned in (u, v) [<uv> = 0] after rotation by theta:
57  * <x^2> + <y^2> = <u^2> + <v^2>
58  * <x^2> - <y^2> = cos(2 theta)*(<u^2> - <v^2>)
59  * 2*<xy> = sin(2 theta)*(<u^2> - <v^2>)
60  */
61  double const Mxx = result.xx; // <x^2>
62  double const Mxy = result.xy; // <xy>
63  double const Myy = result.yy; // <y^2>
64 
65  double const Muu_p_Mvv = Mxx + Myy; // <u^2> + <v^2>
66  double const Muu_m_Mvv = ::sqrt(::pow(Mxx - Myy, 2) + 4*::pow(Mxy, 2)); // <u^2> - <v^2>
67  double const Muu = 0.5*(Muu_p_Mvv + Muu_m_Mvv);
68  double const Mvv = 0.5*(Muu_p_Mvv - Muu_m_Mvv);
69 
70  return lsst::afw::geom::TWOPI * ::sqrt(Muu*Mvv);
71 }
72 
73 /*****************************************************************************/
74 /*
75  * Error analysis, courtesy of David Johnston, University of Chicago
76  */
77 /*
78  * This function takes the 4 Gaussian parameters A, sigmaXXW and the
79  * sky variance and fills in the Fisher matrix from the least squares fit.
80  *
81  * Following "Numerical Recipes in C" section 15.5, it ignores the 2nd
82  * derivative parts and so the fisher matrix is just a function of these
83  * best fit model parameters. The components are calculated analytically.
84  */
85 Matrix4d
86 calc_fisher(SdssShapeResult const& shape, // the Shape that we want the the Fisher matrix for
87  float bkgd_var // background variance level for object
88 ) {
89  float const A = shape.flux; // amplitude; will be converted to flux later
90  float const sigma11W = shape.xx;
91  float const sigma12W = shape.xy;
92  float const sigma22W = shape.yy;
93 
94  double const D = sigma11W*sigma22W - sigma12W*sigma12W;
95 
96  if (D <= std::numeric_limits<double>::epsilon()) {
97  throw LSST_EXCEPT(lsst::pex::exceptions::DomainError,
98  "Determinant is too small calculating Fisher matrix");
99  }
100 /*
101  * a normalization factor
102  */
103  if (bkgd_var <= 0.0) {
104  throw LSST_EXCEPT(lsst::pex::exceptions::DomainError,
105  (boost::format("Background variance must be positive (saw %g)") % bkgd_var).str());
106  }
107  double const F = afwGeom::PI*sqrt(D)/bkgd_var;
108 /*
109  * Calculate the 10 independent elements of the 4x4 Fisher matrix
110  */
111  Matrix4d fisher;
112 
113  double fac = F*A/(4.0*D);
114  fisher(0, 0) = F;
115  fisher(0, 1) = fac*sigma22W;
116  fisher(1, 0) = fisher(0, 1);
117  fisher(0, 2) = fac*sigma11W;
118  fisher(2, 0) = fisher(0, 2);
119  fisher(0, 3) = -fac*2*sigma12W;
120  fisher(3, 0) = fisher(0, 3);
121 
122  fac = 3.0*F*A*A/(16.0*D*D);
123  fisher(1, 1) = fac*sigma22W*sigma22W;
124  fisher(2, 2) = fac*sigma11W*sigma11W;
125  fisher(3, 3) = fac*4.0*(sigma12W*sigma12W + D/3.0);
126 
127  fisher(1, 2) = fisher(3, 3)/4.0;
128  fisher(2, 1) = fisher(1, 2);
129  fisher(1, 3) = fac*(-2*sigma22W*sigma12W);
130  fisher(3, 1) = fisher(1, 3);
131  fisher(2, 3) = fac*(-2*sigma11W*sigma12W);
132  fisher(3, 2) = fisher(2, 3);
133 
134  return fisher;
135 }
136 //
137 // Here's a class to allow us to get the Image and variance from an Image or MaskedImage
138 //
139 template<typename ImageT> // general case
140 struct ImageAdaptor {
141  typedef ImageT Image;
142 
143  static bool const hasVariance = false;
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  static bool const hasVariance = true;
159 
160  Image const& getImage(afwImage::MaskedImage<T> const& mimage) const {
161  return *mimage.getImage();
162  }
163 
164  double getVariance(afwImage::MaskedImage<T> const& mimage, int ix, int iy) {
165  return mimage.at(ix, iy).variance();
166  }
167 };
168 
170 std::tuple<std::pair<bool, double>, double, double, double>
171 getWeights(double sigma11, double sigma12, double sigma22) {
172  double const NaN = std::numeric_limits<double>::quiet_NaN();
173  if (std::isnan(sigma11) || std::isnan(sigma12) || std::isnan(sigma22)) {
174  return std::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 (std::isnan(det) || det < std::numeric_limits<float>::epsilon()) { // a suitably small number
178  return std::make_tuple(std::make_pair(false, det), NaN, NaN, NaN);
179  }
180  return std::make_tuple(std::make_pair(true, det), sigma22/det, -sigma12/det, sigma11/det);
181 }
182 
184 bool shouldInterp(double sigma11, double sigma22, double det) {
185  float const xinterp = 0.25; // I.e. 0.5*0.5
186  return (sigma11 < xinterp || sigma22 < xinterp || det < xinterp*xinterp);
187 }
188 
189 // Decide on the bounding box for the region to examine while calculating the adaptive moments
190 // This routine will work in either LOCAL or PARENT coordinates (but of course which you pass
191 // determine which you will get back).
192 afw::geom::Box2I computeAdaptiveMomentsBBox(
193  afw::geom::Box2I const & bbox, // full image bbox
194  afw::geom::Point2D const & center, // centre of object
195  double sigma11_w, // quadratic moments of the
196  double , // weighting function
197  double sigma22_w, // xx, xy, and yy
198  double maxRadius = 1000 // Maximum radius of area to use
199 ) {
200  double radius = std::min(4*std::sqrt(std::max(sigma11_w, sigma22_w)), maxRadius);
201  afw::geom::Extent2D offset(radius, radius);
202  afw::geom::Box2I result(afw::geom::Box2D(center - offset, center + offset));
203  result.clip(bbox);
204  return result;
205 }
206 
207 /*****************************************************************************/
208 /*
209  * Calculate weighted moments of an object up to 2nd order
210  */
211 template<bool fluxOnly, typename ImageT>
212 static int
213 calcmom(ImageT const& image, // the image data
214  float xcen, float ycen, // centre of object
215  lsst::afw::geom::BoxI bbox, // bounding box to consider
216  float bkgd, // data's background level
217  bool interpflag, // interpolate within pixels?
218  double w11, double w12, double w22, // weights
219  double *pI0, // amplitude of fit
220  double *psum, // sum w*I (if !NULL)
221  double *psumx, double *psumy, // sum [xy]*w*I (if !fluxOnly)
222  double *psumxx, double *psumxy, double *psumyy, // sum [xy]^2*w*I (if !fluxOnly)
223  double *psums4, // sum w*I*weight^2 (if !fluxOnly && !NULL)
224  bool negative = false
225  )
226 {
227 
228  float tmod, ymod;
229  float X, Y; // sub-pixel interpolated [xy]
230  float weight;
231  float tmp;
232  double sum, sumx, sumy, sumxx, sumyy, sumxy, sums4;
233 #define RECALC_W 0 // estimate sigmaXX_w within BBox?
234 #if RECALC_W
235  double wsum, wsumxx, wsumxy, wsumyy;
236 
237  wsum = wsumxx = wsumxy = wsumyy = 0;
238 #endif
239 
240  assert(w11 >= 0); // i.e. it was set
241  if (fabs(w11) > 1e6 || fabs(w12) > 1e6 || fabs(w22) > 1e6) {
242  return(-1);
243  }
244 
245  sum = sumx = sumy = sumxx = sumxy = sumyy = sums4 = 0;
246 
247  int const ix0 = bbox.getMinX(); // corners of the box being analyzed
248  int const ix1 = bbox.getMaxX();
249  int const iy0 = bbox.getMinY(); // corners of the box being analyzed
250  int const iy1 = bbox.getMaxY();
251 
252  if (ix0 < 0 || ix1 >= image.getWidth() || iy0 < 0 || iy1 >= image.getHeight()) {
253  return -1;
254  }
255 
256  for (int i = iy0; i <= iy1; ++i) {
257  typename ImageT::x_iterator ptr = image.x_at(ix0, i);
258  float const y = i - ycen;
259  float const y2 = y*y;
260  float const yl = y - 0.375;
261  float const yh = y + 0.375;
262  for (int j = ix0; j <= ix1; ++j, ++ptr) {
263  float x = j - xcen;
264  if (interpflag) {
265  float const xl = x - 0.375;
266  float const xh = x + 0.375;
267 
268  float expon = xl*xl*w11 + yl*yl*w22 + 2.0*xl*yl*w12;
269  tmp = xh*xh*w11 + yh*yh*w22 + 2.0*xh*yh*w12;
270  expon = (expon > tmp) ? expon : tmp;
271  tmp = xl*xl*w11 + yh*yh*w22 + 2.0*xl*yh*w12;
272  expon = (expon > tmp) ? expon : tmp;
273  tmp = xh*xh*w11 + yl*yl*w22 + 2.0*xh*yl*w12;
274  expon = (expon > tmp) ? expon : tmp;
275 
276  if (expon <= 9.0) {
277  tmod = *ptr - bkgd;
278  for (Y = yl; Y <= yh; Y += 0.25) {
279  double const interpY2 = Y*Y;
280  for (X = xl; X <= xh; X += 0.25) {
281  double const interpX2 = X*X;
282  double const interpXy = X*Y;
283  expon = interpX2*w11 + 2*interpXy*w12 + interpY2*w22;
284  weight = std::exp(-0.5*expon);
285 
286  ymod = tmod*weight;
287  sum += ymod;
288  if (!fluxOnly) {
289  sumx += ymod*(X + xcen);
290  sumy += ymod*(Y + ycen);
291 #if RECALC_W
292  wsum += weight;
293 
294  tmp = interpX2*weight;
295  wsumxx += tmp;
296  sumxx += tmod*tmp;
297 
298  tmp = interpXy*weight;
299  wsumxy += tmp;
300  sumxy += tmod*tmp;
301 
302  tmp = interpY2*weight;
303  wsumyy += tmp;
304  sumyy += tmod*tmp;
305 #else
306  sumxx += interpX2*ymod;
307  sumxy += interpXy*ymod;
308  sumyy += interpY2*ymod;
309 #endif
310  sums4 += expon*expon*ymod;
311  }
312  }
313  }
314  }
315  } else {
316  float x2 = x*x;
317  float xy = x*y;
318  float expon = x2*w11 + 2*xy*w12 + y2*w22;
319 
320  if (expon <= 14.0) {
321  weight = std::exp(-0.5*expon);
322  tmod = *ptr - bkgd;
323  ymod = tmod*weight;
324  sum += ymod;
325  if (!fluxOnly) {
326  sumx += ymod*j;
327  sumy += ymod*i;
328 #if RECALC_W
329  wsum += weight;
330 
331  tmp = x2*weight;
332  wsumxx += tmp;
333  sumxx += tmod*tmp;
334 
335  tmp = xy*weight;
336  wsumxy += tmp;
337  sumxy += tmod*tmp;
338 
339  tmp = y2*weight;
340  wsumyy += tmp;
341  sumyy += tmod*tmp;
342 #else
343  sumxx += x2*ymod;
344  sumxy += xy*ymod;
345  sumyy += y2*ymod;
346 #endif
347  sums4 += expon*expon*ymod;
348  }
349  }
350  }
351  }
352  }
353 
354 
355  std::tuple<std::pair<bool, double>, double, double, double> const weights = getWeights(w11, w12, w22);
356  double const detW = std::get<1>(weights)*std::get<3>(weights) - std::pow(std::get<2>(weights), 2);
357  *pI0 = sum/(afwGeom::PI*sqrt(detW));
358  if (psum) {
359  *psum = sum;
360  }
361  if (!fluxOnly) {
362  *psumx = sumx;
363  *psumy = sumy;
364  *psumxx = sumxx;
365  *psumxy = sumxy;
366  *psumyy = sumyy;
367  if (psums4 != NULL) {
368  *psums4 = sums4;
369  }
370  }
371 
372 #if RECALC_W
373  if (wsum > 0 && !fluxOnly) {
374  double det = w11*w22 - w12*w12;
375  wsumxx /= wsum;
376  wsumxy /= wsum;
377  wsumyy /= wsum;
378  printf("%g %g %g %g %g %g\n", w22/det, -w12/det, w11/det, wsumxx, wsumxy, wsumyy);
379  }
380 #endif
381 
382  if (negative) {
383  return (fluxOnly || (sum < 0 && sumxx < 0 && sumyy < 0)) ? 0 : -1;
384  } else {
385  return (fluxOnly || (sum > 0 && sumxx > 0 && sumyy > 0)) ? 0 : -1;
386  }
387 }
388 
389 /*
390  * Workhorse for adaptive moments
391  *
392  * All inputs are expected to be in LOCAL image coordinates
393  */
394 template<typename ImageT>
395 bool getAdaptiveMoments(ImageT const& mimage, double bkgd, double xcen, double ycen, double shiftmax,
396  SdssShapeResult *shape, int maxIter, float tol1, float tol2, bool negative)
397 {
398  double I0 = 0; // amplitude of best-fit Gaussian
399  double sum; // sum of intensity*weight
400  double sumx, sumy; // sum ((int)[xy])*intensity*weight
401  double sumxx, sumxy, sumyy; // sum {x^2,xy,y^2}*intensity*weight
402  double sums4; // sum intensity*weight*exponent^2
403  float const xcen0 = xcen; // initial centre
404  float const ycen0 = ycen; // of object
405 
406  double sigma11W = 1.5; // quadratic moments of the
407  double sigma12W = 0.0; // weighting fcn;
408  double sigma22W = 1.5; // xx, xy, and yy
409 
410  double w11 = -1, w12 = -1, w22 = -1; // current weights for moments; always set when iter == 0
411  float e1_old = 1e6, e2_old = 1e6; // old values of shape parameters e1 and e2
412  float sigma11_ow_old = 1e6; // previous version of sigma11_ow
413 
414  typename ImageAdaptor<ImageT>::Image const &image = ImageAdaptor<ImageT>().getImage(mimage);
415 
416  if (std::isnan(xcen) || std::isnan(ycen)) {
417  // Can't do anything
418  shape->flags[SdssShapeAlgorithm::UNWEIGHTED_BAD] = true;
419  return false;
420  }
421 
422  bool interpflag = false; // interpolate finer than a pixel?
424  int iter = 0; // iteration number
425  for (; iter < maxIter; iter++) {
426  bbox = computeAdaptiveMomentsBBox(image.getBBox(afw::image::LOCAL), afw::geom::Point2D(xcen, ycen),
427  sigma11W, sigma12W, sigma22W);
428  std::tuple<std::pair<bool, double>, double, double, double> weights =
429  getWeights(sigma11W, sigma12W, sigma22W);
430  if (!std::get<0>(weights).first) {
431  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
432  break;
433  }
434 
435  double const detW = std::get<0>(weights).second;
436 
437 #if 0 // this form was numerically unstable on my G4 powerbook
438  assert(detW >= 0.0);
439 #else
440  assert(sigma11W*sigma22W >= sigma12W*sigma12W - std::numeric_limits<float>::epsilon());
441 #endif
442 
443  {
444  const double ow11 = w11; // old
445  const double ow12 = w12; // values
446  const double ow22 = w22; // of w11, w12, w22
447 
448  w11 = std::get<1>(weights);
449  w12 = std::get<2>(weights);
450  w22 = std::get<3>(weights);
451 
452  if (shouldInterp(sigma11W, sigma22W, detW)) {
453  if (!interpflag) {
454  interpflag = true; // N.b.: stays set for this object
455  if (iter > 0) {
456  sigma11_ow_old = 1.e6; // force at least one more iteration
457  w11 = ow11;
458  w12 = ow12;
459  w22 = ow22;
460  iter--; // we didn't update wXX
461  }
462  }
463  }
464  }
465 
466  if (calcmom<false>(image, xcen, ycen, bbox, bkgd, interpflag, w11, w12, w22,
467  &I0, &sum, &sumx, &sumy, &sumxx, &sumxy, &sumyy, &sums4, negative) < 0) {
468  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
469  break;
470  }
471 
472 #if 0
473 /*
474  * Find new centre
475  *
476  * This is only needed if we update the centre; if we use the input position we've already done the work
477  */
478  xcen = sumx/sum;
479  ycen = sumy/sum;
480 #endif
481  shape->x = sumx/sum; // update centroid. N.b. we're not setting errors here
482  shape->y = sumy/sum;
483 
484  if (fabs(shape->x - xcen0) > shiftmax || fabs(shape->y - ycen0) > shiftmax) {
485  shape->flags[SdssShapeAlgorithm::SHIFT] = true;
486  }
487 /*
488  * OK, we have the centre. Proceed to find the second moments.
489  */
490  float const sigma11_ow = sumxx/sum; // quadratic moments of
491  float const sigma22_ow = sumyy/sum; // weight*object
492  float const sigma12_ow = sumxy/sum; // xx, xy, and yy
493 
494  if (sigma11_ow <= 0 || sigma22_ow <= 0) {
495  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
496  break;
497  }
498 
499  float const d = sigma11_ow + sigma22_ow; // current values of shape parameters
500  float const e1 = (sigma11_ow - sigma22_ow)/d;
501  float const e2 = 2.0*sigma12_ow/d;
502 /*
503  * Did we converge?
504  */
505  if (iter > 0 &&
506  fabs(e1 - e1_old) < tol1 && fabs(e2 - e2_old) < tol1 &&
507  fabs(sigma11_ow/sigma11_ow_old - 1.0) < tol2 ) {
508  break; // yes; we converged
509  }
510 
511  e1_old = e1;
512  e2_old = e2;
513  sigma11_ow_old = sigma11_ow;
514 /*
515  * Didn't converge, calculate new values for weighting function
516  *
517  * The product of two Gaussians is a Gaussian:
518  * <x^2 exp(-a x^2 - 2bxy - cy^2) exp(-Ax^2 - 2Bxy - Cy^2)> =
519  * <x^2 exp(-(a + A) x^2 - 2(b + B)xy - (c + C)y^2)>
520  * i.e. the inverses of the covariances matrices add.
521  *
522  * We know sigmaXX_ow and sigmaXXW, the covariances of the weighted object
523  * and of the weights themselves. We can estimate the object's covariance as
524  * sigmaXX_ow^-1 - sigmaXXW^-1
525  * and, as we want to find a set of weights with the _same_ covariance as the
526  * object we take this to be the an estimate of our correct weights.
527  *
528  * N.b. This assumes that the object is roughly Gaussian.
529  * Consider the object:
530  * O == delta(x + p) + delta(x - p)
531  * the covariance of the weighted object is equal to that of the unweighted
532  * object, and this prescription fails badly. If we detect this, we set
533  * the UNWEIGHTED flag, and calculate the UNweighted moments
534  * instead.
535  */
536  {
537  float n11, n12, n22; // elements of inverse of next guess at weighting function
538  float ow11, ow12, ow22; // elements of inverse of sigmaXX_ow
539 
540  std::tuple<std::pair<bool, double>, double, double, double> weights =
541  getWeights(sigma11_ow, sigma12_ow, sigma22_ow);
542  if (!std::get<0>(weights).first) {
543  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
544  break;
545  }
546 
547  ow11 = std::get<1>(weights);
548  ow12 = std::get<2>(weights);
549  ow22 = std::get<3>(weights);
550 
551  n11 = ow11 - w11;
552  n12 = ow12 - w12;
553  n22 = ow22 - w22;
554 
555  weights = getWeights(n11, n12, n22);
556  if (!std::get<0>(weights).first) {
557  // product-of-Gaussians assumption failed
558  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
559  break;
560  }
561 
562  sigma11W = std::get<1>(weights);
563  sigma12W = std::get<2>(weights);
564  sigma22W = std::get<3>(weights);
565  }
566 
567  if (sigma11W <= 0 || sigma22W <= 0) {
568  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
569  break;
570  }
571  }
572 
573  if (iter == maxIter) {
574  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
575  shape->flags[SdssShapeAlgorithm::MAXITER] = true;
576  }
577 
578  if (sumxx + sumyy == 0.0) {
579  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = true;
580  }
581 /*
582  * Problems; try calculating the un-weighted moments
583  */
584  if (shape->flags[SdssShapeAlgorithm::UNWEIGHTED]) {
585  w11 = w22 = w12 = 0;
586  if (calcmom<false>(image, xcen, ycen, bbox, bkgd, interpflag, w11, w12, w22,
587  &I0, &sum, &sumx, &sumy, &sumxx, &sumxy, &sumyy, NULL, negative) < 0 ||
588  (!negative && sum <= 0) || (negative && sum >= 0)) {
589  shape->flags[SdssShapeAlgorithm::UNWEIGHTED] = false;
590  shape->flags[SdssShapeAlgorithm::UNWEIGHTED_BAD] = true;
591 
592  if (sum > 0) {
593  shape->xx = 1/12.0; // a single pixel
594  shape->xy = 0.0;
595  shape->yy = 1/12.0;
596  }
597 
598  return false;
599  }
600 
601  sigma11W = sumxx/sum; // estimate of object moments
602  sigma12W = sumxy/sum; // usually, object == weight
603  sigma22W = sumyy/sum; // at this point
604  }
605 
606  shape->flux = I0;
607  shape->xx = sigma11W;
608  shape->xy = sigma12W;
609  shape->yy = sigma22W;
610 
611  if (shape->xx + shape->yy != 0.0) {
612  int const ix = lsst::afw::image::positionToIndex(xcen);
613  int const iy = lsst::afw::image::positionToIndex(ycen);
614 
615  if (ix >= 0 && ix < mimage.getWidth() && iy >= 0 && iy < mimage.getHeight()) {
616  float const bkgd_var =
617  ImageAdaptor<ImageT>().getVariance(mimage, ix, iy); // XXX Overestimate as it includes object
618 
619  if (bkgd_var > 0.0) { // NaN is not > 0.0
620  if (!(shape->flags[SdssShapeAlgorithm::UNWEIGHTED])) {
621  Matrix4d fisher = calc_fisher(*shape, bkgd_var); // Fisher matrix
622  Matrix4d cov = fisher.inverse();
623  // convention in afw::geom::ellipses is to order moments (xx, yy, xy),
624  // but the older algorithmic code uses (xx, xy, yy) - the order of
625  // indices here is not a bug.
626  shape->fluxSigma = std::sqrt(cov(0, 0));
627  shape->xxSigma = std::sqrt(cov(1, 1));
628  shape->xySigma = std::sqrt(cov(2, 2));
629  shape->yySigma = std::sqrt(cov(3, 3));
630  shape->flux_xx_Cov = cov(0, 1);
631  shape->flux_xy_Cov = cov(0, 2);
632  shape->flux_yy_Cov = cov(0, 3);
633  shape->xx_yy_Cov = cov(1, 3);
634  shape->xx_xy_Cov = cov(1, 2);
635  shape->yy_xy_Cov = cov(2, 3);
636  }
637  }
638  }
639  }
640 
641  return true;
642 }
643 
644 } // anonymous
645 
646 
648  flux_xx_Cov(std::numeric_limits<ErrElement>::quiet_NaN()),
649  flux_yy_Cov(std::numeric_limits<ErrElement>::quiet_NaN()),
650  flux_xy_Cov(std::numeric_limits<ErrElement>::quiet_NaN())
651 {}
652 
653 static std::array<FlagDefinition,SdssShapeAlgorithm::N_FLAGS> const flagDefs = {{
654  {"flag", "general failure flag, set if anything went wrong"},
655  {"flag_unweightedBad", "Both weighted and unweighted moments were invalid"},
656  {"flag_unweighted", "Weighted moments converged to an invalid value; using unweighted moments"},
657  {"flag_shift", "centroid shifted by more than the maximum allowed amount"},
658  {"flag_maxIter", "Too many iterations in adaptive moments"},
659  {"flag_psf", "Failure in measuring PSF model shape"}
660  }};
661 
664  std::string const & name,
665  bool doMeasurePsf
666 ) {
667 
669  r._shapeResult = ShapeResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments",
670  SIGMA_ONLY);
671  r._centroidResult = CentroidResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments",
673  r._fluxResult = FluxResultKey::addFields(schema, name, "elliptical Gaussian adaptive moments");
674 
675  // Only include PSF shape fields if doMeasurePsf = True
676  if (doMeasurePsf) {
677  r._includePsf = true;
679  schema, schema.join(name, "psf"), "adaptive moments of the PSF model at the object position");
680  } else {
681  r._includePsf = false;
682  }
683 
684  r._flux_xx_Cov = schema.addField<ErrElement>(
685  schema.join(name, "flux", "xx", "Cov"),
686  (boost::format("uncertainty covariance between %s and %s")
687  % schema.join(name, "flux") % schema.join(name, "xx")).str(),
688  "count*pixel^2"
689  );
690  r._flux_yy_Cov = schema.addField<ErrElement>(
691  schema.join(name, "flux", "yy", "Cov"),
692  (boost::format("uncertainty covariance between %s and %s")
693  % schema.join(name, "flux") % schema.join(name, "yy")).str(),
694  "count*pixel^2"
695  );
696  r._flux_xy_Cov = schema.addField<ErrElement>(
697  schema.join(name, "flux", "xy", "Cov"),
698  (boost::format("uncertainty covariance between %s and %s")
699  % schema.join(name, "flux") % schema.join(name, "xy")).str(),
700  "count*pixel^2"
701  );
702 
703  // Skip the last flag if not recording the PSF shape.
704  r._flagHandler = FlagHandler::addFields(schema, name, flagDefs.begin(),
705  flagDefs.end() - (r._includePsf ? 0 : 1));
706 
707  return r;
708 }
709 
711  _shapeResult(s),
712  _centroidResult(s),
713  _fluxResult(s),
714  _flux_xx_Cov(s["flux"]["xx"]["Cov"]),
715  _flux_yy_Cov(s["flux"]["yy"]["Cov"]),
716  _flux_xy_Cov(s["flux"]["xy"]["Cov"])
717 {
718  // The input SubSchema may optionally provide for a PSF.
719  try {
721  _flagHandler = FlagHandler(s, flagDefs.begin(), flagDefs.end());
722  _includePsf = true;
723  } catch (pex::exceptions::NotFoundError& e) {
724  _flagHandler = FlagHandler(s, flagDefs.begin(), flagDefs.end() - 1);
725  _includePsf = false;
726  }
727 }
728 
730  SdssShapeResult result;
731  static_cast<ShapeResult&>(result) = record.get(_shapeResult);
732  static_cast<CentroidResult&>(result) = record.get(_centroidResult);
733  static_cast<FluxResult&>(result) = record.get(_fluxResult);
734  result.flux_xx_Cov = record.get(_flux_xx_Cov);
735  result.flux_yy_Cov = record.get(_flux_yy_Cov);
736  result.flux_xy_Cov = record.get(_flux_xy_Cov);
737  for (int n = 0; n < SdssShapeAlgorithm::N_FLAGS - (_includePsf ? 0 : 1); ++n) {
738  result.flags[n] = _flagHandler.getValue(record, n);
739  }
740  return result;
741 }
742 
743 afwGeom::ellipses::Quadrupole SdssShapeResultKey::getPsfShape(afw::table::BaseRecord const & record) const {
744  return record.get(_psfShapeResult);
745 }
746 
748  record.set(_shapeResult, value);
749  record.set(_centroidResult, value);
750  record.set(_fluxResult, value);
751  record.set(_flux_xx_Cov, value.flux_xx_Cov);
752  record.set(_flux_yy_Cov, value.flux_yy_Cov);
753  record.set(_flux_xy_Cov, value.flux_xy_Cov);
754  for (int n = 0; n < SdssShapeAlgorithm::N_FLAGS - (_includePsf ? 0 : 1); ++n) {
755  _flagHandler.setValue(record, n, value.flags[n]);
756  }
757 }
758 
760  afwGeom::ellipses::Quadrupole const & value) const {
761  record.set(_psfShapeResult, value);
762 }
763 
765  return _shapeResult == other._shapeResult &&
766  _centroidResult == other._centroidResult &&
767  _fluxResult == other._fluxResult &&
768  _psfShapeResult == other._psfShapeResult &&
769  _flux_xx_Cov == other._flux_xx_Cov &&
770  _flux_yy_Cov == other._flux_yy_Cov &&
771  _flux_xy_Cov == other._flux_xy_Cov;
772  // don't bother with flags - if we've gotten this far, it's basically impossible the flags don't match
773 }
774 
776  return _shapeResult.isValid() &&
778  _fluxResult.isValid() &&
780  _flux_xx_Cov.isValid() &&
781  _flux_yy_Cov.isValid() &&
783  // don't bother with flags - if we've gotten this far, it's basically impossible the flags are invalid
784 }
785 
787  Control const & ctrl,
788  std::string const & name,
790 )
791  : _ctrl(ctrl),
792  _resultKey(ResultKey::addFields(schema, name, ctrl.doMeasurePsf)),
793  _centroidExtractor(schema, name)
794 {}
795 
796 template <typename ImageT>
798  ImageT const & image,
799  afw::geom::Point2D const & center,
800  bool negative,
801  Control const & control
802 ) {
803  double xcen = center.getX(); // object's column position
804  double ycen = center.getY(); // object's row position
805 
806  xcen -= image.getX0(); // work in image Pixel coordinates
807  ycen -= image.getY0();
808 
809  float shiftmax = control.maxShift; // Max allowed centroid shift
810  if (shiftmax < 2) {
811  shiftmax = 2;
812  } else if (shiftmax > 10) {
813  shiftmax = 10;
814  }
815 
816  SdssShapeResult result;
817  try {
818  result.flags[FAILURE] = !getAdaptiveMoments(
819  image, control.background, xcen, ycen, shiftmax, &result,
820  control.maxIter, control.tol1, control.tol2, negative
821  );
822  } catch (pex::exceptions::Exception & err) {
823  result.flags[FAILURE] = true;
824  }
825  if (result.flags[UNWEIGHTED] || result.flags[SHIFT]) {
826  // These are also considered fatal errors in terms of the quality of the results,
827  // even though they do produce some results.
828  result.flags[FAILURE] = true;
829  }
830  if (result.getQuadrupole().getIxx()*result.getQuadrupole().getIyy() <
831  (1.0 + 1.0e-6)*result.getQuadrupole().getIxy()*result.getQuadrupole().getIxy())
832  // We are checking that Ixx*Iyy > (1 + epsilon)*Ixy*Ixy where epsilon is suitably small. The
833  // value of epsilon used here is a magic number. DM-5801 is supposed to figure out if we are
834  // to keep this value.
835  {
836  if (!result.flags[FAILURE]) {
837  throw LSST_EXCEPT(
838  pex::exceptions::LogicError,
839  "Should not get singular moments unless a flag is set");
840  }
841  }
842 
843  // getAdaptiveMoments() just computes the zeroth moment in result.flux (and its error in
844  // result.fluxSigma, result.flux_xx_Cov, etc.) That's related to the flux by some geometric
845  // factors, which we apply here.
846  double fluxScale = computeFluxScale(result);
847 
848  result.flux *= fluxScale;
849  result.fluxSigma *= fluxScale;
850  result.x += image.getX0();
851  result.y += image.getY0();
852 
853  if (ImageAdaptor<ImageT>::hasVariance) {
854  result.flux_xx_Cov *= fluxScale;
855  result.flux_yy_Cov *= fluxScale;
856  result.flux_xy_Cov *= fluxScale;
857  }
858 
859  return result;
860 }
861 
862 template <typename ImageT>
864  ImageT const & image,
865  afw::geom::ellipses::Quadrupole const & shape,
866  afw::geom::Point2D const & center
867 ) {
868  // while arguments to computeFixedMomentsFlux are in PARENT coordinates, the implementation is LOCAL.
869  afw::geom::Point2D localCenter = center - afw::geom::Extent2D(image.getXY0());
870 
871  afwGeom::BoxI const bbox = computeAdaptiveMomentsBBox(image.getBBox(afw::image::LOCAL),
872  localCenter,
873  shape.getIxx(), shape.getIxy(), shape.getIyy());
874 
875  std::tuple<std::pair<bool, double>, double, double, double> weights =
876  getWeights(shape.getIxx(), shape.getIxy(), shape.getIyy());
877 
878  FluxResult result;
879 
880  if (!std::get<0>(weights).first) {
881  throw pex::exceptions::InvalidParameterError("Input shape is singular");
882  }
883 
884  double const w11 = std::get<1>(weights);
885  double const w12 = std::get<2>(weights);
886  double const w22 = std::get<3>(weights);
887  bool const interp = shouldInterp(shape.getIxx(), shape.getIyy(), std::get<0>(weights).second);
888 
889  double i0 = 0; // amplitude of Gaussian
890  if (calcmom<true>(ImageAdaptor<ImageT>().getImage(image), localCenter.getX(), localCenter.getY(),
891  bbox, 0.0, interp, w11, w12, w22, &i0, NULL, NULL, NULL, NULL, NULL, NULL, NULL)< 0) {
892  throw LSST_EXCEPT(pex::exceptions::RuntimeError, "Error from calcmom");
893  }
894 
895  double const wArea = afw::geom::PI*std::sqrt(shape.getDeterminant());
896 
897  result.flux = i0*2*wArea;
898 
899  if (ImageAdaptor<ImageT>::hasVariance) {
900  int ix = static_cast<int>(center.getX() - image.getX0());
901  int iy = static_cast<int>(center.getY() - image.getY0());
902  if (!image.getBBox(afw::image::LOCAL).contains(afw::geom::Point2I(ix, iy))) {
903  throw LSST_EXCEPT(pex::exceptions::RuntimeError,
904  (boost::format("Center (%d,%d) not in image (%dx%d)") %
905  ix % iy % image.getWidth() % image.getHeight()).str());
906  }
907  double var = ImageAdaptor<ImageT>().getVariance(image, ix, iy);
908  double i0Err = std::sqrt(var/wArea);
909  result.fluxSigma = i0Err*2*wArea;
910  }
911 
912  return result;
913 }
914 
916  afw::table::SourceRecord & measRecord,
917  afw::image::Exposure<float> const & exposure
918 ) const {
919  bool negative = false;
920 
921  try {
922  negative = measRecord.get(measRecord.getSchema().find<afw::table::Flag>("flags_negative").key);
923  } catch(pexExcept::Exception &e) {
924  }
926  exposure.getMaskedImage(),
928  negative,
929  _ctrl
930  );
931 
932  if (_ctrl.doMeasurePsf) {
933  // Compute moments of Psf model. In the interest of implementing this quickly, we're just
934  // calling Psf::computeShape(), which delegates to SdssShapeResult::computeAdaptiveMoments
935  // for all nontrivial Psf classes. But this could in theory save the results of a shape
936  // computed some other way as part of base_SdssShape, which might be confusing. We should
937  // fix this eventually either by making Psf shape measurement not part of base_SdssShape, or
938  // by making the measurements stored with shape.sdss always computed via the
939  // SdssShapeAlgorithm instead of delegating to the Psf class.
940  try {
941  PTR(afw::detection::Psf const) psf = exposure.getPsf();
942  if (!psf) {
943  result.flags[PSF_SHAPE_BAD] = true;
944  } else {
945  _resultKey.setPsfShape(measRecord, psf->computeShape());
946  }
947  } catch (pex::exceptions::Exception & err) {
948  result.flags[PSF_SHAPE_BAD] = true;
949  }
950  }
951 
952  measRecord.set(_resultKey, result);
953 }
954 
956  afw::table::SourceRecord & measRecord,
958 ) const {
959  _resultKey.getFlagHandler().handleFailure(measRecord, error);
960 }
961 
962 #define INSTANTIATE_IMAGE(IMAGE) \
963  template SdssShapeResult SdssShapeAlgorithm::computeAdaptiveMoments( \
964  IMAGE const &, \
965  afw::geom::Point2D const &, \
966  bool, \
967  Control const & \
968  ); \
969  template FluxResult SdssShapeAlgorithm::computeFixedMomentsFlux( \
970  IMAGE const &, \
971  afw::geom::ellipses::Quadrupole const &, \
972  afw::geom::Point2D const & \
973  )
974 
975 #define INSTANTIATE_PIXEL(PIXEL) \
976  INSTANTIATE_IMAGE(lsst::afw::image::Image<PIXEL>); \
977  INSTANTIATE_IMAGE(lsst::afw::image::MaskedImage<PIXEL>);
978 
979 INSTANTIATE_PIXEL(int);
980 INSTANTIATE_PIXEL(float);
981 INSTANTIATE_PIXEL(double);
982 
984  Control const & ctrl,
985  std::string const & name,
986  afw::table::SchemaMapper & mapper
987 ) :
988  BaseTransform{name},
989  _fluxTransform{name, mapper},
990  _centroidTransform{name, mapper}
991 {
992  // If the input schema has a PSF flag -- it's optional -- assume we are also transforming the PSF.
993  _transformPsf = mapper.getInputSchema().getNames().count("sdssShape_flag_psf") ? true : false;
994 
995  // Skip the last flag if not transforming the PSF shape.
996  for (auto flag = flagDefs.begin() + 1; flag < flagDefs.end() - (_transformPsf ? 0 : 1); flag++) {
997  mapper.addMapping(mapper.getInputSchema().find<afw::table::Flag>(
998  mapper.getInputSchema().join(name, flag->name)).key);
999  }
1000 
1001  _outShapeKey = ShapeResultKey::addFields(mapper.editOutputSchema(), name, "Shape in celestial moments",
1003  if (_transformPsf) {
1004  _outPsfShapeKey = afwTable::QuadrupoleKey::addFields(mapper.editOutputSchema(), name + "_psf",
1005  "PSF shape in celestial moments",
1007  }
1008 }
1009 
1011  afw::table::SourceCatalog const & inputCatalog,
1012  afw::table::BaseCatalog & outputCatalog,
1013  afw::image::Wcs const & wcs,
1014  afw::image::Calib const & calib
1015 ) const {
1016  // The flux and cetroid transforms will check that the catalog lengths
1017  // match and throw if not, so we don't repeat the test here.
1018  _fluxTransform(inputCatalog, outputCatalog, wcs, calib);
1019  _centroidTransform(inputCatalog, outputCatalog, wcs, calib);
1020 
1021  CentroidResultKey centroidKey(inputCatalog.getSchema()[_name]);
1022  ShapeResultKey inShapeKey(inputCatalog.getSchema()[_name]);
1023  afwTable::QuadrupoleKey inPsfShapeKey;
1024  if (_transformPsf) {
1025  inPsfShapeKey = afwTable::QuadrupoleKey(
1026  inputCatalog.getSchema()[inputCatalog.getSchema().join(_name, "psf")]);
1027  }
1028 
1029  afw::table::SourceCatalog::const_iterator inSrc = inputCatalog.begin();
1030  afw::table::BaseCatalog::iterator outSrc = outputCatalog.begin();
1031  for (; inSrc != inputCatalog.end(); ++inSrc, ++outSrc) {
1032  ShapeResult inShape = inShapeKey.get(*inSrc);
1033  ShapeResult outShape;
1034 
1035  // The transformation from the (x, y) to the (Ra, Dec) basis.
1036  afw::geom::AffineTransform crdTr = wcs.linearizePixelToSky(centroidKey.get(*inSrc).getCentroid(),
1038  outShape.setShape(inShape.getShape().transform(crdTr.getLinear()));
1039 
1040  // Transformation matrix from pixel to celestial basis.
1042  outShape.setShapeErr((m*inShape.getShapeErr().cast<double>()*m.transpose()).cast<ErrElement>());
1043 
1044  _outShapeKey.set(*outSrc, outShape);
1045 
1046  if (_transformPsf) {
1047  _outPsfShapeKey.set(*outSrc, inPsfShapeKey.get(*inSrc).transform(crdTr.getLinear()));
1048  }
1049  }
1050 }
1051 
1052 }}} // end namespace lsst::meas::base
int y
bool isValid() const
Return true if the key was initialized to valid offset.
Definition: Key.h:83
int iter
An ellipse core with quadrupole moments as parameters.
Definition: Quadrupole.h:45
Defines the fields and offsets for a table.
Definition: Schema.h:44
Eigen::Matrix< ShapeElement, 3, 3, Eigen::DontAlign > ShapeTrMatrix
Definition: constants.h:60
bool getValue(afw::table::BaseRecord const &record, std::size_t i) const
Definition: FlagHandler.h:180
afw::table::QuadrupoleKey _psfShapeResult
Definition: SdssShape.h:134
int getMaxY() const
Definition: Box.h:129
CentroidElement y
y (row) coordinate of the measured position
A proxy type for name lookups in a Schema.
Definition: Schema.h:326
ShapeTrMatrix makeShapeTransformMatrix(afw::geom::LinearTransform const &xform)
Construct a matrix suitable for transforming second moments.
table::Key< std::string > name
Definition: ApCorrMap.cc:71
Public header class for ellipse library.
geom::AffineTransform linearizePixelToSky(coord::Coord const &coord, geom::AngleUnit skyUnit=geom::degrees) const
Return the local linear approximation to Wcs::pixelToSky at a point given in sky coordinates.
Definition: Wcs.cc:929
tbl::Key< double > weight
static ShapeResultKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc, UncertaintyEnum uncertainty, afw::table::CoordinateType coordType=afw::table::CoordinateType::PIXEL)
Add the appropriate fields to a Schema, and return a ShapeResultKey that manages them.
int positionToIndex(double pos)
Convert image position to nearest integer index.
Definition: ImageUtils.h:69
afw::table::QuadrupoleKey _outPsfShapeKey
Definition: SdssShape.h:284
CentroidResultKey _centroidResult
Definition: SdssShape.h:132
virtual void fail(afw::table::SourceRecord &measRecord, MeasurementError *error=NULL) const
Definition: SdssShape.cc:955
A custom container class for records, based on std::vector.
Definition: Catalog.h:95
afw::table::Schema schema
Definition: GaussianPsf.cc:41
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
bool operator==(SdssShapeResultKey const &other) const
Compare the FunctorKey for equality with another, using the underlying Keys.
Definition: SdssShape.cc:764
A reusable struct for centroid measurements.
float tol2
&quot;Convergence tolerance for FWHM&quot; ;
Definition: SdssShape.h:56
iterator at(int const x, int const y) const
Return an iterator at the point (x, y)
Definition: MaskedImage.cc:727
bool isValid() const
Return True if all the constituent Keys are valid.
Definition: aggregates.h:244
SafeCentroidExtractor _centroidExtractor
Definition: SdssShape.h:222
Point< double, 2 > Point2D
Definition: Point.h:288
tbl::Key< int > wcs
double const getIxx() const
Definition: Quadrupole.h:56
Flux flux
Measured flux in DN.
Definition: FluxUtilities.h:38
ImagePtr getImage(bool const noThrow=false) const
Return a (Ptr to) the MaskedImage&#39;s image.
Definition: MaskedImage.h:875
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
ErrElement flux_xy_Cov
flux, xy term in the uncertainty covariance matrix
Definition: SdssShape.h:241
static FluxResult computeFixedMomentsFlux(ImageT const &image, afw::geom::ellipses::Quadrupole const &shape, afw::geom::Point2D const &position)
Definition: SdssShape.cc:863
virtual void set(afw::table::BaseRecord &record, SdssShapeResult const &value) const
Set an SdssShapeResult in the given record.
Definition: SdssShape.cc:747
static Result computeAdaptiveMoments(ImageT const &image, afw::geom::Point2D const &position, bool negative=false, Control const &ctrl=Control())
Algorithm provides no uncertainy information at all.
Definition: constants.h:42
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
afw::geom::ellipses::Quadrupole getQuadrupole()
bool isValid() const
Return True if the shape key is valid.
void handleFailure(afw::table::BaseRecord &record, MeasurementError const *error=NULL) const
Definition: FlagHandler.cc:77
double const getIyy() const
Definition: Quadrupole.h:59
An integer coordinate rectangle.
Definition: Box.h:53
FluxErrElement fluxSigma
1-Sigma error (sqrt of variance) on flux in DN.
Definition: FluxUtilities.h:39
virtual void set(afw::table::BaseRecord &record, ShapeResult const &value) const
Set a ShapeResult in the given record.
A FunctorKey for ShapeResult.
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
double background
&quot;Additional value to add to background&quot; ;
Definition: SdssShape.h:52
afw::table::Key< ErrElement > _flux_yy_Cov
Definition: SdssShape.h:136
Shape const getShape() const
Return an afw::geom::ellipses object corresponding to xx, yy, xy.
def error
Definition: log.py:103
An include file to include the header files for lsst::afw::image.
A reusable struct for moments-based shape measurements.
float ErrElement
Definition: constants.h:53
FlagHandler const & getFlagHandler() const
Definition: SdssShape.h:127
double const getIxy() const
Definition: Quadrupole.h:62
void setShapeErr(ShapeCov const &matrix)
Set the struct uncertainty elements from the given matrix, with rows and columns ordered (xx...
double const PI
The ratio of a circle&#39;s circumference to diameter.
Definition: Angle.h:17
MaskedImageT getMaskedImage()
Return the MaskedImage.
Definition: Exposure.h:148
Result object SdssShapeAlgorithm.
Definition: SdssShape.h:237
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
virtual SdssShapeResult get(afw::table::BaseRecord const &record) const
Get an SdssShapeResult from the given record.
Definition: SdssShape.cc:729
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
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...
virtual afw::geom::ellipses::Quadrupole getPsfShape(afw::table::BaseRecord const &record) const
Get a Quadrupole for the Psf from the given record.
Definition: SdssShape.cc:743
An affine coordinate transformation consisting of a linear transformation and an offset.
bool isValid() const
Return True if both the flux and fluxSigma Keys are valid.
The full covariance matrix is provided.
Definition: constants.h:44
int getMinY() const
Definition: Box.h:125
AngleUnit const radians
constant with units of radians
Definition: Angle.h:90
Iterator class for CatalogT.
Definition: Catalog.h:35
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:78
SdssShapeResultKey()
Default constructor; instance will not be usuable unless subsequently assigned to.
Definition: SdssShape.h:92
double maxShift
&quot;Maximum centroid shift, limited to 2-10&quot; ;
Definition: SdssShape.h:54
int getMinX() const
Definition: Box.h:124
afw::table::Key< ErrElement > _flux_xy_Cov
Definition: SdssShape.h:137
A C++ control class to handle SdssShapeAlgorithm&#39;s configuration.
Definition: SdssShape.h:50
double x
Transformer transform(LinearTransform const &transform)
Definition: Transformer.h:117
virtual void set(BaseRecord &record, geom::ellipses::Quadrupole const &value) const
Set a Quadrupole in the given record.
CentroidTransform _centroidTransform
Definition: SdssShape.h:282
double const TWOPI
Definition: Angle.h:18
static FluxResultKey addFields(afw::table::Schema &schema, std::string const &name, std::string const &doc)
void setShape(Shape const &shape)
Set struct elements from the given Quadrupole object.
boost::shared_ptr< lsst::afw::detection::Psf > getPsf()
Return the Exposure&#39;s Psf object.
Definition: Exposure.h:222
bool doMeasurePsf
&quot;Whether to also compute the shape of the PSF model&quot; ;
Definition: SdssShape.h:57
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
Base class for all records.
Definition: BaseRecord.h:27
tuple m
Definition: lsstimport.py:48
SdssShapeAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema)
Definition: SdssShape.cc:786
static FlagHandler addFields(afw::table::Schema &schema, std::string const &prefix, FlagDefinition const *begin, FlagDefinition const *end)
Definition: FlagHandler.cc:28
virtual void operator()(afw::table::SourceCatalog const &inputCatalog, afw::table::BaseCatalog &outputCatalog, afw::image::Wcs const &wcs, afw::image::Calib const &calib) const
Definition: SdssShape.cc:1010
A FunctorKey that maps SdssShapeResult to afw::table Records.
Definition: SdssShape.h:71
std::string join(std::string const &a, std::string const &b) const
Join strings using the field delimiter appropriate for this Schema.
#define PTR(...)
Definition: base.h:41
CentroidElement x
x (column) coordinate of the measured position
void setValue(afw::table::BaseRecord &record, std::size_t i, bool value) const
Definition: FlagHandler.h:188
A FunctorKey for CentroidResult.
std::bitset< SdssShapeAlgorithm::N_FLAGS > flags
Status flags (see SdssShapeAlgorithm).
Definition: SdssShape.h:244
void set(Key< T > const &key, U const &value)
Set value of a field for the given key.
Definition: BaseRecord.h:145
Field< T >::Value get(Key< T > const &key) const
Return the value of a field for the given key.
Definition: BaseRecord.h:132
Record class that contains measurements made on a single exposure.
Definition: Source.h:80
LinearTransform const & getLinear() const
Extent< double, 2 > Extent2D
Definition: Extent.h:361
SchemaItem< T > find(std::string const &name) const
Find a SchemaItem in the Schema by name.
int getMaxX() const
Definition: Box.h:128
#define INSTANTIATE_PIXEL(PIXEL)
Definition: SdssShape.cc:975
ShapeCov const getShapeErr() const
Return the 3x3 symmetric covariance matrix, with rows and columns ordered (xx, yy, xy)
SdssShapeTransform(Control const &ctrl, std::string const &name, afw::table::SchemaMapper &mapper)
Definition: SdssShape.cc:983
A polymorphic base class for representing an image&#39;s Point Spread Function.
Definition: Psf.h:68
virtual void setPsfShape(afw::table::BaseRecord &record, afw::geom::ellipses::Quadrupole const &value) const
Set a Quadrupole for the Psf at the position of the given record.
Definition: SdssShape.cc:759
ErrElement flux_xx_Cov
flux, xx term in the uncertainty covariance matrix
Definition: SdssShape.h:239
double getDeterminant() const
Return the determinant of the matrix representation.
Definition: Quadrupole.h:85
Only the diagonal elements of the covariance matrix are provided.
Definition: constants.h:43
float tol1
&quot;Convergence tolerance for e1,e2&quot; ;
Definition: SdssShape.h:55
static SdssShapeResultKey addFields(afw::table::Schema &schema, std::string const &name, bool doMeasurePsf)
Add the appropriate fields to a Schema, and return a SdssShapeResultKey that manages them...
Definition: SdssShape.cc:662
A FunctorKey used to get or set a geom::ellipses::Quadrupole from a tuple of constituent Keys...
Definition: aggregates.h:188
bool isValid() const
Return True if the key is valid.
Definition: SdssShape.cc:775
Schema getSchema() const
Return the schema associated with the catalog&#39;s table.
Definition: Catalog.h:115
A reusable result struct for flux measurements.
Definition: FluxUtilities.h:37
virtual void measure(afw::table::SourceRecord &measRecord, afw::image::Exposure< float > const &exposure) const
Definition: SdssShape.cc:915
Schema getSchema() const
Return the Schema that holds this record&#39;s fields and keys.
Definition: BaseRecord.h:57
SdssShapeResult()
Constructor; initializes everything to NaN.
Definition: SdssShape.cc:647
int maxIter
&quot;Maximum number of iterations&quot; ;
Definition: SdssShape.h:53
bool isValid() const
Return True if the centroid key is valid.
afw::table::Key< ErrElement > _flux_xx_Cov
Definition: SdssShape.h:135
ErrElement flux_yy_Cov
flux, yy term in the uncertainty covariance matrix
Definition: SdssShape.h:240