lsst.afw  22.0.1-31-gd62ef0f05+23bd69c089
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 using IdPixelT = std::uint64_t; // 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(nullptr),
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  using FootprintList = FootprintSet::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  using OldIdMap = std::map<int, std::set<std::uint64_t>>;
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.emplace_back(dy, 0, 0);
281  }
282  for (auto dy = -1; dy >= -bottom; --dy) {
283  spanList.emplace_back(dy, 0, 0);
284  }
285  spanList.emplace_back(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) {
296  std::shared_ptr<Footprint> foot = *ptr;
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)) {
316  std::shared_ptr<Footprint> foot = *ptr;
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 (const auto& foot : *fs.getFootprints()) {
346  // find the (mangled) [lr]hsFootprint IDs that contribute to foot
347  foot->getSpans()->applyFunctor(idFinder, *idImage);
348 
349  std::set<std::uint64_t> lhsFootprintIndxs, rhsFootprintIndxs; // indexes into [lr]hsFootprints
350 
351  for (unsigned int indx : idFinder.getIds()) {
352  if ((indx & lhsIdMask) > 0) {
353  std::uint64_t i = (indx & lhsIdMask) - 1;
354  lhsFootprintIndxs.insert(i);
355  /*
356  * Now allow for Footprints that vanished beneath this one
357  */
358  OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
359  if (mapPtr != overwrittenIds.end()) {
360  std::set<std::uint64_t> &overwritten = mapPtr->second;
361 
362  for (unsigned long ptr : overwritten) {
363  lhsFootprintIndxs.insert((ptr & lhsIdMask) - 1);
364  }
365  }
366  }
367  indx >>= lhsIdNbit;
368 
369  if (indx > 0) {
370  std::uint64_t i = indx - 1;
371  rhsFootprintIndxs.insert(i);
372  /*
373  * Now allow for Footprints that vanished beneath this one
374  */
375  OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
376  if (mapPtr != overwrittenIds.end()) {
377  std::set<std::uint64_t> &overwritten = mapPtr->second;
378 
379  for (unsigned long ptr : overwritten) {
380  rhsFootprintIndxs.insert(ptr - 1);
381  }
382  }
383  }
384  }
385  /*
386  * We now have a complete set of Footprints that contributed to this one, so merge
387  * all their Peaks into the new one
388  */
389  PeakCatalog &peaks = foot->getPeaks();
390 
391  for (unsigned long i : lhsFootprintIndxs) {
392  assert(i < lhsFootprints.size());
393  PeakCatalog const &oldPeaks = lhsFootprints[i]->getPeaks();
394 
395  int const nold = peaks.size();
396  peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
397  // We use getInternal() here to get the vector of shared_ptr that Catalog uses internally,
398  // which causes the STL algorithm to copy pointers instead of PeakRecords (which is what
399  // it'd try to do if we passed Catalog's own iterators).
400  std::inplace_merge(peaks.getInternal().begin(), peaks.getInternal().begin() + nold,
401  peaks.getInternal().end(), SortPeaks());
402  }
403 
404  for (unsigned long i : rhsFootprintIndxs) {
405  assert(i < rhsFootprints.size());
406  PeakCatalog const &oldPeaks = rhsFootprints[i]->getPeaks();
407 
408  int const nold = peaks.size();
409  peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
410  // See note above on why we're using getInternal() here.
411  std::inplace_merge(peaks.getInternal().begin(), peaks.getInternal().begin() + nold,
412  peaks.getInternal().end(), SortPeaks());
413  }
414  idFinder.reset();
415  }
416 
417  return fs;
418 }
419 /*
420  * run-length code for part of object
421  */
422 class IdSpan {
423 public:
424  explicit IdSpan(int id, int y, int x0, int x1, double good) : id(id), y(y), x0(x0), x1(x1), good(good) {}
425  int id; /* ID for object */
426  int y; /* Row wherein IdSpan dwells */
427  int x0, x1; /* inclusive range of columns */
428  bool good; /* includes a value over the desired threshold? */
429 };
430 /*
431  * comparison functor; sort by ID then row
432  */
433 struct IdSpanCompare {
434  bool operator()(IdSpan const &a, IdSpan const &b) const {
435  if (a.id < b.id) {
436  return true;
437  } else if (a.id > b.id) {
438  return false;
439  } else {
440  return (a.y < b.y) ? true : false;
441  }
442  }
443 };
444 /*
445  * Follow a chain of aliases, returning the final resolved value.
446  */
447 int resolve_alias(std::vector<int> const &aliases, /* list of aliases */
448  int id) { /* alias to look up */
449  int resolved = id; /* resolved alias */
450 
451  while (id != aliases[id]) {
452  resolved = id = aliases[id];
453  }
454 
455  return (resolved);
456 }
458 } // namespace
459 
460 namespace {
461 template <typename ImageT>
462 void findPeaksInFootprint(ImageT const &image, bool polarity, PeakCatalog &peaks, Footprint &foot,
463  std::size_t const margin = 0) {
464  auto spanSet = foot.getSpans();
465  if (spanSet->size() == 0) {
466  return;
467  }
468  auto bbox = image.getBBox();
469  for (auto const &spanIter : *spanSet) {
470  auto y = spanIter.getY() - image.getY0();
471  if (static_cast<std::size_t>(y + image.getY0()) < bbox.getMinY() + margin ||
472  static_cast<std::size_t>(y + image.getY0()) > bbox.getMaxY() - margin) {
473  continue;
474  }
475  for (auto x = spanIter.getMinX() - image.getX0(); x <= spanIter.getMaxX() - image.getX0(); ++x) {
476  if (static_cast<std::size_t>(x + image.getX0()) < (bbox.getMinX() + margin) ||
477  static_cast<std::size_t>(x + image.getX0()) > (bbox.getMaxX() - margin)) {
478  continue;
479  }
480  auto val = image(x, y);
481  if (polarity) { // look for +ve peaks
482  if (image(x - 1, y + 1) > val || image(x, y + 1) > val || image(x + 1, y + 1) > val ||
483  image(x - 1, y) > val || image(x + 1, y) > val || image(x - 1, y - 1) > val ||
484  image(x, y - 1) > val || image(x + 1, y - 1) > val) {
485  continue;
486  }
487  } else { // look for -ve "peaks" (pits)
488  if (image(x - 1, y + 1) < val || image(x, y + 1) < val || image(x + 1, y + 1) < val ||
489  image(x - 1, y) < val || image(x + 1, y) < val || image(x - 1, y - 1) < val ||
490  image(x, y - 1) < val || image(x + 1, y - 1) < val) {
491  continue;
492  }
493  }
494 
495  foot.addPeak(x + image.getX0(), y + image.getY0(), val);
496  }
497  }
498 }
499 
500 template <typename ImageT>
501 class FindMaxInFootprint {
502 public:
503  explicit FindMaxInFootprint(bool polarity)
504  : _polarity(polarity),
505  _x(0),
506  _y(0),
507  _min(std::numeric_limits<double>::max()),
508  _max(-std::numeric_limits<double>::max()) {}
509 
510  void operator()(lsst::geom::Point2I const &point, ImageT const &val) {
511  if (_polarity) {
512  if (val > _max) {
513  _max = val;
514  _x = point.getX();
515  _y = point.getY();
516  }
517  } else {
518  if (val < _min) {
519  _min = val;
520  _x = point.getX();
521  _y = point.getY();
522  }
523  }
524  }
525 
526  void addRecord(Footprint &foot) const { foot.addPeak(_x, _y, _polarity ? _max : _min); }
527 
528 private:
529  bool _polarity;
530  int _x, _y;
531  double _min, _max;
532 };
533 
534 template <typename ImageT, typename ThresholdT>
535 void findPeaks(std::shared_ptr<Footprint> foot, ImageT const &img, bool polarity, ThresholdT) {
536  findPeaksInFootprint(img, polarity, foot->getPeaks(), *foot, 1);
537 
538  // We use getInternal() here to get the vector of shared_ptr that Catalog uses internally,
539  // which causes the STL algorithm to copy pointers instead of PeakRecords (which is what
540  // it'd try to do if we passed Catalog's own iterators).
541  std::stable_sort(foot->getPeaks().getInternal().begin(), foot->getPeaks().getInternal().end(),
542  SortPeaks());
543 
544  if (foot->getPeaks().empty()) {
545  FindMaxInFootprint<typename ImageT::Pixel> maxFinder(polarity);
546  foot->getSpans()->applyFunctor(maxFinder, ndarray::ndImage(img.getArray(), img.getXY0()));
547  maxFinder.addRecord(*foot);
548  }
549 }
550 
551 // No need to search for peaks when processing a Mask
552 template <typename ImageT>
553 void findPeaks(std::shared_ptr<Footprint>, ImageT const &, bool, ThresholdBitmask_traits) {
554  ;
555 }
556 } // namespace
557 
558 /*
559  * Functions to determine if a pixel's in a Footprint
560  */
561 template <typename ImagePixelT, typename IterT>
562 static inline bool inFootprint(ImagePixelT pixVal, IterT, bool polarity, double thresholdVal,
563  ThresholdLevel_traits) {
564  return (polarity ? pixVal : -pixVal) >= thresholdVal;
565 }
566 
567 template <typename ImagePixelT, typename IterT>
568 static inline bool inFootprint(ImagePixelT pixVal, IterT var, bool polarity, double thresholdVal,
569  ThresholdPixelLevel_traits) {
570  return (polarity ? pixVal : -pixVal) >= thresholdVal * ::sqrt(*var);
571 }
572 
573 template <typename ImagePixelT, typename IterT>
574 static inline bool inFootprint(ImagePixelT pixVal, IterT, bool, double thresholdVal,
575  ThresholdBitmask_traits) {
576  return (pixVal & static_cast<long>(thresholdVal));
577 }
578 
579 /*
580  * Advance the x_iterator to the variance image, when relevant (it may be NULL otherwise)
581  */
582 template <typename IterT>
583 static inline IterT advancePtr(IterT varPtr, Threshold_traits) {
584  return varPtr;
585 }
586 
587 template <typename IterT>
588 static inline IterT advancePtr(IterT varPtr, ThresholdPixelLevel_traits) {
589  return varPtr + 1;
590 }
591 
592 /*
593  * Here's the working routine for the FootprintSet constructors; see documentation
594  * of the constructors themselves
595  */
596 template <typename ImagePixelT, typename MaskPixelT, typename VariancePixelT, typename ThresholdTraitT>
597 static void findFootprints(
598  typename FootprintSet::FootprintList *_footprints, // Footprints
599  lsst::geom::Box2I const &_region, // BBox of pixels that are being searched
600  image::ImageBase<ImagePixelT> const &img, // Image to search for objects
601  image::Image<VariancePixelT> const *var, // img's variance
602  double const footprintThreshold, // threshold value for footprint
603  double const includeThresholdMultiplier, // threshold (relative to footprintThreshold) for inclusion
604  bool const polarity, // if false, search _below_ thresholdVal
605  int const npixMin, // minimum number of pixels in an object
606  bool const setPeaks // should I set the Peaks list?
607 ) {
608  int id; /* object ID */
609  int in_span; /* object ID of current IdSpan */
610  int nobj = 0; /* number of objects found */
611  int x0 = 0; /* unpacked from a IdSpan */
612 
613  double includeThreshold = footprintThreshold * includeThresholdMultiplier; // Threshold for inclusion
614 
615  int const row0 = img.getY0();
616  int const col0 = img.getX0();
617  int const height = img.getHeight();
618  int const width = img.getWidth();
619  /*
620  * Storage for arrays that identify objects by ID. We want to be able to
621  * refer to idp[-1] and idp[width], hence the (width + 2)
622  */
623  std::vector<int> id1(width + 2);
624  std::fill(id1.begin(), id1.end(), 0);
625  std::vector<int> id2(width + 2);
626  std::fill(id2.begin(), id2.end(), 0);
627  std::vector<int>::iterator idc = id1.begin() + 1; // object IDs in current/
628  std::vector<int>::iterator idp = id2.begin() + 1; // previous row
629 
630  std::vector<int> aliases; // aliases for initially disjoint parts of Footprints
631  aliases.reserve(1 + height / 20); // initial size of aliases
632 
633  std::vector<IdSpan> spans; // y:x0,x1 for objects
634  spans.reserve(aliases.capacity()); // initial size of spans
635 
636  aliases.push_back(0); // 0 --> 0
637  /*
638  * Go through image identifying objects
639  */
640  using x_iterator = typename image::Image<ImagePixelT>::x_iterator;
641  using x_var_iterator = typename image::Image<VariancePixelT>::x_iterator;
642 
643  in_span = 0; // not in a span
644  for (int y = 0; y != height; ++y) {
645  if (idc == id1.begin() + 1) {
646  idc = id2.begin() + 1;
647  idp = id1.begin() + 1;
648  } else {
649  idc = id1.begin() + 1;
650  idp = id2.begin() + 1;
651  }
652  std::fill_n(idc - 1, width + 2, 0);
653 
654  in_span = 0; /* not in a span */
655  bool good = (includeThresholdMultiplier == 1.0); /* Span exceeds the threshold? */
656 
657  x_iterator pixPtr = img.row_begin(y);
658  x_var_iterator varPtr = (var == nullptr) ? nullptr : var->row_begin(y);
659  for (int x = 0; x < width; ++x, ++pixPtr, varPtr = advancePtr(varPtr, ThresholdTraitT())) {
660  ImagePixelT const pixVal = *pixPtr;
661 
662  if (isBadPixel(pixVal) ||
663  !inFootprint(pixVal, varPtr, polarity, footprintThreshold, ThresholdTraitT())) {
664  if (in_span) {
665  spans.emplace_back(in_span, y, x0, x - 1, good);
666 
667  in_span = 0;
668  good = false;
669  }
670  } else { /* a pixel to fix */
671  if (idc[x - 1] != 0) {
672  id = idc[x - 1];
673  } else if (idp[x - 1] != 0) {
674  id = idp[x - 1];
675  } else if (idp[x] != 0) {
676  id = idp[x];
677  } else if (idp[x + 1] != 0) {
678  id = idp[x + 1];
679  } else {
680  id = ++nobj;
681  aliases.push_back(id);
682  }
683 
684  idc[x] = id;
685  if (!in_span) {
686  x0 = x;
687  in_span = id;
688  }
689  /*
690  * Do we need to merge ID numbers? If so, make suitable entries in aliases[]
691  */
692  if (idp[x + 1] != 0 && idp[x + 1] != id) {
693  aliases[resolve_alias(aliases, idp[x + 1])] = resolve_alias(aliases, id);
694 
695  idc[x] = id = idp[x + 1];
696  }
697 
698  if (!good && inFootprint(pixVal, varPtr, polarity, includeThreshold, ThresholdTraitT())) {
699  good = true;
700  }
701  }
702  }
703 
704  if (in_span) {
705  spans.emplace_back(in_span, y, x0, width - 1, good);
706  }
707  }
708  /*
709  * Resolve aliases; first alias chains, then the IDs in the spans
710  */
711  for (auto & span : spans) {
712  span.id = resolve_alias(aliases, span.id);
713  }
714  /*
715  * Sort spans by ID, so we can sweep through them once
716  */
717  if (spans.size() > 0) {
718  std::sort(spans.begin(), spans.end(), IdSpanCompare());
719  }
720  /*
721  * Build Footprints from spans
722  */
723  unsigned int i0; // initial value of i
724  if (spans.size() > 0) {
725  id = spans[0].id;
726  i0 = 0;
727  for (unsigned int i = 0; i <= spans.size(); i++) { // <= size to catch the last object
728  if (i == spans.size() || spans[i].id != id) {
729  bool good = false; // Span includes pixel sufficient to include footprint in set?
730  std::vector<geom::Span> tempSpanList;
731  for (; i0 < i; i0++) {
732  good |= spans[i0].good;
733  tempSpanList.emplace_back(spans[i0].y + row0, spans[i0].x0 + col0, spans[i0].x1 + col0);
734  }
735  auto tempSpanSet = std::make_shared<geom::SpanSet>(std::move(tempSpanList));
736  auto fp = std::make_shared<Footprint>(tempSpanSet, _region);
737 
738  if (good && fp->getArea() >= static_cast<std::size_t>(npixMin)) {
739  _footprints->push_back(fp);
740  }
741  }
742 
743  if (i < spans.size()) {
744  id = spans[i].id;
745  }
746  }
747  }
748  /*
749  * Find all peaks within those Footprints
750  */
751  if (setPeaks) {
752  using fiterator = FootprintSet::FootprintList::iterator;
753  for (auto const &_footprint : *_footprints) {
754  findPeaks(_footprint, img, polarity, ThresholdTraitT());
755  }
756  }
757 }
758 
759 template <typename ImagePixelT>
760 FootprintSet::FootprintSet(image::Image<ImagePixelT> const &img, Threshold const &threshold,
761  int const npixMin, bool const setPeaks)
762  : _footprints(new FootprintList()), _region(img.getBBox()) {
763  using VariancePixelT = float;
764 
765  findFootprints<ImagePixelT, image::MaskPixel, VariancePixelT, ThresholdLevel_traits>(
766  _footprints.get(), _region, img, nullptr, threshold.getValue(img), threshold.getIncludeMultiplier(),
767  threshold.getPolarity(), npixMin, setPeaks);
768 }
769 
770 // NOTE: not a template to appease swig (see note by instantiations at bottom)
771 
772 template <typename MaskPixelT>
773 FootprintSet::FootprintSet(image::Mask<MaskPixelT> const &msk, Threshold const &threshold, int const npixMin)
774  : _footprints(new FootprintList()), _region(msk.getBBox()) {
775  switch (threshold.getType()) {
776  case Threshold::BITMASK:
777  findFootprints<MaskPixelT, MaskPixelT, float, ThresholdBitmask_traits>(
778  _footprints.get(), _region, msk, nullptr, threshold.getValue(),
779  threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin, false);
780  break;
781 
782  case Threshold::VALUE:
783  findFootprints<MaskPixelT, MaskPixelT, float, ThresholdLevel_traits>(
784  _footprints.get(), _region, msk, nullptr, threshold.getValue(),
785  threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin, false);
786  break;
787 
788  default:
789  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
790  "You must specify a numerical threshold value with a Mask");
791  }
792 }
793 
794 template <typename ImagePixelT, typename MaskPixelT>
795 FootprintSet::FootprintSet(const image::MaskedImage<ImagePixelT, MaskPixelT> &maskedImg,
796  Threshold const &threshold, std::string const &planeName, int const npixMin,
797  bool const setPeaks)
798  : _footprints(new FootprintList()),
799  _region(lsst::geom::Point2I(maskedImg.getX0(), maskedImg.getY0()),
800  lsst::geom::Extent2I(maskedImg.getWidth(), maskedImg.getHeight())) {
801  using VariancePixelT = typename image::MaskedImage<ImagePixelT, MaskPixelT>::Variance::Pixel;
802  // Find the Footprints
803  switch (threshold.getType()) {
804  case Threshold::PIXEL_STDEV:
805  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdPixelLevel_traits>(
806  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
807  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
808  npixMin, setPeaks);
809  break;
810  default:
811  findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdLevel_traits>(
812  _footprints.get(), _region, *maskedImg.getImage(), maskedImg.getVariance().get(),
813  threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
814  npixMin, setPeaks);
815  break;
816  }
817  // Set Mask if requested
818  if (planeName == "") {
819  return;
820  }
821  //
822  // Define the maskPlane
823  //
825  mask->addMaskPlane(planeName);
826 
827  MaskPixelT const bitPlane = mask->getPlaneBitMask(planeName);
828  //
829  // Set the bits where objects are detected
830  //
831  for (auto const &fIter : *_footprints) {
832  fIter->getSpans()->setMask(*(maskedImg.getMask()), bitPlane);
833  }
834 }
835 
836 FootprintSet::FootprintSet(lsst::geom::Box2I region)
837  : _footprints(std::make_shared<FootprintList>()), _region(region) {}
838 
839 FootprintSet::FootprintSet(FootprintSet const &rhs) : _footprints(new FootprintList), _region(rhs._region) {
840  _footprints->reserve(rhs._footprints->size());
841  for (FootprintSet::FootprintList::const_iterator ptr = rhs._footprints->begin(),
842  end = rhs._footprints->end();
843  ptr != end; ++ptr) {
844  _footprints->push_back(std::make_shared<Footprint>(**ptr));
845  }
846 }
847 
848 // Delegate to copy-constructor for backward-compatibility
849 FootprintSet::FootprintSet(FootprintSet &&rhs) : FootprintSet(rhs) {}
850 
851 FootprintSet &FootprintSet::operator=(FootprintSet const &rhs) {
852  FootprintSet tmp(rhs);
853  swap(tmp); // See Meyers, Effective C++, Item 11
854  return *this;
855 }
856 
857 // Delegate to copy-assignment for backward-compatibility
858 FootprintSet &FootprintSet::operator=(FootprintSet &&rhs) { return *this = rhs; }
859 
860 FootprintSet::~FootprintSet() = default;
861 
862 void FootprintSet::merge(FootprintSet const &rhs, int tGrow, int rGrow, bool isotropic) {
863  FootprintControl const ctrl(true, isotropic);
864  FootprintSet fs = mergeFootprintSets(*this, tGrow, rhs, rGrow, ctrl);
865  swap(fs); // Swap the new FootprintSet into place
866 }
867 
868 void FootprintSet::setRegion(lsst::geom::Box2I const &region) {
869  _region = region;
870 
871  for (auto const &ptr : *_footprints) {
872  ptr->setRegion(region);
873  }
874 }
875 
876 FootprintSet::FootprintSet(FootprintSet const &rhs, int r, bool isotropic)
877  : _footprints(new FootprintList), _region(rhs._region) {
878  if (r == 0) {
879  FootprintSet fs = rhs;
880  swap(fs); // Swap the new FootprintSet into place
881  return;
882  } else if (r < 0) {
883  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
884  (boost::format("I cannot grow by negative numbers: %d") % r).str());
885  }
886 
887  FootprintControl const ctrl(true, isotropic);
888  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, r, ctrl);
889  swap(fs); // Swap the new FootprintSet into place
890 }
891 
892 FootprintSet::FootprintSet(FootprintSet const &rhs, int ngrow, FootprintControl const &ctrl)
893  : _footprints(new FootprintList), _region(rhs._region) {
894  if (ngrow == 0) {
895  FootprintSet fs = rhs;
896  swap(fs); // Swap the new FootprintSet into place
897  return;
898  } else if (ngrow < 0) {
899  throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
900  str(boost::format("I cannot grow by negative numbers: %d") % ngrow));
901  }
902 
903  FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, ngrow, ctrl);
904  swap(fs); // Swap the new FootprintSet into place
905 }
906 
907 FootprintSet::FootprintSet(FootprintSet const &fs1, FootprintSet const &fs2, bool const)
908  : _footprints(new FootprintList()), _region(fs1._region) {
909  _region.include(fs2._region);
910  throw LSST_EXCEPT(pex::exceptions::LogicError, "NOT IMPLEMENTED");
911 }
912 
913 std::shared_ptr<image::Image<FootprintIdPixel>> FootprintSet::insertIntoImage() const {
914  auto im = std::make_shared<image::Image<FootprintIdPixel>>(_region);
915  *im = 0;
916 
917  FootprintIdPixel id = 0;
918  for (auto const &fIter : *_footprints) {
919  id++;
920  fIter->getSpans()->applyFunctor(setIdImage<FootprintIdPixel>(id), *im);
921  }
922 
923  return im;
924 }
925 
926 template <typename ImagePixelT, typename MaskPixelT>
927 void FootprintSet::makeHeavy(image::MaskedImage<ImagePixelT, MaskPixelT> const &mimg,
928  HeavyFootprintCtrl const *ctrl) {
929  HeavyFootprintCtrl ctrl_s = HeavyFootprintCtrl();
930 
931  if (!ctrl) {
932  ctrl = &ctrl_s;
933  }
934 
935  for (FootprintList::iterator ptr = _footprints->begin(), end = _footprints->end(); ptr != end; ++ptr) {
936  ptr->reset(new HeavyFootprint<ImagePixelT, MaskPixelT>(**ptr, mimg, ctrl));
937  }
938 }
939 
940 void FootprintSet::makeSources(afw::table::SourceCatalog &cat) const {
941  for (auto const &i : *_footprints) {
943  r->setFootprint(i);
944  }
945 }
946 
947  //
948  // Explicit instantiations
949  //
950 
951 #ifndef DOXYGEN
952 
953 #define INSTANTIATE(PIXEL) \
954  template FootprintSet::FootprintSet(image::Image<PIXEL> const &, Threshold const &, int const, \
955  bool const); \
956  template FootprintSet::FootprintSet(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
957  Threshold const &, std::string const &, int const, bool const); \
958  template void FootprintSet::makeHeavy(image::MaskedImage<PIXEL, image::MaskPixel> const &, \
959  HeavyFootprintCtrl const *)
960 
961 template FootprintSet::FootprintSet(image::Mask<image::MaskPixel> const &, Threshold const &, int const);
962 
963 template void FootprintSet::setMask(image::Mask<image::MaskPixel> *, std::string const &);
964 template void FootprintSet::setMask(std::shared_ptr<image::Mask<image::MaskPixel>>, std::string const &);
965 
967 INSTANTIATE(int);
968 INSTANTIATE(float);
969 INSTANTIATE(double);
970 } // namespace detection
971 } // namespace afw
972 } // namespace lsst
973 
974 #endif // !DOXYGEN
AmpInfoBoxKey bbox
Definition: Amplifier.cc:117
double element[2]
Definition: BaseTable.cc:91
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,...)
afw::table::Key< afw::table::Array< ImagePixelT > > image
afw::table::Key< afw::table::Array< MaskPixelT > > mask
int y
Definition: SpanSet.cc:49
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
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:58
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.
int getMinY() const noexcept
void include(Point2I const &point)
int getMinX() const noexcept
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)
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:59
SortedCatalogT< SourceRecord > SourceCatalog
Definition: fwd.h:85
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)
T stable_sort(T... args)