LSSTApplications  18.1.0
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>
773  int const npixMin, bool const setPeaks)
774  : daf::base::Citizen(typeid(this)), _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  : daf::base::Citizen(typeid(this)), _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  : daf::base::Citizen(typeid(this)),
811  _footprints(new FootprintList()),
812  _region(lsst::geom::Point2I(maskedImg.getX0(), maskedImg.getY0()),
813  lsst::geom::Extent2I(maskedImg.getWidth(), maskedImg.getHeight())) {
814  typedef typename image::MaskedImage<ImagePixelT, MaskPixelT>::Variance::Pixel VariancePixelT;
815  // Find the Footprints
816  switch (threshold.getType()) {
818  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdPixelLevel_traits>(
819  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
820  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
821  npixMin, setPeaks);
822  break;
823  default:
824  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdLevel_traits>(
825  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
826  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
827  npixMin, setPeaks);
828  break;
829  }
830  // Set Mask if requested
831  if (planeName == "") {
832  return;
833  }
834  //
835  // Define the maskPlane
836  //
838  mask->addMaskPlane(planeName);
839 
840  MaskPixelT const bitPlane = mask->getPlaneBitMask(planeName);
841  //
842  // Set the bits where objects are detected
843  //
844  for (auto const &fIter : *_footprints) {
845  fIter->getSpans()->setMask(*(maskedImg.getMask()), bitPlane);
846  }
847 }
848 
850  : daf::base::Citizen(typeid(this)), _footprints(std::make_shared<FootprintList>()), _region(region) {}
851 
852 FootprintSet::FootprintSet(FootprintSet const &rhs)
853  : daf::base::Citizen(typeid(this)), _footprints(new FootprintList), _region(rhs._region) {
854  _footprints->reserve(rhs._footprints->size());
855  for (FootprintSet::FootprintList::const_iterator ptr = rhs._footprints->begin(),
856  end = rhs._footprints->end();
857  ptr != end; ++ptr) {
858  _footprints->push_back(std::make_shared<Footprint>(**ptr));
859  }
860 }
861 
862 // Delegate to copy-constructor for backward-compatibility
863 FootprintSet::FootprintSet(FootprintSet &&rhs) : FootprintSet(rhs) {}
864 
865 FootprintSet &FootprintSet::operator=(FootprintSet const &rhs) {
866  FootprintSet tmp(rhs);
867  swap(tmp); // See Meyers, Effective C++, Item 11
868  return *this;
869 }
870 
871 // Delegate to copy-assignment for backward-compatibility
872 FootprintSet &FootprintSet::operator=(FootprintSet &&rhs) { return *this = rhs; }
873 
874 FootprintSet::~FootprintSet() = default;
875 
876 void FootprintSet::merge(FootprintSet const &rhs, int tGrow, int rGrow, bool isotropic) {
877  FootprintControl const ctrl(true, isotropic);
878  FootprintSet fs = mergeFootprintSets(*this, tGrow, rhs, rGrow, ctrl);
879  swap(fs); // Swap the new FootprintSet into place
880 }
881 
882 void FootprintSet::setRegion(lsst::geom::Box2I const &region) {
883  _region = region;
884 
885  for (FootprintSet::FootprintList::iterator ptr = _footprints->begin(), end = _footprints->end();
886  ptr != end; ++ptr) {
887  (*ptr)->setRegion(region);
888  }
889 }
890 
891 FootprintSet::FootprintSet(FootprintSet const &rhs, int r, bool isotropic)
892  : daf::base::Citizen(typeid(this)), _footprints(new FootprintList), _region(rhs._region) {
893  if (r == 0) {
894  FootprintSet fs = rhs;
895  swap(fs); // Swap the new FootprintSet into place
896  return;
897  } else if (r < 0) {
898  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
899  (boost::format("I cannot grow by negative numbers: %d") % r).str());
900  }
901 
902  FootprintControl const ctrl(true, isotropic);
903  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, r, ctrl);
904  swap(fs); // Swap the new FootprintSet into place
905 }
906 
907 FootprintSet::FootprintSet(FootprintSet const &rhs, int ngrow, FootprintControl const &ctrl)
908  : daf::base::Citizen(typeid(this)), _footprints(new FootprintList), _region(rhs._region) {
909  if (ngrow == 0) {
910  FootprintSet fs = rhs;
911  swap(fs); // Swap the new FootprintSet into place
912  return;
913  } else if (ngrow < 0) {
914  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
915  str(boost::format("I cannot grow by negative numbers: %d") % ngrow));
916  }
917 
918  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, ngrow, ctrl);
919  swap(fs); // Swap the new FootprintSet into place
920 }
921 
922 FootprintSet::FootprintSet(FootprintSet const &fs1, FootprintSet const &fs2, bool const)
923  : daf::base::Citizen(typeid(this)), _footprints(new FootprintList()), _region(fs1._region) {
924  _region.include(fs2._region);
925  throw LSST_EXCEPT(pex::exceptions::LogicError, "NOT IMPLEMENTED");
926 }
927 
929  auto im = std::make_shared<image::Image<FootprintIdPixel>>(_region);
930  *im = 0;
931 
932  FootprintIdPixel id = 0;
933  for (auto const &fIter : *_footprints) {
934  if (relativeIDs) {
935  id++;
936  } else {
937  id = fIter->getId();
938  }
939 
940  fIter->getSpans()->applyFunctor(setIdImage<FootprintIdPixel>(id), *im);
941  }
942 
943  return im;
944 }
945 
946 template <typename ImagePixelT, typename MaskPixelT>
948  HeavyFootprintCtrl const *ctrl) {
949  HeavyFootprintCtrl ctrl_s = HeavyFootprintCtrl();
950 
951  if (!ctrl) {
952  ctrl = &ctrl_s;
953  }
954 
955  for (FootprintList::iterator ptr = _footprints->begin(), end = _footprints->end(); ptr != end; ++ptr) {
956  ptr->reset(new HeavyFootprint<ImagePixelT, MaskPixelT>(**ptr, mimg, ctrl));
957  }
958 }
959 
961  for (FootprintList::const_iterator i = _footprints->begin(); i != _footprints->end(); ++i) {
963  r->setFootprint(*i);
964  }
965 }
966 
967 //
968 // Explicit instantiations
969 //
970 
971 #ifndef DOXYGEN
972 
973 #define INSTANTIATE(PIXEL) \
974  template FootprintSet::FootprintSet(image::Image<PIXEL> const &, Threshold const &, int const, \
975  bool const); \
976  template FootprintSet::FootprintSet(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
977  Threshold const &, std::string const &, int const, bool const); \
978  template void FootprintSet::makeHeavy(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
979  HeavyFootprintCtrl const *)
980 
981 template FootprintSet::FootprintSet(image::Mask<image::MaskPixel> const &, Threshold const &, int const);
982 
985 
987 INSTANTIATE(int);
988 INSTANTIATE(float);
989 INSTANTIATE(double);
990 } // namespace detection
991 } // namespace afw
992 } // namespace lsst
993 
994 #endif // !DOXYGEN
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:1091
uint64_t * ptr
Definition: RangeSet.cc:88
T empty(T... args)
T inplace_merge(T... args)
T stable_sort(T... args)
std::shared_ptr< image::Image< FootprintIdPixel > > insertIntoImage(const bool relativeIDs) const
Return an Image with pixels set to the Footprints in the FootprintSet.
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:316
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:166
int getX0() const
Return the image&#39;s column-origin.
Definition: ImageBase.h:324
void setMask(image::Mask< MaskPixelT > *mask, std::string const &planeName)
Definition: FootprintSet.h:201
The base class for all image classed (Image, Mask, MaskedImage, ...)
Definition: ImageBase.h:103
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:419
ImagePtr getImage() const
Return a (shared_ptr to) the MaskedImage&#39;s image.
Definition: MaskedImage.h:1058
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:78
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
A class to manipulate images, masks, and variance as a single object.
Definition: MaskedImage.h:74
T capacity(T... args)
T make_pair(T... args)
int max
table::Box2IKey bbox
Definition: Detector.cc:169
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:1070
int getMinX() const noexcept
Definition: Box.h:144
void swap(Image< PixelT > &a, Image< PixelT > &b)
Definition: Image.cc:471
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
afw::table::Key< afw::table::Array< MaskPixelT > > mask
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:332
Citizen(const std::type_info &)
Definition: Citizen.cc:163
T begin(T... args)
void include(Point2I const &point)
Expand this to ensure that this->contains(point).
Definition: Box.cc:157
SortedCatalogT< SourceRecord > SourceCatalog
Definition: fwd.h:85
int getWidth() const
Return the number of columns in the image.
Definition: ImageBase.h:314
lsst::afw::detection::Footprint Footprint
Definition: Source.h:61
afw::table::Key< afw::table::Array< ImagePixelT > > image
int resolve_alias(const std::vector< int > &aliases, int id)
Follow a chain of aliases, returning the final resolved value.
Definition: CR.cc:96
void swap(FootprintSet &rhs) noexcept
Definition: FootprintSet.h:140
T fill_n(T... args)
T isnan(T... args)
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:275
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)
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:54
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:59
int end
int getMinY() const noexcept
Definition: Box.h:145
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:349
T reserve(T... args)
std::uint64_t FootprintIdPixel
Pixel type for FootprintSet::insertIntoImage()
Definition: FootprintSet.h:48
T emplace_back(T... args)