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