53#include "boost/format.hpp"
71struct Threshold_traits {};
72struct ThresholdLevel_traits :
public Threshold_traits {
74struct ThresholdPixelLevel_traits :
public Threshold_traits {
76struct ThresholdBitmask_traits :
public Threshold_traits {
79template <
typename PixelT>
82 explicit setIdImage(
std::uint64_t const id,
bool overwriteId =
false,
long const idMask = 0x0)
85 _withSetReplace(false),
86 _overwriteId(overwriteId),
91 pex::exceptions::InvalidParameterError,
92 str(boost::format(
"Id 0x%x sets bits in the protected mask 0x%x") % _id % _idMask));
97 long const idMask = 0x0)
100 _withSetReplace(true),
101 _overwriteId(overwriteId),
103 _pos(oldIds->
begin()) {
106 pex::exceptions::InvalidParameterError,
107 str(boost::format(
"Id 0x%x sets bits in the protected mask 0x%x") % _id % _idMask));
113 auto val = input & ~_idMask;
115 if (
val != 0 && _withSetReplace) {
116 _pos = _oldIds->insert(_pos,
val);
119 input = (input & _idMask) + _id;
128 bool _withSetReplace;
139inline bool isBadPixel(T) {
144inline bool isBadPixel(
float val) {
149inline bool isBadPixel(
double val) {
156int nbit(
unsigned long i) {
171class FindIdsInFootprint {
173 explicit FindIdsInFootprint() : _ids(), _old(0) {}
201 if (
a->getPeakValue() !=
b->getPeakValue()) {
202 return (
a->getPeakValue() >
b->getPeakValue());
205 if (
a->getIx() !=
b->getIx()) {
206 return (
a->getIx() <
b->getIx());
209 return (
a->getIy() <
b->getIy());
215FootprintSet mergeFootprintSets(FootprintSet
const &lhs,
217 FootprintSet
const &rhs,
219 FootprintControl
const &ctrl
223 bool const circular = ctrl.isCircular().first && ctrl.isCircular().second;
224 bool const isotropic = ctrl.isIsotropic().second;
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;
232 if (region != rhs.getRegion()) {
233 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
234 boost::format(
"The two FootprintSets must have the same region").
str());
237 auto idImage = std::make_shared<image::Image<IdPixelT>>(region);
241 FootprintList
const &lhsFootprints = *lhs.getFootprints();
242 FootprintList
const &rhsFootprints = *rhs.getFootprints();
243 int const nLhs = lhsFootprints.size();
244 int const nRhs = rhsFootprints.size();
246 if (nLhs > 0 && nRhs > 0 &&
247 lhsFootprints[0]->getPeaks().getSchema() != rhsFootprints[0]->getPeaks().getSchema()) {
249 "FootprintSets to be merged must have identical peak schemas.");
256 int const lhsIdNbit = nbit(nLhs);
257 int const lhsIdMask = (lhsIdNbit == 0) ? 0x0 : (1 << lhsIdNbit) - 1;
261 (boost::format(
"%d + %d footprints need too many bits; change IdPixelT typedef") %
271 OldIdMap overwrittenIds;
273 auto grower = [&circular, &up, &down, &
left, &
right, &isotropic](
277 auto tmpFoot = std::make_shared<Footprint>(foot->getSpans()->dilated(amount,
element),
281 int top = up ? amount : 0;
282 int bottom = down ? amount : 0;
283 int lLimit =
left ? amount : 0;
284 int rLimit =
right ? amount : 0;
286 auto yRange = top + bottom + 1;
290 for (
auto dy = 1; dy <= top; ++dy) {
293 for (
auto dy = -1; dy >= -bottom; --dy) {
297 geom::SpanSet structure(
std::move(spanList));
299 std::make_shared<Footprint>(foot->getSpans()->dilated(structure), foot->getRegion());
305 for (FootprintList::const_iterator
ptr = lhsFootprints.begin(),
end = lhsFootprints.end();
ptr !=
end;
309 if (rLhs > 0 && foot->getArea() > 0) {
310 foot = grower(foot, rLhs);
315 ->clippedTo(idImage->getBBox())
316 ->applyFunctor(setIdImage<IdPixelT>(
id, &overwritten,
true), *idImage);
318 if (!overwritten.
empty()) {
319 overwrittenIds.insert(overwrittenIds.end(),
std::make_pair(
id, overwritten));
324 id = (1 << lhsIdNbit);
325 for (FootprintList::const_iterator
ptr = rhsFootprints.begin(),
end = rhsFootprints.end();
ptr !=
end;
326 ++
ptr,
id += (1 << lhsIdNbit)) {
329 if (rRhs > 0 && foot->getArea() > 0) {
330 foot = grower(foot, rRhs);
335 ->clippedTo(idImage->getBBox())
336 ->applyFunctor(setIdImage<IdPixelT>(
id, &overwritten,
true, lhsIdMask), *idImage);
338 if (!overwritten.
empty()) {
339 overwrittenIds.insert(overwrittenIds.end(),
std::make_pair(
id, overwritten));
346 schema = lhsFootprints[0]->getPeaks().getSchema();
347 }
else if (nRhs > 0) {
348 schema = rhsFootprints[0]->getPeaks().getSchema();
352 FootprintSet fs(*idImage, Threshold(1), 1,
false,
schema);
365 FindIdsInFootprint<IdPixelT> idFinder;
366 for (
const auto& foot : *fs.getFootprints()) {
368 foot->getSpans()->applyFunctor(idFinder, *idImage);
372 for (
unsigned int indx : idFinder.getIds()) {
373 if ((indx & lhsIdMask) > 0) {
375 lhsFootprintIndxs.
insert(i);
379 OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
380 if (mapPtr != overwrittenIds.end()) {
383 for (
unsigned long ptr : overwritten) {
384 lhsFootprintIndxs.
insert((
ptr & lhsIdMask) - 1);
392 rhsFootprintIndxs.
insert(i);
396 OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
397 if (mapPtr != overwrittenIds.end()) {
400 for (
unsigned long ptr : overwritten) {
412 for (
unsigned long i : lhsFootprintIndxs) {
413 assert(i < lhsFootprints.size());
414 PeakCatalog const &oldPeaks = lhsFootprints[i]->getPeaks();
416 int const nold = peaks.
size();
417 peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
422 peaks.getInternal().end(), SortPeaks());
425 for (
unsigned long i : rhsFootprintIndxs) {
426 assert(i < rhsFootprints.size());
427 PeakCatalog const &oldPeaks = rhsFootprints[i]->getPeaks();
429 int const nold = peaks.
size();
430 peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
433 peaks.getInternal().end(), SortPeaks());
454struct IdSpanCompare {
455 bool operator()(IdSpan
const &
a, IdSpan
const &
b)
const {
458 }
else if (
a.id >
b.id) {
461 return (
a.y <
b.y) ? true :
false;
472 while (
id != aliases[
id]) {
473 resolved =
id = aliases[
id];
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) {
489 for (
auto const &spanIter : *spanSet) {
490 auto y = spanIter.getY() -
image.getY0();
495 for (
auto x = spanIter.getMinX() -
image.getX0();
x <= spanIter.getMaxX() -
image.getX0(); ++
x) {
520template <
typename ImageT>
521class FindMaxInFootprint {
523 explicit FindMaxInFootprint(
bool polarity)
524 : _polarity(polarity),
527 _min(
std::numeric_limits<double>::
max()),
528 _max(-
std::numeric_limits<double>::
max()) {}
546 void addRecord(Footprint &foot)
const { foot.addPeak(_x, _y, _polarity ? _max : _min); }
554template <
typename ImageT,
typename ThresholdT>
556 findPeaksInFootprint(img, polarity, *foot, 1);
561 std::stable_sort(foot->getPeaks().getInternal().begin(), foot->getPeaks().getInternal().end(),
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);
572template <
typename ImageT>
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;
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);
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));
602template <
typename IterT>
603static inline IterT advancePtr(IterT varPtr, Threshold_traits) {
607template <
typename IterT>
608static inline IterT advancePtr(IterT varPtr, ThresholdPixelLevel_traits) {
616template <
typename ImagePixelT,
typename MaskPixelT,
typename VariancePixelT,
typename ThresholdTraitT>
617static void findFootprints(
622 double const footprintThreshold,
623 double const includeThresholdMultiplier,
627 table::Schema
const &peakSchema =
635 double includeThreshold = footprintThreshold * includeThresholdMultiplier;
637 int const row0 = img.
getY0();
638 int const col0 = img.
getX0();
653 aliases.
reserve(1 + height / 20);
666 for (
int y = 0;
y != height; ++
y) {
667 if (idc == id1.begin() + 1) {
668 idc = id2.
begin() + 1;
669 idp = id1.
begin() + 1;
671 idc = id1.
begin() + 1;
672 idp = id2.
begin() + 1;
677 bool good = (includeThresholdMultiplier == 1.0);
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;
684 if (isBadPixel(pixVal) ||
685 !inFootprint(pixVal, varPtr, polarity, footprintThreshold, ThresholdTraitT())) {
693 if (idc[
x - 1] != 0) {
695 }
else if (idp[
x - 1] != 0) {
697 }
else if (idp[
x] != 0) {
699 }
else if (idp[
x + 1] != 0) {
714 if (idp[
x + 1] != 0 && idp[
x + 1] !=
id) {
717 idc[
x] =
id = idp[
x + 1];
720 if (!good && inFootprint(pixVal, varPtr, polarity, includeThreshold, ThresholdTraitT())) {
733 for (
auto & span : spans) {
739 if (spans.size() > 0) {
740 std::sort(spans.begin(), spans.end(), IdSpanCompare());
746 if (spans.size() > 0) {
749 for (
unsigned int i = 0; i <= spans.size(); i++) {
750 if (i == spans.size() || spans[i].id !=
id) {
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);
757 auto tempSpanSet = std::make_shared<geom::SpanSet>(
std::move(tempSpanList));
758 auto fp = std::make_shared<Footprint>(tempSpanSet, peakSchema, _region);
760 if (good && fp->getArea() >=
static_cast<std::size_t>(npixMin)) {
761 _footprints->push_back(fp);
765 if (i < spans.size()) {
774 using fiterator = FootprintSet::FootprintList::iterator;
775 for (
auto const &_footprint : *_footprints) {
776 findPeaks(_footprint, img, polarity, ThresholdTraitT());
781template <
typename ImagePixelT>
783 int const npixMin,
bool const setPeaks, table::Schema
const &peakSchema)
784 : _footprints(new FootprintList()), _region(img.getBBox()) {
785 using VariancePixelT = float;
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);
794template <
typename MaskPixelT>
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);
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);
811 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
812 "You must specify a numerical threshold value with a Mask");
816template <
typename ImagePixelT,
typename MaskPixelT>
818 Threshold
const &threshold,
std::string const &planeName,
int const npixMin,
820 : _footprints(new FootprintList()),
825 switch (threshold.getType()) {
826 case Threshold::PIXEL_STDEV:
827 findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdPixelLevel_traits>(
829 threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
833 findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdLevel_traits>(
835 threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
840 if (planeName ==
"") {
847 mask->addMaskPlane(planeName);
849 MaskPixelT
const bitPlane =
mask->getPlaneBitMask(planeName);
853 for (
auto const &fIter : *_footprints) {
854 fIter->getSpans()->setMask(*(maskedImg.
getMask()), bitPlane);
859 : _footprints(
std::
make_shared<FootprintList>()), _region(region) {}
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();
866 _footprints->push_back(std::make_shared<Footprint>(**
ptr));
871FootprintSet::FootprintSet(FootprintSet &&rhs) : FootprintSet(rhs) {}
873FootprintSet &FootprintSet::operator=(FootprintSet
const &rhs) {
874 FootprintSet tmp(rhs);
880FootprintSet &FootprintSet::operator=(FootprintSet &&rhs) {
return *
this = rhs; }
882FootprintSet::~FootprintSet() =
default;
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);
893 for (
auto const &
ptr : *_footprints) {
894 ptr->setRegion(region);
898FootprintSet::FootprintSet(FootprintSet
const &rhs,
int r,
bool isotropic)
899 : _footprints(new FootprintList), _region(rhs._region) {
901 FootprintSet fs = rhs;
905 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
906 (boost::format(
"I cannot grow by negative numbers: %d") % r).
str());
909 FootprintControl
const ctrl(
true, isotropic);
910 FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, r, ctrl);
914FootprintSet::FootprintSet(FootprintSet
const &rhs,
int ngrow, FootprintControl
const &ctrl)
915 : _footprints(new FootprintList), _region(rhs._region) {
917 FootprintSet fs = rhs;
920 }
else if (ngrow < 0) {
921 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
922 str(boost::format(
"I cannot grow by negative numbers: %d") % ngrow));
925 FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, ngrow, ctrl);
929FootprintSet::FootprintSet(FootprintSet
const &fs1, FootprintSet
const &fs2,
bool const)
930 : _footprints(new FootprintList()), _region(fs1._region) {
932 throw LSST_EXCEPT(pex::exceptions::LogicError,
"NOT IMPLEMENTED");
936 auto im = std::make_shared<image::Image<FootprintIdPixel>>(_region);
940 for (
auto const &fIter : *_footprints) {
942 fIter->getSpans()->applyFunctor(setIdImage<FootprintIdPixel>(
id), *im);
948template <
typename ImagePixelT,
typename MaskPixelT>
950 HeavyFootprintCtrl
const *ctrl) {
951 HeavyFootprintCtrl ctrl_s = HeavyFootprintCtrl();
957 for (FootprintList::iterator
ptr = _footprints->begin(),
end = _footprints->end();
ptr !=
end; ++
ptr) {
958 ptr->reset(
new HeavyFootprint<ImagePixelT, MaskPixelT>(**
ptr, mimg, ctrl));
962void FootprintSet::makeSources(afw::table::SourceCatalog &cat)
const {
963 for (
auto const &i : *_footprints) {
970 os << rhs.getFootprints()->size() <<
" footprints:\n";
972 for (
const auto &i : *(rhs.getFootprints())) {
973 os << delimiter << *i;
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 *)
#define INSTANTIATE(FROMSYS, TOSYS)
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
IdSpan(int id, int y, int x0, int x1)
static afw::table::Schema makeMinimalSchema()
Return a minimal schema for Peak tables and records.
The base class for all image classed (Image, Mask, MaskedImage, ...)
int getX0() const
Return the image's column-origin.
int getWidth() const
Return the number of columns in the image.
int getY0() const
Return the image's row-origin.
int getHeight() const
Return the number of rows in the image.
x_iterator row_begin(int y) const
Return an x_iterator to the start of the y'th row.
A class to represent a 2-dimensional array of pixels.
Represent a 2-dimensional array of bitmask pixels.
A class to manipulate images, masks, and variance as a single object.
VariancePtr getVariance() const
Return a (shared_ptr to) the MaskedImage's variance.
MaskPtr getMask() const
Return a (shared_ptr to) the MaskedImage's mask.
ImagePtr getImage() const
Return a (shared_ptr to) the MaskedImage's image.
size_type size() const
Return the number of elements in the catalog.
An integer coordinate rectangle.
int getMinY() const noexcept
void include(Point2I const &point)
Expand this to ensure that this->contains(point).
int getMinX() const noexcept
T emplace_back(T... args)
T inplace_merge(T... args)
std::ostream & operator<<(std::ostream &os, CameraSysPrefix const &detSysPrefix)
int resolve_alias(const std::vector< int > &aliases, int id)
Follow a chain of aliases, returning the final resolved value.
afw::table::CatalogT< PeakRecord > PeakCatalog
std::uint64_t FootprintIdPixel
Pixel type for FootprintSet::insertIntoImage()
Extent< int, 2 > Extent2I
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.