LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
FootprintSet.cc
Go to the documentation of this file.
1 // -*- lsst-c++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008, 2009, 2010 LSST Corporation.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <http://www.lsstcorp.org/LegalNotices/>.
23  */
24 
25 /*
26  * Utilities to detect sets of Footprint%s
27  *
28  * Create and use an lsst::afw::detection::FootprintSet, a collection of pixels above (or below) a threshold
29  * in an Image
30  *
31  * The "collections of pixels" are represented as lsst::afw::detection::Footprint%s, so an example application
32  * would be:
33  *
34  namespace image = lsst::afw::image; namespace detection = lsst::afw::detection;
35 
36  image::MaskedImage<float> img(10,20);
37  *img.getImage() = 100;
38 
39  detection::FootprintSet<float> sources(img, 10);
40  cout << "Found " << sources.getFootprints()->size() << " sources" << std::endl;
41  */
42 #include <cstdint>
43 #include <memory>
44 #include <algorithm>
45 #include <cassert>
46 #include <set>
47 #include <string>
48 #include <map>
49 #include <vector>
50 
51 #include "boost/format.hpp"
52 #include "lsst/pex/exceptions.h"
59 
60 namespace lsst {
61 namespace afw {
62 namespace detection {
63 
64 namespace {
66 
67 using IdPixelT = std::uint64_t; // Type of temporary Images used in merging Footprints
68 
69 struct Threshold_traits {};
70 struct ThresholdLevel_traits : public Threshold_traits { // Threshold is a single number
71 };
72 struct ThresholdPixelLevel_traits : public Threshold_traits { // Threshold varies from pixel to pixel
73 };
74 struct ThresholdBitmask_traits : public Threshold_traits { // Threshold ORs with a bitmask
75 };
76 
77 template <typename PixelT>
78 class setIdImage {
79 public:
80  explicit setIdImage(std::uint64_t const id, bool overwriteId = false, long const idMask = 0x0)
81  : _id(id),
82  _idMask(idMask),
83  _withSetReplace(false),
84  _overwriteId(overwriteId),
85  _oldIds(nullptr),
86  _pos() {
87  if (_id & _idMask) {
88  throw LSST_EXCEPT(
89  pex::exceptions::InvalidParameterError,
90  str(boost::format("Id 0x%x sets bits in the protected mask 0x%x") % _id % _idMask));
91  }
92  }
93 
94  setIdImage(std::uint64_t const id, std::set<std::uint64_t> *oldIds, bool overwriteId = false,
95  long const idMask = 0x0)
96  : _id(id),
97  _idMask(idMask),
98  _withSetReplace(true),
99  _overwriteId(overwriteId),
100  _oldIds(oldIds),
101  _pos(oldIds->begin()) {
102  if (_id & _idMask) {
103  throw LSST_EXCEPT(
104  pex::exceptions::InvalidParameterError,
105  str(boost::format("Id 0x%x sets bits in the protected mask 0x%x") % _id % _idMask));
106  }
107  }
108 
109  void operator()(lsst::geom::Point2I const &point, PixelT &input) {
110  if (_overwriteId) {
111  auto val = input & ~_idMask;
112 
113  if (val != 0 && _withSetReplace) {
114  _pos = _oldIds->insert(_pos, val);
115  }
116 
117  input = (input & _idMask) + _id;
118  } else {
119  input += _id;
120  }
121  }
122 
123 private:
124  std::uint64_t const _id;
125  long const _idMask;
126  bool _withSetReplace;
127  bool _overwriteId;
128  std::set<std::uint64_t> *_oldIds;
130 };
131 
132 //
133 // Define our own functions to handle NaN tests; this gives us the
134 // option to define a value for e.g. image::MaskPixel or int
135 //
136 template <typename T>
137 inline bool isBadPixel(T) {
138  return false;
139 }
140 
141 template <>
142 inline bool isBadPixel(float val) {
143  return std::isnan(val);
144 }
145 
146 template <>
147 inline bool isBadPixel(double val) {
148  return std::isnan(val);
149 }
150 
151 /*
152  * Return the number of bits required to represent a unsigned long
153  */
154 int nbit(unsigned long i) {
155  int n = 0;
156  while (i > 0) {
157  ++n;
158  i >>= 1;
159  }
160 
161  return n;
162 }
163 /*
164  * Find the list of pixel values that lie in a Footprint
165  *
166  * Used when the Footprints are constructed from an Image containing Footprint indices
167  */
168 template <typename T>
169 class FindIdsInFootprint {
170 public:
171  explicit FindIdsInFootprint() : _ids(), _old(0) {}
172 
173  // Reset everything for a new Footprint
174  void reset() {
175  _ids.clear();
176  _old = 0;
177  }
178 
179  // Take by copy and not be reference on purpose
180  void operator()(lsst::geom::Point2I const &point, T val) {
181  if (val != _old) {
182  _ids.insert(val);
183  _old = val;
184  }
185  }
186 
187  std::set<T> const &getIds() const { return _ids; }
188 
189 private:
190  std::set<T> _ids;
191  T _old;
192 };
193 
194 /*
195  * Sort peaks by decreasing pixel value. N.b. -ve peaks are sorted the same way as +ve ones
196  */
197 struct SortPeaks {
199  if (a->getPeakValue() != b->getPeakValue()) {
200  return (a->getPeakValue() > b->getPeakValue());
201  }
202 
203  if (a->getIx() != b->getIx()) {
204  return (a->getIx() < b->getIx());
205  }
206 
207  return (a->getIy() < b->getIy());
208  }
209 };
210 /*
211  * Worker routine for merging two FootprintSets, possibly growing them as we proceed
212  */
213 FootprintSet mergeFootprintSets(FootprintSet const &lhs, // the FootprintSet to be merged to
214  int rLhs, // Grow lhs Footprints by this many pixels
215  FootprintSet const &rhs, // the FootprintSet to be merged into lhs
216  int rRhs, // Grow rhs Footprints by this many pixels
217  FootprintControl const &ctrl // Control how the grow is done
218 ) {
219  using FootprintList = FootprintSet::FootprintList;
220  // The isXXX routines return <isset, value>
221  bool const circular = ctrl.isCircular().first && ctrl.isCircular().second;
222  bool const isotropic = ctrl.isIsotropic().second; // isotropic grow as opposed to a Manhattan metric
223  // n.b. Isotropic grows are significantly slower
224  bool const left = ctrl.isLeft().first && ctrl.isLeft().second;
225  bool const right = ctrl.isRight().first && ctrl.isRight().second;
226  bool const up = ctrl.isUp().first && ctrl.isUp().second;
227  bool const down = ctrl.isDown().first && ctrl.isDown().second;
228 
229  lsst::geom::Box2I const region = lhs.getRegion();
230  if (region != rhs.getRegion()) {
231  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
232  boost::format("The two FootprintSets must have the same region").str());
233  }
234 
235  auto idImage = std::make_shared<image::Image<IdPixelT>>(region);
236  idImage->setXY0(region.getMinX(), region.getMinY());
237  *idImage = 0;
238 
239  FootprintList const &lhsFootprints = *lhs.getFootprints();
240  FootprintList const &rhsFootprints = *rhs.getFootprints();
241  int const nLhs = lhsFootprints.size();
242  int const nRhs = rhsFootprints.size();
243  /*
244  * In general the lists of Footprints overlap, so we need to make sure that the IDs can be
245  * uniquely recovered from the idImage. We do this by allocating a range of bits to the lhs IDs
246  */
247  int const lhsIdNbit = nbit(nLhs);
248  int const lhsIdMask = (lhsIdNbit == 0) ? 0x0 : (1 << lhsIdNbit) - 1;
249 
250  if (std::size_t(nRhs << lhsIdNbit) > std::numeric_limits<IdPixelT>::max() - 1) {
251  throw LSST_EXCEPT(pex::exceptions::OverflowError,
252  (boost::format("%d + %d footprints need too many bits; change IdPixelT typedef") %
253  nLhs % nRhs)
254  .str());
255  }
256  /*
257  * When we insert grown Footprints into the idImage we can potentially overwrite an entire Footprint,
258  * losing any peaks that it might contain. We'll preserve the overwritten Ids in case we need to
259  * get them back (n.b. Footprints that overlap, but both if which survive, will appear in this list)
260  */
261  using OldIdMap = std::map<int, std::set<std::uint64_t>>;
262  OldIdMap overwrittenIds; // here's a map from id -> overwritten IDs
263 
264  auto grower = [&circular, &up, &down, &left, &right, &isotropic](
265  std::shared_ptr<Footprint> const &foot, int amount) -> std::shared_ptr<Footprint> {
266  if (circular) {
268  auto tmpFoot = std::make_shared<Footprint>(foot->getSpans()->dilated(amount, element),
269  foot->getRegion());
270  return tmpFoot;
271  } else {
272  int top = up ? amount : 0;
273  int bottom = down ? amount : 0;
274  int lLimit = left ? amount : 0;
275  int rLimit = right ? amount : 0;
276 
277  auto yRange = top + bottom + 1;
278  std::vector<geom::Span> spanList;
279  spanList.reserve(yRange);
280 
281  for (auto dy = 1; dy <= top; ++dy) {
282  spanList.emplace_back(dy, 0, 0);
283  }
284  for (auto dy = -1; dy >= -bottom; --dy) {
285  spanList.emplace_back(dy, 0, 0);
286  }
287  spanList.emplace_back(0, -lLimit, rLimit);
288  geom::SpanSet structure(std::move(spanList));
289  auto tmpFoot =
290  std::make_shared<Footprint>(foot->getSpans()->dilated(structure), foot->getRegion());
291  return tmpFoot;
292  }
293  };
294 
295  IdPixelT id = 1; // the ID inserted into the image
296  for (FootprintList::const_iterator ptr = lhsFootprints.begin(), end = lhsFootprints.end(); ptr != end;
297  ++ptr, ++id) {
299 
300  if (rLhs > 0 && foot->getArea() > 0) {
301  foot = grower(foot, rLhs);
302  }
303 
304  std::set<std::uint64_t> overwritten;
305  foot->getSpans()
306  ->clippedTo(idImage->getBBox())
307  ->applyFunctor(setIdImage<IdPixelT>(id, &overwritten, true), *idImage);
308 
309  if (!overwritten.empty()) {
310  overwrittenIds.insert(overwrittenIds.end(), std::make_pair(id, overwritten));
311  }
312  }
313 
314  assert(id <= std::size_t(1 << lhsIdNbit));
315  id = (1 << lhsIdNbit);
316  for (FootprintList::const_iterator ptr = rhsFootprints.begin(), end = rhsFootprints.end(); ptr != end;
317  ++ptr, id += (1 << lhsIdNbit)) {
319 
320  if (rRhs > 0 && foot->getArea() > 0) {
321  foot = grower(foot, rRhs);
322  }
323 
324  std::set<std::uint64_t> overwritten;
325  foot->getSpans()
326  ->clippedTo(idImage->getBBox())
327  ->applyFunctor(setIdImage<IdPixelT>(id, &overwritten, true, lhsIdMask), *idImage);
328 
329  if (!overwritten.empty()) {
330  overwrittenIds.insert(overwrittenIds.end(), std::make_pair(id, overwritten));
331  }
332  }
333 
334  FootprintSet fs(*idImage, Threshold(1), 1, false); // detect all pixels in rhs + lhs
335  /*
336  * Now go through the new Footprints looking up and remembering their progenitor's IDs; we'll use
337  * these IDs to merge the peaks in a moment
338  *
339  * We can't do this as we go through the idFinder as the IDs it returns are
340  * (lhsId + 1) | ((rhsId + 1) << nbit)
341  * and, depending on the geometry, values of lhsId and/or rhsId can appear multiple times
342  * (e.g. if nbit is 2, idFinder IDs 0x5 and 0x6 both contain lhsId = 0) so we get duplicates
343  * of peaks. This is not too bad, but it's a bit of a pain to make the lists unique again,
344  * and we avoid this by this two-step process.
345  */
346  FindIdsInFootprint<IdPixelT> idFinder;
347  for (const auto& foot : *fs.getFootprints()) {
348  // find the (mangled) [lr]hsFootprint IDs that contribute to foot
349  foot->getSpans()->applyFunctor(idFinder, *idImage);
350 
351  std::set<std::uint64_t> lhsFootprintIndxs, rhsFootprintIndxs; // indexes into [lr]hsFootprints
352 
353  for (unsigned int indx : idFinder.getIds()) {
354  if ((indx & lhsIdMask) > 0) {
355  std::uint64_t i = (indx & lhsIdMask) - 1;
356  lhsFootprintIndxs.insert(i);
357  /*
358  * Now allow for Footprints that vanished beneath this one
359  */
360  OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
361  if (mapPtr != overwrittenIds.end()) {
362  std::set<std::uint64_t> &overwritten = mapPtr->second;
363 
364  for (unsigned long ptr : overwritten) {
365  lhsFootprintIndxs.insert((ptr & lhsIdMask) - 1);
366  }
367  }
368  }
369  indx >>= lhsIdNbit;
370 
371  if (indx > 0) {
372  std::uint64_t i = indx - 1;
373  rhsFootprintIndxs.insert(i);
374  /*
375  * Now allow for Footprints that vanished beneath this one
376  */
377  OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
378  if (mapPtr != overwrittenIds.end()) {
379  std::set<std::uint64_t> &overwritten = mapPtr->second;
380 
381  for (unsigned long ptr : overwritten) {
382  rhsFootprintIndxs.insert(ptr - 1);
383  }
384  }
385  }
386  }
387  /*
388  * We now have a complete set of Footprints that contributed to this one, so merge
389  * all their Peaks into the new one
390  */
391  PeakCatalog &peaks = foot->getPeaks();
392 
393  for (unsigned long i : lhsFootprintIndxs) {
394  assert(i < lhsFootprints.size());
395  PeakCatalog const &oldPeaks = lhsFootprints[i]->getPeaks();
396 
397  int const nold = peaks.size();
398  peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
399  // We use getInternal() here to get the vector of shared_ptr that Catalog uses internally,
400  // which causes the STL algorithm to copy pointers instead of PeakRecords (which is what
401  // it'd try to do if we passed Catalog's own iterators).
402  std::inplace_merge(peaks.getInternal().begin(), peaks.getInternal().begin() + nold,
403  peaks.getInternal().end(), SortPeaks());
404  }
405 
406  for (unsigned long i : rhsFootprintIndxs) {
407  assert(i < rhsFootprints.size());
408  PeakCatalog const &oldPeaks = rhsFootprints[i]->getPeaks();
409 
410  int const nold = peaks.size();
411  peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
412  // See note above on why we're using getInternal() here.
413  std::inplace_merge(peaks.getInternal().begin(), peaks.getInternal().begin() + nold,
414  peaks.getInternal().end(), SortPeaks());
415  }
416  idFinder.reset();
417  }
418 
419  return fs;
420 }
421 /*
422  * run-length code for part of object
423  */
424 class IdSpan {
425 public:
426  explicit IdSpan(int id, int y, int x0, int x1, double good) : id(id), y(y), x0(x0), x1(x1), good(good) {}
427  int id; /* ID for object */
428  int y; /* Row wherein IdSpan dwells */
429  int x0, x1; /* inclusive range of columns */
430  bool good; /* includes a value over the desired threshold? */
431 };
432 /*
433  * comparison functor; sort by ID then row
434  */
435 struct IdSpanCompare {
436  bool operator()(IdSpan const &a, IdSpan const &b) const {
437  if (a.id < b.id) {
438  return true;
439  } else if (a.id > b.id) {
440  return false;
441  } else {
442  return (a.y < b.y) ? true : false;
443  }
444  }
445 };
446 /*
447  * Follow a chain of aliases, returning the final resolved value.
448  */
449 int resolve_alias(std::vector<int> const &aliases, /* list of aliases */
450  int id) { /* alias to look up */
451  int resolved = id; /* resolved alias */
452 
453  while (id != aliases[id]) {
454  resolved = id = aliases[id];
455  }
456 
457  return (resolved);
458 }
460 } // namespace
461 
462 namespace {
463 template <typename ImageT>
464 void findPeaksInFootprint(ImageT const &image, bool polarity, PeakCatalog &peaks, Footprint &foot,
465  std::size_t const margin = 0) {
466  auto spanSet = foot.getSpans();
467  if (spanSet->size() == 0) {
468  return;
469  }
470  auto bbox = image.getBBox();
471  for (auto const &spanIter : *spanSet) {
472  auto y = spanIter.getY() - image.getY0();
473  if (static_cast<std::size_t>(y + image.getY0()) < bbox.getMinY() + margin ||
474  static_cast<std::size_t>(y + image.getY0()) > bbox.getMaxY() - margin) {
475  continue;
476  }
477  for (auto x = spanIter.getMinX() - image.getX0(); x <= spanIter.getMaxX() - image.getX0(); ++x) {
478  if (static_cast<std::size_t>(x + image.getX0()) < (bbox.getMinX() + margin) ||
479  static_cast<std::size_t>(x + image.getX0()) > (bbox.getMaxX() - margin)) {
480  continue;
481  }
482  auto val = image(x, y);
483  if (polarity) { // look for +ve peaks
484  if (image(x - 1, y + 1) > val || image(x, y + 1) > val || image(x + 1, y + 1) > val ||
485  image(x - 1, y) > val || image(x + 1, y) > val || image(x - 1, y - 1) > val ||
486  image(x, y - 1) > val || image(x + 1, y - 1) > val) {
487  continue;
488  }
489  } else { // look for -ve "peaks" (pits)
490  if (image(x - 1, y + 1) < val || image(x, y + 1) < val || image(x + 1, y + 1) < val ||
491  image(x - 1, y) < val || image(x + 1, y) < val || image(x - 1, y - 1) < val ||
492  image(x, y - 1) < val || image(x + 1, y - 1) < val) {
493  continue;
494  }
495  }
496 
497  foot.addPeak(x + image.getX0(), y + image.getY0(), val);
498  }
499  }
500 }
501 
502 template <typename ImageT>
503 class FindMaxInFootprint {
504 public:
505  explicit FindMaxInFootprint(bool polarity)
506  : _polarity(polarity),
507  _x(0),
508  _y(0),
509  _min(std::numeric_limits<double>::max()),
510  _max(-std::numeric_limits<double>::max()) {}
511 
512  void operator()(lsst::geom::Point2I const &point, ImageT const &val) {
513  if (_polarity) {
514  if (val > _max) {
515  _max = val;
516  _x = point.getX();
517  _y = point.getY();
518  }
519  } else {
520  if (val < _min) {
521  _min = val;
522  _x = point.getX();
523  _y = point.getY();
524  }
525  }
526  }
527 
528  void addRecord(Footprint &foot) const { foot.addPeak(_x, _y, _polarity ? _max : _min); }
529 
530 private:
531  bool _polarity;
532  int _x, _y;
533  double _min, _max;
534 };
535 
536 template <typename ImageT, typename ThresholdT>
537 void findPeaks(std::shared_ptr<Footprint> foot, ImageT const &img, bool polarity, ThresholdT) {
538  findPeaksInFootprint(img, polarity, foot->getPeaks(), *foot, 1);
539 
540  // We use getInternal() here to get the vector of shared_ptr that Catalog uses internally,
541  // which causes the STL algorithm to copy pointers instead of PeakRecords (which is what
542  // it'd try to do if we passed Catalog's own iterators).
543  std::stable_sort(foot->getPeaks().getInternal().begin(), foot->getPeaks().getInternal().end(),
544  SortPeaks());
545 
546  if (foot->getPeaks().empty()) {
547  FindMaxInFootprint<typename ImageT::Pixel> maxFinder(polarity);
548  foot->getSpans()->applyFunctor(maxFinder, ndarray::ndImage(img.getArray(), img.getXY0()));
549  maxFinder.addRecord(*foot);
550  }
551 }
552 
553 // No need to search for peaks when processing a Mask
554 template <typename ImageT>
555 void findPeaks(std::shared_ptr<Footprint>, ImageT const &, bool, ThresholdBitmask_traits) {
556  ;
557 }
558 } // namespace
559 
560 /*
561  * Functions to determine if a pixel's in a Footprint
562  */
563 template <typename ImagePixelT, typename IterT>
564 static inline bool inFootprint(ImagePixelT pixVal, IterT, bool polarity, double thresholdVal,
565  ThresholdLevel_traits) {
566  return (polarity ? pixVal : -pixVal) >= thresholdVal;
567 }
568 
569 template <typename ImagePixelT, typename IterT>
570 static inline bool inFootprint(ImagePixelT pixVal, IterT var, bool polarity, double thresholdVal,
571  ThresholdPixelLevel_traits) {
572  return (polarity ? pixVal : -pixVal) >= thresholdVal * ::sqrt(*var);
573 }
574 
575 template <typename ImagePixelT, typename IterT>
576 static inline bool inFootprint(ImagePixelT pixVal, IterT, bool, double thresholdVal,
577  ThresholdBitmask_traits) {
578  return (pixVal & static_cast<long>(thresholdVal));
579 }
580 
581 /*
582  * Advance the x_iterator to the variance image, when relevant (it may be NULL otherwise)
583  */
584 template <typename IterT>
585 static inline IterT advancePtr(IterT varPtr, Threshold_traits) {
586  return varPtr;
587 }
588 
589 template <typename IterT>
590 static inline IterT advancePtr(IterT varPtr, ThresholdPixelLevel_traits) {
591  return varPtr + 1;
592 }
593 
594 /*
595  * Here's the working routine for the FootprintSet constructors; see documentation
596  * of the constructors themselves
597  */
598 template <typename ImagePixelT, typename MaskPixelT, typename VariancePixelT, typename ThresholdTraitT>
599 static void findFootprints(
600  typename FootprintSet::FootprintList *_footprints, // Footprints
601  lsst::geom::Box2I const &_region, // BBox of pixels that are being searched
602  image::ImageBase<ImagePixelT> const &img, // Image to search for objects
603  image::Image<VariancePixelT> const *var, // img's variance
604  double const footprintThreshold, // threshold value for footprint
605  double const includeThresholdMultiplier, // threshold (relative to footprintThreshold) for inclusion
606  bool const polarity, // if false, search _below_ thresholdVal
607  int const npixMin, // minimum number of pixels in an object
608  bool const setPeaks // should I set the Peaks list?
609 ) {
610  int id; /* object ID */
611  int in_span; /* object ID of current IdSpan */
612  int nobj = 0; /* number of objects found */
613  int x0 = 0; /* unpacked from a IdSpan */
614 
615  double includeThreshold = footprintThreshold * includeThresholdMultiplier; // Threshold for inclusion
616 
617  int const row0 = img.getY0();
618  int const col0 = img.getX0();
619  int const height = img.getHeight();
620  int const width = img.getWidth();
621  /*
622  * Storage for arrays that identify objects by ID. We want to be able to
623  * refer to idp[-1] and idp[width], hence the (width + 2)
624  */
625  std::vector<int> id1(width + 2);
626  std::fill(id1.begin(), id1.end(), 0);
627  std::vector<int> id2(width + 2);
628  std::fill(id2.begin(), id2.end(), 0);
629  std::vector<int>::iterator idc = id1.begin() + 1; // object IDs in current/
630  std::vector<int>::iterator idp = id2.begin() + 1; // previous row
631 
632  std::vector<int> aliases; // aliases for initially disjoint parts of Footprints
633  aliases.reserve(1 + height / 20); // initial size of aliases
634 
635  std::vector<IdSpan> spans; // y:x0,x1 for objects
636  spans.reserve(aliases.capacity()); // initial size of spans
637 
638  aliases.push_back(0); // 0 --> 0
639  /*
640  * Go through image identifying objects
641  */
642  using x_iterator = typename image::Image<ImagePixelT>::x_iterator;
643  using x_var_iterator = typename image::Image<VariancePixelT>::x_iterator;
644 
645  in_span = 0; // not in a span
646  for (int y = 0; y != height; ++y) {
647  if (idc == id1.begin() + 1) {
648  idc = id2.begin() + 1;
649  idp = id1.begin() + 1;
650  } else {
651  idc = id1.begin() + 1;
652  idp = id2.begin() + 1;
653  }
654  std::fill_n(idc - 1, width + 2, 0);
655 
656  in_span = 0; /* not in a span */
657  bool good = (includeThresholdMultiplier == 1.0); /* Span exceeds the threshold? */
658 
659  x_iterator pixPtr = img.row_begin(y);
660  x_var_iterator varPtr = (var == nullptr) ? nullptr : var->row_begin(y);
661  for (int x = 0; x < width; ++x, ++pixPtr, varPtr = advancePtr(varPtr, ThresholdTraitT())) {
662  ImagePixelT const pixVal = *pixPtr;
663 
664  if (isBadPixel(pixVal) ||
665  !inFootprint(pixVal, varPtr, polarity, footprintThreshold, ThresholdTraitT())) {
666  if (in_span) {
667  spans.emplace_back(in_span, y, x0, x - 1, good);
668 
669  in_span = 0;
670  good = false;
671  }
672  } else { /* a pixel to fix */
673  if (idc[x - 1] != 0) {
674  id = idc[x - 1];
675  } else if (idp[x - 1] != 0) {
676  id = idp[x - 1];
677  } else if (idp[x] != 0) {
678  id = idp[x];
679  } else if (idp[x + 1] != 0) {
680  id = idp[x + 1];
681  } else {
682  id = ++nobj;
683  aliases.push_back(id);
684  }
685 
686  idc[x] = id;
687  if (!in_span) {
688  x0 = x;
689  in_span = id;
690  }
691  /*
692  * Do we need to merge ID numbers? If so, make suitable entries in aliases[]
693  */
694  if (idp[x + 1] != 0 && idp[x + 1] != id) {
695  aliases[resolve_alias(aliases, idp[x + 1])] = resolve_alias(aliases, id);
696 
697  idc[x] = id = idp[x + 1];
698  }
699 
700  if (!good && inFootprint(pixVal, varPtr, polarity, includeThreshold, ThresholdTraitT())) {
701  good = true;
702  }
703  }
704  }
705 
706  if (in_span) {
707  spans.emplace_back(in_span, y, x0, width - 1, good);
708  }
709  }
710  /*
711  * Resolve aliases; first alias chains, then the IDs in the spans
712  */
713  for (auto & span : spans) {
714  span.id = resolve_alias(aliases, span.id);
715  }
716  /*
717  * Sort spans by ID, so we can sweep through them once
718  */
719  if (spans.size() > 0) {
720  std::sort(spans.begin(), spans.end(), IdSpanCompare());
721  }
722  /*
723  * Build Footprints from spans
724  */
725  unsigned int i0; // initial value of i
726  if (spans.size() > 0) {
727  id = spans[0].id;
728  i0 = 0;
729  for (unsigned int i = 0; i <= spans.size(); i++) { // <= size to catch the last object
730  if (i == spans.size() || spans[i].id != id) {
731  bool good = false; // Span includes pixel sufficient to include footprint in set?
732  std::vector<geom::Span> tempSpanList;
733  for (; i0 < i; i0++) {
734  good |= spans[i0].good;
735  tempSpanList.emplace_back(spans[i0].y + row0, spans[i0].x0 + col0, spans[i0].x1 + col0);
736  }
737  auto tempSpanSet = std::make_shared<geom::SpanSet>(std::move(tempSpanList));
738  auto fp = std::make_shared<Footprint>(tempSpanSet, _region);
739 
740  if (good && fp->getArea() >= static_cast<std::size_t>(npixMin)) {
741  _footprints->push_back(fp);
742  }
743  }
744 
745  if (i < spans.size()) {
746  id = spans[i].id;
747  }
748  }
749  }
750  /*
751  * Find all peaks within those Footprints
752  */
753  if (setPeaks) {
754  using fiterator = FootprintSet::FootprintList::iterator;
755  for (auto const &_footprint : *_footprints) {
756  findPeaks(_footprint, img, polarity, ThresholdTraitT());
757  }
758  }
759 }
760 
761 template <typename ImagePixelT>
762 FootprintSet::FootprintSet(image::Image<ImagePixelT> const &img, Threshold const &threshold,
763  int const npixMin, bool const setPeaks)
764  : _footprints(new FootprintList()), _region(img.getBBox()) {
765  using VariancePixelT = float;
766 
767  findFootprints<ImagePixelT, image::MaskPixel, VariancePixelT, ThresholdLevel_traits>(
768  _footprints.get(), _region, img, nullptr, threshold.getValue(img), threshold.getIncludeMultiplier(),
769  threshold.getPolarity(), npixMin, setPeaks);
770 }
771 
772 // NOTE: not a template to appease swig (see note by instantiations at bottom)
773 
774 template <typename MaskPixelT>
775 FootprintSet::FootprintSet(image::Mask<MaskPixelT> const &msk, Threshold const &threshold, int const npixMin)
776  : _footprints(new FootprintList()), _region(msk.getBBox()) {
777  switch (threshold.getType()) {
778  case Threshold::BITMASK:
779  findFootprints<MaskPixelT, MaskPixelT, float, ThresholdBitmask_traits>(
780  _footprints.get(), _region, msk, nullptr, threshold.getValue(),
781  threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin, false);
782  break;
783 
784  case Threshold::VALUE:
785  findFootprints<MaskPixelT, MaskPixelT, float, ThresholdLevel_traits>(
786  _footprints.get(), _region, msk, nullptr, threshold.getValue(),
787  threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin, false);
788  break;
789 
790  default:
791  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
792  "You must specify a numerical threshold value with a Mask");
793  }
794 }
795 
796 template <typename ImagePixelT, typename MaskPixelT>
797 FootprintSet::FootprintSet(const image::MaskedImage<ImagePixelT, MaskPixelT> &maskedImg,
798  Threshold const &threshold, std::string const &planeName, int const npixMin,
799  bool const setPeaks)
800  : _footprints(new FootprintList()),
801  _region(lsst::geom::Point2I(maskedImg.getX0(), maskedImg.getY0()),
802  lsst::geom::Extent2I(maskedImg.getWidth(), maskedImg.getHeight())) {
803  using VariancePixelT = typename image::MaskedImage<ImagePixelT, MaskPixelT>::Variance::Pixel;
804  // Find the Footprints
805  switch (threshold.getType()) {
806  case Threshold::PIXEL_STDEV:
807  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdPixelLevel_traits>(
808  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
809  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
810  npixMin, setPeaks);
811  break;
812  default:
813  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdLevel_traits>(
814  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
815  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
816  npixMin, setPeaks);
817  break;
818  }
819  // Set Mask if requested
820  if (planeName == "") {
821  return;
822  }
823  //
824  // Define the maskPlane
825  //
827  mask->addMaskPlane(planeName);
828 
829  MaskPixelT const bitPlane = mask->getPlaneBitMask(planeName);
830  //
831  // Set the bits where objects are detected
832  //
833  for (auto const &fIter : *_footprints) {
834  fIter->getSpans()->setMask(*(maskedImg.getMask()), bitPlane);
835  }
836 }
837 
838 FootprintSet::FootprintSet(lsst::geom::Box2I region)
839  : _footprints(std::make_shared<FootprintList>()), _region(region) {}
840 
841 FootprintSet::FootprintSet(FootprintSet const &rhs) : _footprints(new FootprintList), _region(rhs._region) {
842  _footprints->reserve(rhs._footprints->size());
843  for (FootprintSet::FootprintList::const_iterator ptr = rhs._footprints->begin(),
844  end = rhs._footprints->end();
845  ptr != end; ++ptr) {
846  _footprints->push_back(std::make_shared<Footprint>(**ptr));
847  }
848 }
849 
850 // Delegate to copy-constructor for backward-compatibility
851 FootprintSet::FootprintSet(FootprintSet &&rhs) : FootprintSet(rhs) {}
852 
853 FootprintSet &FootprintSet::operator=(FootprintSet const &rhs) {
854  FootprintSet tmp(rhs);
855  swap(tmp); // See Meyers, Effective C++, Item 11
856  return *this;
857 }
858 
859 // Delegate to copy-assignment for backward-compatibility
860 FootprintSet &FootprintSet::operator=(FootprintSet &&rhs) { return *this = rhs; }
861 
862 FootprintSet::~FootprintSet() = default;
863 
864 void FootprintSet::merge(FootprintSet const &rhs, int tGrow, int rGrow, bool isotropic) {
865  FootprintControl const ctrl(true, isotropic);
866  FootprintSet fs = mergeFootprintSets(*this, tGrow, rhs, rGrow, ctrl);
867  swap(fs); // Swap the new FootprintSet into place
868 }
869 
870 void FootprintSet::setRegion(lsst::geom::Box2I const &region) {
871  _region = region;
872 
873  for (auto const &ptr : *_footprints) {
874  ptr->setRegion(region);
875  }
876 }
877 
878 FootprintSet::FootprintSet(FootprintSet const &rhs, int r, bool isotropic)
879  : _footprints(new FootprintList), _region(rhs._region) {
880  if (r == 0) {
881  FootprintSet fs = rhs;
882  swap(fs); // Swap the new FootprintSet into place
883  return;
884  } else if (r < 0) {
885  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
886  (boost::format("I cannot grow by negative numbers: %d") % r).str());
887  }
888 
889  FootprintControl const ctrl(true, isotropic);
890  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, r, ctrl);
891  swap(fs); // Swap the new FootprintSet into place
892 }
893 
894 FootprintSet::FootprintSet(FootprintSet const &rhs, int ngrow, FootprintControl const &ctrl)
895  : _footprints(new FootprintList), _region(rhs._region) {
896  if (ngrow == 0) {
897  FootprintSet fs = rhs;
898  swap(fs); // Swap the new FootprintSet into place
899  return;
900  } else if (ngrow < 0) {
901  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
902  str(boost::format("I cannot grow by negative numbers: %d") % ngrow));
903  }
904 
905  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, ngrow, ctrl);
906  swap(fs); // Swap the new FootprintSet into place
907 }
908 
909 FootprintSet::FootprintSet(FootprintSet const &fs1, FootprintSet const &fs2, bool const)
910  : _footprints(new FootprintList()), _region(fs1._region) {
911  _region.include(fs2._region);
912  throw LSST_EXCEPT(pex::exceptions::LogicError, "NOT IMPLEMENTED");
913 }
914 
915 std::shared_ptr<image::Image<FootprintIdPixel>> FootprintSet::insertIntoImage() const {
916  auto im = std::make_shared<image::Image<FootprintIdPixel>>(_region);
917  *im = 0;
918 
919  FootprintIdPixel id = 0;
920  for (auto const &fIter : *_footprints) {
921  id++;
922  fIter->getSpans()->applyFunctor(setIdImage<FootprintIdPixel>(id), *im);
923  }
924 
925  return im;
926 }
927 
928 template <typename ImagePixelT, typename MaskPixelT>
929 void FootprintSet::makeHeavy(image::MaskedImage<ImagePixelT, MaskPixelT> const &mimg,
930  HeavyFootprintCtrl const *ctrl) {
931  HeavyFootprintCtrl ctrl_s = HeavyFootprintCtrl();
932 
933  if (!ctrl) {
934  ctrl = &ctrl_s;
935  }
936 
937  for (FootprintList::iterator ptr = _footprints->begin(), end = _footprints->end(); ptr != end; ++ptr) {
938  ptr->reset(new HeavyFootprint<ImagePixelT, MaskPixelT>(**ptr, mimg, ctrl));
939  }
940 }
941 
942 void FootprintSet::makeSources(afw::table::SourceCatalog &cat) const {
943  for (auto const &i : *_footprints) {
945  r->setFootprint(i);
946  }
947 }
948 
949  //
950  // Explicit instantiations
951  //
952 
953 #ifndef DOXYGEN
954 
955 #define INSTANTIATE(PIXEL) \
956  template FootprintSet::FootprintSet(image::Image<PIXEL> const &, Threshold const &, int const, \
957  bool const); \
958  template FootprintSet::FootprintSet(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
959  Threshold const &, std::string const &, int const, bool const); \
960  template void FootprintSet::makeHeavy(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
961  HeavyFootprintCtrl const *)
962 
963 template FootprintSet::FootprintSet(image::Mask<image::MaskPixel> const &, Threshold const &, int const);
964 
965 template void FootprintSet::setMask(image::Mask<image::MaskPixel> *, std::string const &);
966 template void FootprintSet::setMask(std::shared_ptr<image::Mask<image::MaskPixel>>, std::string const &);
967 
969 INSTANTIATE(int);
970 INSTANTIATE(float);
971 INSTANTIATE(double);
972 } // namespace detection
973 } // namespace afw
974 } // namespace lsst
975 
976 #endif // !DOXYGEN
AmpInfoBoxKey bbox
Definition: Amplifier.cc:117
double element[2]
Definition: BaseTable.cc:90
int end
int max
double x
table::Key< int > id
Definition: Detector.cc:162
#define INSTANTIATE(FROMSYS, TOSYS)
Definition: Detector.cc:484
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition: Exception.h:48
afw::table::Key< afw::table::Array< ImagePixelT > > image
afw::table::Key< afw::table::Array< MaskPixelT > > mask
uint64_t * ptr
Definition: RangeSet.cc:88
int y
Definition: SpanSet.cc:48
table::Key< int > b
table::Key< int > a
T begin(T... args)
T capacity(T... args)
FootprintSet(image::Image< ImagePixelT > const &img, Threshold const &threshold, int const npixMin=1, bool const setPeaks=true)
Find a FootprintSet given an Image and a threshold.
std::vector< std::shared_ptr< Footprint > > FootprintList
The FootprintSet's set of Footprints.
Definition: FootprintSet.h:56
IdSpan(int id, int y, int x0, int x1)
Definition: CR.cc:68
The base class for all image classed (Image, Mask, MaskedImage, ...)
Definition: ImageBase.h:102
int getX0() const
Return the image's column-origin.
Definition: ImageBase.h:306
int getWidth() const
Return the number of columns in the image.
Definition: ImageBase.h:294
int getY0() const
Return the image's row-origin.
Definition: ImageBase.h:314
int getHeight() const
Return the number of rows in the image.
Definition: ImageBase.h:296
x_iterator row_begin(int y) const
Return an x_iterator to the start of the y'th row.
Definition: ImageBase.h:401
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:51
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:77
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:73
VariancePtr getVariance() const
Return a (shared_ptr to) the MaskedImage's variance.
Definition: MaskedImage.h:1051
MaskPtr getMask() const
Return a (shared_ptr to) the MaskedImage's mask.
Definition: MaskedImage.h:1030
ImagePtr getImage() const
Return a (shared_ptr to) the MaskedImage's image.
Definition: MaskedImage.h:1018
size_type size() const
Return the number of elements in the catalog.
Definition: Catalog.h:413
void swap(PolymorphicValue &lhs, PolymorphicValue &rhs) noexcept
Swap specialization for PolymorphicValue.
An integer coordinate rectangle.
Definition: Box.h:55
int getMinY() const noexcept
Definition: Box.h:158
void include(Point2I const &point)
Expand this to ensure that this->contains(point).
Definition: Box.cc:152
int getMinX() const noexcept
Definition: Box.h:157
T emplace_back(T... args)
T empty(T... args)
T fill(T... args)
T fill_n(T... args)
T get(T... args)
T inplace_merge(T... args)
T insert(T... args)
T isnan(T... args)
T left(T... args)
T make_pair(T... args)
T make_shared(T... args)
T max(T... args)
T move(T... args)
int resolve_alias(const std::vector< int > &aliases, int id)
Follow a chain of aliases, returning the final resolved value.
Definition: CR.cc:96
afw::table::CatalogT< PeakRecord > PeakCatalog
Definition: Peak.h:244
std::uint64_t FootprintIdPixel
Pixel type for FootprintSet::insertIntoImage()
Definition: FootprintSet.h:48
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
FilterProperty & operator=(FilterProperty const &)=default
lsst::afw::detection::Footprint Footprint
Definition: Source.h:57
SortedCatalogT< SourceRecord > SourceCatalog
Definition: fwd.h:85
Extent< int, 2 > Extent2I
Definition: Extent.h:397
Point< int, 2 > Point2I
Definition: Point.h:321
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
A base class for image defects.
details::ImageNdGetter< T, inA, inB > ndImage(ndarray::Array< T, inA, inB > const &array, lsst::geom::Point2I xy0=lsst::geom::Point2I())
Marks a ndarray to be interpreted as an image when applying a functor from a SpanSet.
STL namespace.
T push_back(T... args)
T reserve(T... args)
T sort(T... args)
ImageT val
Definition: CR.cc:146
T stable_sort(T... args)