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