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