lsst.meas.base  14.0-21-gb9e430a+9
Blendedness.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 /*
3  * LSST Data Management System
4  * Copyright 2016 LSST/AURA
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 <cmath>
25 
26 #include "boost/math/constants/constants.hpp"
27 
34 
35 namespace lsst { namespace meas { namespace base {
36 namespace {
37 FlagDefinitionList flagDefinitions;
38 } // end anonymous
39 
40 FlagDefinition const BlendednessAlgorithm::FAILURE = flagDefinitions.addFailureFlag();
41 FlagDefinition const BlendednessAlgorithm::NO_CENTROID = flagDefinitions.add("flag_noCentroid", "Object has no centroid");
42 FlagDefinition const BlendednessAlgorithm::NO_SHAPE = flagDefinitions.add("flag_noShape", "Object has no shape");
43 
45  return flagDefinitions;
46 }
47 
48 
49 
50 namespace {
51 
52 double computeOldBlendedness(
53  PTR(afw::detection::Footprint const) childFootprint,
54  afw::image::Image<float> const & parentImage
55 ) {
56  if (!childFootprint) {
57  throw LSST_EXCEPT(
59  "blendedness_old requires a Footprint."
60  );
61  }
62 
63  PTR(afw::detection::HeavyFootprint<float> const) childHeavy =
65 
66  if (!childHeavy) {
67  return 0.0; // if it's not a HeavyFootprint, it's not blended.
68  }
69 
70  if (!parentImage.getBBox(afw::image::PARENT).contains(childHeavy->getBBox())) {
71  throw LSST_EXCEPT(
73  "Child footprint extends beyond image."
74  );
75  }
76 
77  // Iterate over all the spans in the child HeavyFootprint,
78  // along with iterators for the child pixels (from the HeavyFootprint)
79  // and parent pixels (from the Exposure).
80  typedef afw::image::Image<float>::const_x_iterator ParentPixIter;
81  typedef ndarray::Array<float const,1,1>::Iterator ChildPixIter;
82  auto spanIter = childHeavy->getSpans()->begin();
83  auto const spanEnd = childHeavy->getSpans()->end();
84  ChildPixIter childPixIter = childHeavy->getImageArray().begin();
85  double cp = 0.0; // child.dot(parent)
86  double cc = 0.0; // child.dot(child)
87  while (spanIter != spanEnd) {
88  afw::geom::Span const & span = *spanIter;
89  ParentPixIter parentPixIter = parentImage.x_at(
90  span.getBeginX() - parentImage.getX0(),
91  span.getY() - parentImage.getY0()
92  );
93  int const width = span.getWidth();
94  // Iterate over the pixels within the span, updating the dot products.
95  for (int x = 0; x < width; ++parentPixIter, ++childPixIter, ++x) {
96  cp += (*childPixIter) * ((*parentPixIter) - (*childPixIter));
97  cc += (*childPixIter) * (*childPixIter);
98  }
99  ++spanIter;
100  }
101  if (cc > 0.0) {
102  return cp/cc;
103  }
104  return 0.0;
105 }
106 
107 class FluxAccumulator {
108 public:
109 
110  FluxAccumulator() : _w(0.0), _ww(0.0), _wd(0.0) {}
111 
112  void operator()(double, double, float weight, float data) {
113  _w += weight;
114  _ww += weight*weight;
115  _wd += weight*data;
116  }
117 
118  double getFlux() const { return _w*_wd/_ww; }
119 
120 protected:
121  double _w;
122  double _ww;
123  double _wd;
124 };
125 
126 
127 class ShapeAccumulator : public FluxAccumulator {
128 public:
129 
130  ShapeAccumulator() : FluxAccumulator(), _wdxx(0.0), _wdyy(0.0), _wdxy(0.0) {}
131 
132  void operator()(double x, double y, float weight, float data) {
133  FluxAccumulator::operator()(x, y, weight, data);
134  _wdxx += x*x*weight*data;
135  _wdyy += y*y*weight*data;
136  _wdxy += x*y*weight*data;
137  }
138 
139  ShapeResult getShape() const {
140  // Factor of 2 corrects for bias from weight function (correct is exact for an object
141  // with a Gaussian profile.)
142  ShapeResult result;
143  result.xx = 2.0*_wdxx/_wd;
144  result.yy = 2.0*_wdyy/_wd;
145  result.xy = 2.0*_wdxy/_wd;
146  return result;
147  }
148 
149 private:
150  double _wdxx;
151  double _wdyy;
152  double _wdxy;
153 };
154 
155 template <typename Accumulator>
156 void computeMoments(
157  afw::image::MaskedImage<float> const & image,
158  afw::geom::Point2D const & centroid,
159  afw::geom::ellipses::Quadrupole const & shape,
160  double nSigmaWeightMax,
161  Accumulator & accumulatorRaw,
162  Accumulator & accumulatorAbs
163 ) {
164  afw::geom::Box2I bbox = image.getBBox(lsst::afw::image::PARENT);
165 
166  afw::geom::ellipses::Ellipse ellipse(shape, centroid);
167  ellipse.getCore().scale(nSigmaWeightMax);
168 
169  // To evaluate an elliptically-symmetric function, we transform points
170  // by the following transform, then evaluate a circularly-symmetric function
171  // at the transformed positions.
172  afw::geom::LinearTransform transform = shape.getGridTransform();
173 
174  typedef afw::geom::ellipses::PixelRegion::Iterator SpanIter; // yields Spans
175  typedef afw::geom::Span::Iterator PointIter; // yields Point2I positions
176  typedef afw::image::MaskedImage<float>::const_x_iterator PixelIter; // yields pixel values
177 
178  afw::geom::ellipses::PixelRegion region(ellipse);
179  bool isContained = bbox.contains(region.getBBox());
180  SpanIter const spanEnd = region.end();
181  for (SpanIter spanIter = region.begin(); spanIter != spanEnd; ++spanIter) {
182  afw::geom::Span span = *spanIter;
183  if (!isContained) {
184  if (span.getY() < bbox.getMinY() || span.getY() > bbox.getMaxY()) {
185  continue;
186  }
187  span = afw::geom::Span(
188  span.getY(),
189  std::max(span.getMinX(), bbox.getMinX()),
190  std::min(span.getMaxX(), bbox.getMaxX())
191  );
192  if (span.getMinX() > span.getMaxX()) {
193  continue;
194  }
195  }
196  PixelIter pixelIter = image.x_at(
197  span.getBeginX() - image.getX0(),
198  span.getY() - image.getY0()
199  );
200  PointIter const pointEnd = span.end();
201  for (PointIter pointIter = span.begin(); pointIter != pointEnd; ++pointIter, ++pixelIter) {
202  afw::geom::Extent2D d = afw::geom::Point2D(*pointIter) - centroid;
203  afw::geom::Extent2D td = transform(d);
204  // use single precision for faster exp, erf
205  float weight = std::exp(static_cast<float>(-0.5*td.computeSquaredNorm()));
206  float data = pixelIter.image();
207  accumulatorRaw(d.getX(), d.getY(), weight, data);
208  float variance = pixelIter.variance();
209  float mu = BlendednessAlgorithm::computeAbsExpectation(data, variance);
210  float bias = BlendednessAlgorithm::computeAbsBias(mu, variance);
211  accumulatorAbs(d.getX(), d.getY(), weight, std::abs(data) - bias);
212  }
213  }
214 }
215 
216 
217 
218 } // anonymous
219 
220 
222  _ctrl(ctrl)
223 {
224  if (_ctrl.doOld) {
225  _old = schema.addField<double>(
226  schema.join(name, "old"),
227  "blendedness from dot products: (child.dot(parent)/child.dot(child) - 1)"
228  );
229  }
230  if (_ctrl.doFlux) {
231  _fluxRaw = schema.addField<double>(
232  schema.join(name, "raw_flux"),
233  "measure of how flux is affected by neighbors: (1 - flux.child/flux.parent)"
234  );
235  _fluxChildRaw = schema.addField<double>(
236  schema.join(name, "raw_flux_child"),
237  "flux of the child, measured with a Gaussian weight matched to the child",
238  "count"
239  );
240  _fluxParentRaw = schema.addField<double>(
241  schema.join(name, "raw_flux_parent"),
242  "flux of the parent, measured with a Gaussian weight matched to the child",
243  "count"
244  );
245  _fluxAbs = schema.addField<double>(
246  schema.join(name, "abs_flux"),
247  "measure of how flux is affected by neighbors: (1 - flux.child/flux.parent)"
248  );
249  _fluxChildAbs = schema.addField<double>(
250  schema.join(name, "abs_flux_child"),
251  "flux of the child, measured with a Gaussian weight matched to the child",
252  "count"
253  );
254  _fluxParentAbs = schema.addField<double>(
255  schema.join(name, "abs_flux_parent"),
256  "flux of the parent, measured with a Gaussian weight matched to the child",
257  "count"
258  );
259  }
260  if (_ctrl.doShape) {
261  _shapeChildRaw = ShapeResultKey::addFields(schema, schema.join(name, "raw_child"),
262  "shape of the child, measured with a Gaussian weight matched to the child",
264  _shapeParentRaw = ShapeResultKey::addFields(schema, schema.join(name, "raw_parent"),
265  "shape of the parent, measured with a Gaussian weight matched to the child",
267  _shapeChildAbs = ShapeResultKey::addFields(schema, schema.join(name, "abs_child"),
268  "shape of the child, measured with a Gaussian weight matched to the child",
270  _shapeParentAbs = ShapeResultKey::addFields(schema, schema.join(name, "abs_parent"),
271  "shape of the parent, measured with a Gaussian weight matched to the child",
273  }
274  if (_ctrl.doShape || _ctrl.doFlux) {
275  _flagHandler = FlagHandler::addFields(schema, name,
277  }
278 }
279 
280 float BlendednessAlgorithm::computeAbsExpectation(float data, float variance) {
281  float normalization = 0.5f*std::erfc(-data/std::sqrt(2.0f*variance));
282  if (!(normalization > 0)) {
283  // avoid division by zero; we know the limit at data << -sigma -> 0.
284  return 0.0;
285  }
286  return data + (std::sqrt(0.5f*variance/boost::math::constants::pi<float>()) *
287  std::exp(-0.5f*(data*data)/variance) / normalization);
288 }
289 
290 float BlendednessAlgorithm::computeAbsBias(float mu, float variance) {
291  return (std::sqrt(2.0f*variance/boost::math::constants::pi<float>()) *
292  std::exp(-0.5f*(mu*mu)/variance)) -
293  mu*std::erfc(mu/std::sqrt(2.0f*variance));
294 }
295 
296 void BlendednessAlgorithm::_measureMoments(
298  afw::table::SourceRecord & child,
299  afw::table::Key<double> const & fluxRawKey,
300  afw::table::Key<double> const & fluxAbsKey,
301  ShapeResultKey const & _shapeRawKey,
302  ShapeResultKey const & _shapeAbsKey
303 ) const {
304 
305  if (_ctrl.doFlux || _ctrl.doShape) {
306  if (!child.getTable()->getCentroidKey().isValid()) {
308  "Centroid Key must be defined to measure the blendedness flux");
309  }
310  }
311  if (_ctrl.doShape) {
312  if (!child.getTable()->getShapeKey().isValid()) {
314  "Shape Key must be defined to measure the blendedness shape");
315  }
316  }
317  if (_ctrl.doShape || _ctrl.doFlux) {
318  bool fatal = false;
319  if (child.getTable()->getCentroidFlagKey().isValid()) {
320  if (child.getCentroidFlag()) {
321  // don't set general flag, because even a failed centroid should
322  // just fall back to the peak, and that should be fine for this
323  // measurement.
324  _flagHandler.setValue(child, NO_CENTROID.number, true);
325  }
326  }
327  if (child.getTable()->getShapeFlagKey().isValid()) {
328  if (child.getShapeFlag()) {
329  _flagHandler.setValue(child, NO_SHAPE.number, true);
330  _flagHandler.setValue(child, FAILURE.number, true);
331  }
332  }
333  if (!(child.getShape().getDeterminant() >= 0.0)) {
334  // shape flag should have been set already, but we're paranoid
335  _flagHandler.setValue(child, NO_SHAPE.number, true);
336  _flagHandler.setValue(child, FAILURE.number, true);
337  fatal = true;
338  }
339  if (!(std::isfinite(child.getX()) && std::isfinite(child.getY()))) {
340  // shape flag should have been set already, but we're paranoid
341  _flagHandler.setValue(child, NO_CENTROID.number, true);
342  _flagHandler.setValue(child, FAILURE.number, true);
343  fatal = true;
344  }
345  if (fatal) return;
346  }
347 
348  if (_ctrl.doShape) {
349  ShapeAccumulator accumulatorRaw;
350  ShapeAccumulator accumulatorAbs;
351  computeMoments(
352  image,
353  child.getCentroid(),
354  child.getShape(),
355  _ctrl.nSigmaWeightMax,
356  accumulatorRaw,
357  accumulatorAbs
358  );
359  if (_ctrl.doFlux) {
360  child.set(fluxRawKey, accumulatorRaw.getFlux());
361  child.set(fluxAbsKey, std::max(accumulatorAbs.getFlux(), 0.0));
362  }
363  _shapeRawKey.set(child, accumulatorRaw.getShape());
364  _shapeAbsKey.set(child, accumulatorAbs.getShape());
365  } else if (_ctrl.doFlux) {
366  FluxAccumulator accumulatorRaw;
367  FluxAccumulator accumulatorAbs;
368  computeMoments(
369  image,
370  child.getCentroid(),
371  child.getShape(),
372  _ctrl.nSigmaWeightMax,
373  accumulatorRaw,
374  accumulatorAbs
375  );
376  child.set(fluxRawKey, accumulatorRaw.getFlux());
377  child.set(fluxAbsKey, std::max(accumulatorAbs.getFlux(), 0.0));
378  }
379 }
380 
382  afw::image::MaskedImage<float> const & image,
384 ) const {
385  _measureMoments(image, child, _fluxChildRaw, _fluxChildAbs, _shapeChildRaw, _shapeChildAbs);
386 }
387 
388 
390  afw::image::MaskedImage<float> const & image,
392 ) const {
393  if (_ctrl.doOld) {
394  child.set(_old, computeOldBlendedness(child.getFootprint(), *image.getImage()));
395  }
396  _measureMoments(image, child, _fluxParentRaw, _fluxParentAbs, _shapeParentRaw, _shapeParentAbs);
397  if (_ctrl.doFlux) {
398  child.set(_fluxRaw, 1.0 - child.get(_fluxChildRaw)/child.get(_fluxParentRaw));
399  child.set(_fluxAbs, 1.0 - child.get(_fluxChildAbs)/child.get(_fluxParentAbs));
400  if (child.get(_fluxParentAbs) == 0.0) {
401  // We can get NaNs in the absolute measure if both parent and child have only negative
402  // biased-corrected fluxes (which we clip to zero). We can't really recover from this,
403  // so we should set the flag.
404  _flagHandler.setValue(child, FAILURE.number, true);
405  }
406  }
407 }
408 
409 }}} // namespace lsst::meas::base
virtual void set(afw::table::BaseRecord &record, ShapeResult const &value) const
Set a ShapeResult in the given record.
afw::table::Key< afw::table::Array< VariancePixelT > > variance
afw::table::Key< afw::table::Array< ImagePixelT > > image
static FlagDefinition const NO_CENTROID
Definition: Blendedness.h:83
Extent< double, 2 > Extent2D
geom::Box2I getBBox(ImageOrigin origin=PARENT) const
ShapeSlotDefinition::MeasValue getShape() const
double _w
Definition: Blendedness.cc:121
T exp(T... args)
std::string join(std::string const &a, std::string const &b) const
_const_view_t::x_iterator const_x_iterator
std::shared_ptr< Footprint > getFootprint() const
T erfc(T... args)
#define PTR(...)
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
Key< T > addField(Field< T > const &field, bool doReplace=false)
Field< T >::Value get(Key< T > const &key) const
static FlagDefinition const FAILURE
Definition: Blendedness.h:82
A FunctorKey for ShapeResult.
STL class.
static float computeAbsExpectation(float data, float variance)
Compute the posterior expectation value of the true flux in a pixel from its (Gaussian) likelihood an...
Definition: Blendedness.cc:280
T min(T... args)
double _wd
Definition: Blendedness.cc:123
static FlagDefinition const NO_SHAPE
Definition: Blendedness.h:84
std::shared_ptr< SourceTable const > getTable() const
Point< double, 2 > Point2D
void measureChildPixels(afw::image::MaskedImage< float > const &image, afw::table::SourceRecord &child) const
Definition: Blendedness.cc:381
T isfinite(T... args)
double nSigmaWeightMax
"Radius factor that sets the maximum extent of the weight function (and hence the flux measurements)"...
Definition: Blendedness.h:57
bool doShape
"Whether to compute quantities related to the Gaussian-weighted shape" ;
Definition: Blendedness.h:52
T dynamic_pointer_cast(T... args)
double x
T max(T... args)
static FlagHandler addFields(afw::table::Schema &schema, std::string const &prefix, FlagDefinitionList const &flagDefs, FlagDefinitionList const &exclDefs=FlagDefinitionList::getEmptyList())
Add Flag fields to a schema, creating a FlagHandler object to manage them.
Definition: FlagHandler.cc:37
#define LSST_EXCEPT(type,...)
Algorithm provides no uncertainy information at all.
Definition: constants.h:42
static float computeAbsBias(float mu, float variance)
Compute the bias induced by using the absolute value of a pixel instead of its value.
Definition: Blendedness.cc:290
x_iterator x_at(int x, int y) const
void set(Key< T > const &key, U const &value)
table::Box2IKey bbox
double _ww
Definition: Blendedness.cc:122
T sqrt(T... args)
SpanPixelIterator Iterator
vector-type utility class to build a collection of FlagDefinitions
Definition: FlagHandler.h:63
static FlagDefinitionList const & getFlagDefinitions()
Definition: Blendedness.cc:44
bool doFlux
"Whether to compute quantities related to the Gaussian-weighted flux" ;
Definition: Blendedness.h:47
bool doOld
"Whether to compute HeavyFootprint dot products (the old deblend.blendedness parameter)" ; ...
Definition: Blendedness.h:42
void measureParentPixels(afw::image::MaskedImage< float > const &image, afw::table::SourceRecord &child) const
Definition: Blendedness.cc:389
const_MaskedImageIterator< typename Image::x_iterator, typename Mask::x_iterator, typename Variance::x_iterator > const_x_iterator
CentroidSlotDefinition::MeasValue getCentroid() const
BlendednessAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema)
Definition: Blendedness.cc:221
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.