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