LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
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/special_functions/erf.hpp"
27 #include <boost/math/constants/constants.hpp>
28 
35 
36 namespace lsst { namespace meas { namespace base {
37 
38 
39 namespace {
40 
41 double computeOldBlendedness(
42  PTR(afw::detection::Footprint const) childFootprint,
43  afw::image::Image<float> const & parentImage
44 ) {
45  if (!childFootprint) {
46  throw LSST_EXCEPT(
47  pex::exceptions::LogicError,
48  "blendedness_old requires a Footprint."
49  );
50  }
51 
52  PTR(afw::detection::HeavyFootprint<float> const) childHeavy =
53  std::dynamic_pointer_cast<afw::detection::HeavyFootprint<float> const>(childFootprint);
54 
55  if (!childHeavy) {
56  return 0.0; // if it's not a HeavyFootprint, it's not blended.
57  }
58 
59  if (!parentImage.getBBox(afw::image::PARENT).contains(childHeavy->getBBox())) {
60  throw LSST_EXCEPT(
61  pex::exceptions::LogicError,
62  "Child footprint extends beyond image."
63  );
64  }
65 
66  // Iterate over all the spans in the child HeavyFootprint,
67  // along with iterators for the child pixels (from the HeavyFootprint)
68  // and parent pixels (from the Exposure).
69  typedef afw::detection::Footprint::SpanList::const_iterator SpanIter;
70  typedef afw::image::Image<float>::const_x_iterator ParentPixIter;
71  typedef ndarray::Array<float const,1,1>::Iterator ChildPixIter;
72  SpanIter spanIter = childHeavy->getSpans().begin();
73  SpanIter const spanEnd = childHeavy->getSpans().end();
74  ChildPixIter childPixIter = childHeavy->getImageArray().begin();
75  double cp = 0.0; // child.dot(parent)
76  double cc = 0.0; // child.dot(child)
77  while (spanIter != spanEnd) {
78  afw::geom::Span const & span = **spanIter;
79  ParentPixIter parentPixIter = parentImage.x_at(
80  span.getBeginX() - parentImage.getX0(),
81  span.getY() - parentImage.getY0()
82  );
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 
100  FluxAccumulator() : _w(0.0), _ww(0.0), _wd(0.0) {}
101 
102  void operator()(double, double, float weight, float data) {
103  _w += weight;
104  _ww += weight*weight;
105  _wd += weight*data;
106  }
107 
108  double getFlux() const { return _w*_wd/_ww; }
109 
110 protected:
111  double _w;
112  double _ww;
113  double _wd;
114 };
115 
116 
117 class ShapeAccumulator : public FluxAccumulator {
118 public:
119 
120  ShapeAccumulator() : FluxAccumulator(), _wdxx(0.0), _wdyy(0.0), _wdxy(0.0) {}
121 
122  void operator()(double x, double y, float weight, float data) {
123  FluxAccumulator::operator()(x, y, weight, data);
124  _wdxx += x*x*weight*data;
125  _wdyy += y*y*weight*data;
126  _wdxy += x*y*weight*data;
127  }
128 
129  ShapeResult getShape() const {
130  // Factor of 2 corrects for bias from weight function (correct is exact for an object
131  // with a Gaussian profile.)
132  ShapeResult result;
133  result.xx = 2.0*_wdxx/_wd;
134  result.yy = 2.0*_wdyy/_wd;
135  result.xy = 2.0*_wdxy/_wd;
136  return result;
137  }
138 
139 private:
140  double _wdxx;
141  double _wdyy;
142  double _wdxy;
143 };
144 
145 
146 template <typename Accumulator>
147 void computeMoments(
148  afw::image::MaskedImage<float> const & image,
150  afw::geom::ellipses::Quadrupole const & shape,
151  double nSigmaWeightMax,
152  Accumulator & accumulatorRaw,
153  Accumulator & accumulatorAbs
154 ) {
155  afw::geom::Box2I bbox = image.getBBox(lsst::afw::image::PARENT);
156 
157  afw::geom::ellipses::Ellipse ellipse(shape, centroid);
158  ellipse.getCore().scale(nSigmaWeightMax);
159 
160  // To evaluate an elliptically-symmetric function, we transform points
161  // by the following transform, then evaluate a circularly-symmetric function
162  // at the transformed positions.
163  afw::geom::LinearTransform transform = shape.getGridTransform();
164 
165  typedef afw::geom::ellipses::PixelRegion::Iterator SpanIter; // yields Spans
166  typedef afw::geom::Span::Iterator PointIter; // yields Point2I positions
167  typedef afw::image::MaskedImage<float>::const_x_iterator PixelIter; // yields pixel values
168 
169  afw::geom::ellipses::PixelRegion region(ellipse);
170  bool isContained = bbox.contains(region.getBBox());
171  SpanIter const spanEnd = region.end();
172  for (SpanIter spanIter = region.begin(); spanIter != spanEnd; ++spanIter) {
173  afw::geom::Span span = *spanIter;
174  if (!isContained) {
175  if (span.getY() < bbox.getMinY() || span.getY() > bbox.getMaxY()) {
176  continue;
177  }
178  span = afw::geom::Span(
179  span.getY(),
180  std::max(span.getMinX(), bbox.getMinX()),
181  std::min(span.getMaxX(), bbox.getMaxX())
182  );
183  if (span.getMinX() > span.getMaxX()) {
184  continue;
185  }
186  }
187  PixelIter pixelIter = image.x_at(
188  span.getBeginX() - image.getX0(),
189  span.getY() - image.getY0()
190  );
191  PointIter const pointEnd = span.end();
192  for (PointIter pointIter = span.begin(); pointIter != pointEnd; ++pointIter, ++pixelIter) {
194  afw::geom::Extent2D td = transform(d);
195  // use single precision for faster exp, erf
196  float weight = std::exp(static_cast<float>(-0.5*td.computeSquaredNorm()));
197  float data = pixelIter.image();
198  accumulatorRaw(d.getX(), d.getY(), weight, data);
199  float variance = pixelIter.variance();
200  float mu = (std::sqrt(variance/(2.0f/boost::math::constants::pi<float>()))*
201  std::exp(-0.5f*(data*data)/variance)) +
202  0.5f*data*boost::math::erfc(-data/std::sqrt(2.0f*variance));
203  float bias = (std::sqrt(2.0f*variance/boost::math::constants::pi<float>())*
204  std::exp(-0.5f*(mu*mu)/variance)) -
205  mu*boost::math::erfc(mu/std::sqrt(2.0f*variance));
206  accumulatorAbs(d.getX(), d.getY(), weight, std::max(std::abs(data) - bias, 0.0f));
207  }
208  }
209 }
210 
211 
212 
213 } // anonymous
214 
215 std::array<FlagDefinition,BlendednessAlgorithm::N_FLAGS> const & getFlagDefinitions() {
216  static std::array<FlagDefinition,BlendednessAlgorithm::N_FLAGS> const flagDefs = {{
217  {"flag", "general failure flag"},
218  {"flag_noCentroid", "Object has no centroid"},
219  {"flag_noShape", "Object has no shape"}
220  }};
221  return flagDefs;
222 }
223 
225  _ctrl(ctrl)
226 {
227  if (_ctrl.doOld) {
228  _old = schema.addField<double>(
229  schema.join(name, "old"),
230  "blendedness from dot products: (child.dot(parent)/child.dot(child) - 1)"
231  );
232  }
233  if (_ctrl.doFlux) {
234  _fluxRaw = schema.addField<double>(
235  schema.join(name, "raw_flux"),
236  "measure of how flux is affected by neighbors: (1 - flux.child/flux.parent)"
237  );
238  _fluxChildRaw = schema.addField<double>(
239  schema.join(name, "raw_flux_child"),
240  "flux of the child, measured with a Gaussian weight matched to the child",
241  "count"
242  );
243  _fluxParentRaw = schema.addField<double>(
244  schema.join(name, "raw_flux_parent"),
245  "flux of the parent, measured with a Gaussian weight matched to the child",
246  "count"
247  );
248  _fluxAbs = schema.addField<double>(
249  schema.join(name, "abs_flux"),
250  "measure of how flux is affected by neighbors: (1 - flux.child/flux.parent)"
251  );
252  _fluxChildAbs = schema.addField<double>(
253  schema.join(name, "abs_flux_child"),
254  "flux of the child, measured with a Gaussian weight matched to the child",
255  "count"
256  );
257  _fluxParentAbs = schema.addField<double>(
258  schema.join(name, "abs_flux_parent"),
259  "flux of the parent, measured with a Gaussian weight matched to the child",
260  "count"
261  );
262  }
263  if (_ctrl.doShape) {
264  _shapeChildRaw = ShapeResultKey::addFields(schema, schema.join(name, "raw_child"),
265  "shape of the child, measured with a Gaussian weight matched to the child",
267  _shapeParentRaw = ShapeResultKey::addFields(schema, schema.join(name, "raw_parent"),
268  "shape of the parent, measured with a Gaussian weight matched to the child",
270  _shapeChildAbs = ShapeResultKey::addFields(schema, schema.join(name, "abs_child"),
271  "shape of the child, measured with a Gaussian weight matched to the child",
273  _shapeParentAbs = ShapeResultKey::addFields(schema, schema.join(name, "abs_parent"),
274  "shape of the parent, measured with a Gaussian weight matched to the child",
276  }
277  if (_ctrl.doShape || _ctrl.doFlux) {
278  _flagHandler = FlagHandler::addFields(schema, name,
279  getFlagDefinitions().begin(), getFlagDefinitions().end());
280  }
281 }
282 
285  afw::table::SourceRecord & child,
286  afw::table::Key<double> const & fluxRawKey,
287  afw::table::Key<double> const & fluxAbsKey,
288  ShapeResultKey const & _shapeRawKey,
289  ShapeResultKey const & _shapeAbsKey
290 ) const {
291 
292  if (_ctrl.doFlux || _ctrl.doShape) {
293  if (!child.getTable()->getCentroidKey().isValid()) {
294  throw LSST_EXCEPT(pex::exceptions::LogicError,
295  "Centroid Key must be defined to measure the blendedness flux");
296  }
297  }
298  if (_ctrl.doShape) {
299  if (!child.getTable()->getShapeKey().isValid()) {
300  throw LSST_EXCEPT(pex::exceptions::LogicError,
301  "Shape Key must be defined to measure the blendedness shape");
302  }
303  }
304  if (_ctrl.doShape || _ctrl.doFlux) {
305  bool fatal = false;
306  if (child.getTable()->getCentroidFlagKey().isValid()) {
307  if (child.getCentroidFlag()) {
308  // don't set general flag, because even a failed centroid should
309  // just fall back to the peak, and that should be fine for this
310  // measurement.
311  _flagHandler.setValue(child, NO_CENTROID, true);
312  }
313  }
314  if (child.getTable()->getShapeFlagKey().isValid()) {
315  if (child.getShapeFlag()) {
316  _flagHandler.setValue(child, NO_SHAPE, true);
317  _flagHandler.setValue(child, FAILURE, true);
318  }
319  }
320  if (!(child.getShape().getDeterminant() >= 0.0)) {
321  // shape flag should have been set already, but we're paranoid
322  _flagHandler.setValue(child, NO_SHAPE, true);
323  _flagHandler.setValue(child, FAILURE, true);
324  fatal = true;
325  }
326  if (!(std::isfinite(child.getX()) && std::isfinite(child.getY()))) {
327  // shape flag should have been set already, but we're paranoid
328  _flagHandler.setValue(child, NO_CENTROID, true);
329  _flagHandler.setValue(child, FAILURE, true);
330  fatal = true;
331  }
332  if (fatal) return;
333  }
334 
335  if (_ctrl.doShape) {
336  ShapeAccumulator accumulatorRaw;
337  ShapeAccumulator accumulatorAbs;
338  computeMoments(
339  image,
340  child.getCentroid(),
341  child.getShape(),
343  accumulatorRaw,
344  accumulatorAbs
345  );
346  if (_ctrl.doFlux) {
347  child.set(fluxRawKey, accumulatorRaw.getFlux());
348  child.set(fluxAbsKey, accumulatorAbs.getFlux());
349  }
350  _shapeRawKey.set(child, accumulatorRaw.getShape());
351  _shapeAbsKey.set(child, accumulatorAbs.getShape());
352  } else if (_ctrl.doFlux) {
353  FluxAccumulator accumulatorRaw;
354  FluxAccumulator accumulatorAbs;
355  computeMoments(
356  image,
357  child.getCentroid(),
358  child.getShape(),
360  accumulatorRaw,
361  accumulatorAbs
362  );
363  child.set(fluxRawKey, accumulatorRaw.getFlux());
364  child.set(fluxAbsKey, accumulatorAbs.getFlux());
365  }
366 }
367 
371 ) const {
373 }
374 
375 
379 ) const {
380  if (_ctrl.doOld) {
381  child.set(_old, computeOldBlendedness(child.getFootprint(), *image.getImage()));
382  }
384  if (_ctrl.doFlux) {
385  child.set(_fluxRaw, 1.0 - child.get(_fluxChildRaw)/child.get(_fluxParentRaw));
386  child.set(_fluxAbs, 1.0 - child.get(_fluxChildAbs)/child.get(_fluxParentAbs));
387  if (child.get(_fluxParentAbs) == 0.0) {
388  // We can get NaNs in the absolute measure if both parent and child have only negative
389  // biased-corrected fluxes (which we clip to zero). We can't really recover from this,
390  // so we should set the flag.
391  _flagHandler.setValue(child, FAILURE, true);
392  }
393  }
394 }
395 
396 }}} // namespace lsst::meas::base
int y
Defines the fields and offsets for a table.
Definition: Schema.h:44
double getX() const
Return the centroid slot x coordinate.
Definition: Source.h:929
table::Key< std::string > name
Definition: ApCorrMap.cc:71
CentroidSlotDefinition::MeasValue getCentroid() const
Get the value of the Centroid slot measurement.
Definition: Source.h:902
tbl::Key< double > weight
double _w
Definition: Blendedness.cc:111
afw::table::Key< double > _fluxParentRaw
Definition: Blendedness.h:126
afw::table::Schema schema
Definition: GaussianPsf.cc:41
Extent< double, 2 > Extent2D
Definition: Extent.h:361
_const_view_t::x_iterator const_x_iterator
A const iterator for traversing the pixels in a row.
Definition: Image.h:157
lsst::afw::detection::Footprint Footprint
Definition: Source.h:60
Represent a set of pixels of an arbitrary shape and size, including values for those pixels; a HeavyF...
afw::table::Key< double > _fluxRaw
Definition: Blendedness.h:124
double _wdxy
Definition: Blendedness.cc:142
bool getCentroidFlag() const
Return true if the measurement in the Centroid slot failed.
Definition: Source.h:910
ImagePtr getImage(bool const noThrow=false) const
Return a (Ptr to) the MaskedImage&#39;s image.
Definition: MaskedImage.h:875
std::array< FlagDefinition, BlendednessAlgorithm::N_FLAGS > const & getFlagDefinitions()
Definition: Blendedness.cc:215
std::string join(std::string const &a, std::string const &b) const
Join strings using the field delimiter appropriate for this Schema.
afw::table::Key< double > _fluxChildRaw
Definition: Blendedness.h:125
afw::table::Key< double > _old
Definition: Blendedness.h:123
double _wdxx
Definition: Blendedness.cc:140
void set(Key< T > const &key, U const &value)
Set value of a field for the given key.
Definition: BaseRecord.h:145
A FunctorKey for ShapeResult.
void measureParentPixels(afw::image::MaskedImage< float > const &image, afw::table::SourceRecord &child) const
Definition: Blendedness.cc:376
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
def fatal
Definition: log.py:107
virtual void set(afw::table::BaseRecord &record, ShapeResult const &value) const
Set a ShapeResult in the given record.
double _wd
Definition: Blendedness.cc:113
void ImageT ImageT int float saturatedPixelValue int const width
Definition: saturated.cc:44
void measureChildPixels(afw::image::MaskedImage< float > const &image, afw::table::SourceRecord &child) const
Definition: Blendedness.cc:368
afw::table::Key< double > _fluxChildAbs
Definition: Blendedness.h:128
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:78
if(width!=gim.getWidth()||height!=gim.getHeight()||x0!=gim.getX0()||y0!=gim.getY0())
Definition: saturated.cc:47
double nSigmaWeightMax
&quot;Radius factor that sets the maximum extent of the weight function (and hence the flux measurements)&quot;...
Definition: Blendedness.h:57
bool doShape
&quot;Whether to compute quantities related to the Gaussian-weighted shape&quot; ;
Definition: Blendedness.h:52
double x
Field< T >::Value get(Key< T > const &key) const
Return the value of a field for the given key.
Definition: BaseRecord.h:132
void setValue(afw::table::BaseRecord &record, std::size_t i, bool value) const
Set the flag field corresponding to the given enum value.
Definition: FlagHandler.h:188
afw::table::Key< double > _fluxAbs
Definition: Blendedness.h:127
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Definition: Exception.h:46
void _measureMoments(afw::image::MaskedImage< float > const &image, afw::table::SourceRecord &child, afw::table::Key< double > const &fluxRawKey, afw::table::Key< double > const &fluxAbsKey, ShapeResultKey const &_shapeRawKey, ShapeResultKey const &_shapeAbsKey) const
Definition: Blendedness.cc:283
boost::shared_ptr< SourceTable const > getTable() const
Definition: Source.h:92
#define PTR(...)
Definition: base.h:41
double _wdyy
Definition: Blendedness.cc:141
Algorithm provides no uncertainy information at all.
Definition: constants.h:42
double getY() const
Return the centroid slot y coordinate.
Definition: Source.h:932
bool getShapeFlag() const
Return true if the measurement in the Shape slot failed.
Definition: Source.h:922
Point< double, 2 > Point2D
Definition: Point.h:288
SpanPixelIterator Iterator
An iterator over points in the Span.
Definition: Span.h:61
Key< T > addField(Field< T > const &field, bool doReplace=false)
Add a new field to the Schema, and return the associated Key.
Record class that contains measurements made on a single exposure.
Definition: Source.h:80
double _ww
Definition: Blendedness.cc:112
ShapeSlotDefinition::MeasValue getShape() const
Get the value of the Shape slot measurement.
Definition: Source.h:914
double getDeterminant() const
Return the determinant of the matrix representation.
Definition: Quadrupole.h:85
Forward declarations, typedefs, and definitions for Ellipse.
bool doFlux
&quot;Whether to compute quantities related to the Gaussian-weighted flux&quot; ;
Definition: Blendedness.h:47
boost::shared_ptr< Footprint > getFootprint() const
Definition: Source.h:88
Definitions for Ellipse::GridTransform and BaseCore::GridTransform.
static FlagHandler addFields(afw::table::Schema &schema, std::string const &prefix, FlagDefinition const *begin, FlagDefinition const *end)
Add Flag fields to a schema, creating a FlagHandler object to manage them.
Definition: FlagHandler.cc:28
bool doOld
&quot;Whether to compute HeavyFootprint dot products (the old deblend.blendedness parameter)&quot; ; ...
Definition: Blendedness.h:42
afw::table::Key< double > _fluxParentAbs
Definition: Blendedness.h:129
BlendednessAlgorithm(Control const &ctrl, std::string const &name, afw::table::Schema &schema)
Definition: Blendedness.cc:224
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.