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;
131 std::set<std::uint64_t>::const_iterator _pos;
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;
225 bool const left = ctrl.isLeft().first && ctrl.isLeft().second;
226 bool const right = ctrl.isRight().first && ctrl.isRight().second;
227 bool const up = ctrl.isUp().first && ctrl.isUp().second;
228 bool const down = ctrl.isDown().first && ctrl.isDown().second;
231 if (region != rhs.getRegion()) {
232 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
233 boost::format(
"The two FootprintSets must have the same region").str());
236 auto idImage = std::make_shared<image::Image<IdPixelT>>(region);
240 FootprintList
const &lhsFootprints = *lhs.getFootprints();
241 FootprintList
const &rhsFootprints = *rhs.getFootprints();
242 int const nLhs = lhsFootprints.size();
243 int const nRhs = rhsFootprints.size();
245 if (nLhs > 0 && nRhs > 0 &&
246 lhsFootprints[0]->getPeaks().getSchema() != rhsFootprints[0]->getPeaks().getSchema()) {
248 "FootprintSets to be merged must have identical peak schemas.");
255 int const lhsIdNbit = nbit(nLhs);
256 int const lhsIdMask = (lhsIdNbit == 0) ? 0x0 : (1 << lhsIdNbit) - 1;
260 (boost::format(
"%d + %d footprints need too many bits; change IdPixelT typedef") %
270 OldIdMap overwrittenIds;
272 auto grower = [&circular, &up, &down, &
left, &
right, &isotropic](
276 auto tmpFoot = std::make_shared<Footprint>(foot->getSpans()->dilated(amount,
element),
280 int top = up ? amount : 0;
281 int bottom = down ? amount : 0;
282 int lLimit =
left ? amount : 0;
283 int rLimit =
right ? amount : 0;
285 auto yRange = top + bottom + 1;
289 for (
auto dy = 1; dy <= top; ++dy) {
292 for (
auto dy = -1; dy >= -bottom; --dy) {
296 geom::SpanSet structure(
std::move(spanList));
298 std::make_shared<Footprint>(foot->getSpans()->dilated(structure), foot->getRegion());
304 for (FootprintList::const_iterator
ptr = lhsFootprints.begin(),
end = lhsFootprints.end();
ptr !=
end;
308 if (rLhs > 0 && foot->getArea() > 0) {
309 foot = grower(foot, rLhs);
314 ->clippedTo(idImage->getBBox())
315 ->applyFunctor(setIdImage<IdPixelT>(
id, &overwritten,
true), *idImage);
317 if (!overwritten.
empty()) {
318 overwrittenIds.insert(overwrittenIds.end(),
std::make_pair(
id, overwritten));
323 id = (1 << lhsIdNbit);
324 for (FootprintList::const_iterator
ptr = rhsFootprints.begin(),
end = rhsFootprints.end();
ptr !=
end;
325 ++
ptr,
id += (1 << lhsIdNbit)) {
328 if (rRhs > 0 && foot->getArea() > 0) {
329 foot = grower(foot, rRhs);
334 ->clippedTo(idImage->getBBox())
335 ->applyFunctor(setIdImage<IdPixelT>(
id, &overwritten,
true, lhsIdMask), *idImage);
337 if (!overwritten.
empty()) {
338 overwrittenIds.insert(overwrittenIds.end(),
std::make_pair(
id, overwritten));
345 schema = lhsFootprints[0]->getPeaks().getSchema();
346 }
else if (nRhs > 0) {
347 schema = rhsFootprints[0]->getPeaks().getSchema();
351 FootprintSet fs(*idImage, Threshold(1), 1,
false,
schema);
364 FindIdsInFootprint<IdPixelT> idFinder;
365 for (
const auto& foot : *fs.getFootprints()) {
367 foot->getSpans()->applyFunctor(idFinder, *idImage);
371 for (
unsigned int indx : idFinder.getIds()) {
372 if ((indx & lhsIdMask) > 0) {
374 lhsFootprintIndxs.
insert(i);
378 OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
379 if (mapPtr != overwrittenIds.end()) {
382 for (
unsigned long ptr : overwritten) {
383 lhsFootprintIndxs.
insert((
ptr & lhsIdMask) - 1);
391 rhsFootprintIndxs.
insert(i);
395 OldIdMap::iterator mapPtr = overwrittenIds.find(indx);
396 if (mapPtr != overwrittenIds.end()) {
399 for (
unsigned long ptr : overwritten) {
409 PeakCatalog &peaks = foot->getPeaks();
411 for (
unsigned long i : lhsFootprintIndxs) {
412 assert(i < lhsFootprints.size());
413 PeakCatalog
const &oldPeaks = lhsFootprints[i]->getPeaks();
415 int const nold = peaks.size();
416 peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
421 peaks.getInternal().end(), SortPeaks());
424 for (
unsigned long i : rhsFootprintIndxs) {
425 assert(i < rhsFootprints.size());
426 PeakCatalog
const &oldPeaks = rhsFootprints[i]->getPeaks();
428 int const nold = peaks.size();
429 peaks.insert(peaks.end(), oldPeaks.begin(), oldPeaks.end());
432 peaks.getInternal().end(), SortPeaks());
453struct IdSpanCompare {
454 bool operator()(IdSpan
const &a, IdSpan
const &
b)
const {
457 }
else if (a.id >
b.id) {
460 return (a.y <
b.y) ? true :
false;
471 while (
id != aliases[
id]) {
472 resolved =
id = aliases[
id];
481template <
typename ImageT>
483 auto spanSet = foot.getSpans();
484 if (spanSet->size() == 0) {
488 for (
auto const &spanIter : *spanSet) {
489 auto y = spanIter.getY() -
image.getY0();
494 for (
auto x = spanIter.getMinX() -
image.getX0();
x <= spanIter.getMaxX() -
image.getX0(); ++
x) {
519template <
typename ImageT>
520class FindMaxInFootprint {
522 explicit FindMaxInFootprint(
bool polarity)
523 : _polarity(polarity),
526 _min(
std::numeric_limits<double>::
max()),
527 _max(-
std::numeric_limits<double>::
max()) {}
545 void addRecord(
Footprint &foot)
const { foot.addPeak(_x, _y, _polarity ? _max : _min); }
553template <
typename ImageT,
typename ThresholdT>
555 findPeaksInFootprint(img, polarity, *foot, 1);
560 std::stable_sort(foot->getPeaks().getInternal().begin(), foot->getPeaks().getInternal().end(),
563 if (foot->getPeaks().empty()) {
564 FindMaxInFootprint<typename ImageT::Pixel> maxFinder(polarity);
565 foot->getSpans()->applyFunctor(maxFinder,
ndarray::ndImage(img.getArray(), img.getXY0()));
566 maxFinder.addRecord(*foot);
571template <
typename ImageT>
580template <
typename ImagePixelT,
typename IterT>
581static inline bool inFootprint(ImagePixelT pixVal, IterT,
bool polarity,
double thresholdVal,
582 ThresholdLevel_traits) {
583 return (polarity ? pixVal : -pixVal) >= thresholdVal;
586template <
typename ImagePixelT,
typename IterT>
587static inline bool inFootprint(ImagePixelT pixVal, IterT var,
bool polarity,
double thresholdVal,
588 ThresholdPixelLevel_traits) {
589 return (polarity ? pixVal : -pixVal) >= thresholdVal * ::sqrt(*var);
592template <
typename ImagePixelT,
typename IterT>
593static inline bool inFootprint(ImagePixelT pixVal, IterT,
bool,
double thresholdVal,
594 ThresholdBitmask_traits) {
595 return (pixVal &
static_cast<long>(thresholdVal));
601template <
typename IterT>
602static inline IterT advancePtr(IterT varPtr, Threshold_traits) {
606template <
typename IterT>
607static inline IterT advancePtr(IterT varPtr, ThresholdPixelLevel_traits) {
615template <
typename ImagePixelT,
typename MaskPixelT,
typename VariancePixelT,
typename ThresholdTraitT>
616static void findFootprints(
621 double const footprintThreshold,
622 double const includeThresholdMultiplier,
626 table::Schema
const &peakSchema =
634 double includeThreshold = footprintThreshold * includeThresholdMultiplier;
636 int const row0 = img.
getY0();
637 int const col0 = img.
getX0();
648 std::vector<int>::iterator idc = id1.begin() + 1;
649 std::vector<int>::iterator idp = id2.begin() + 1;
652 aliases.
reserve(1 + height / 20);
665 for (
int y = 0;
y != height; ++
y) {
666 if (idc == id1.begin() + 1) {
667 idc = id2.begin() + 1;
668 idp = id1.begin() + 1;
670 idc = id1.begin() + 1;
671 idp = id2.begin() + 1;
676 bool good = (includeThresholdMultiplier == 1.0);
679 x_var_iterator varPtr = (var ==
nullptr) ?
nullptr : var->row_begin(
y);
680 for (
int x = 0;
x < width; ++
x, ++pixPtr, varPtr = advancePtr(varPtr, ThresholdTraitT())) {
681 ImagePixelT
const pixVal = *pixPtr;
683 if (isBadPixel(pixVal) ||
684 !inFootprint(pixVal, varPtr, polarity, footprintThreshold, ThresholdTraitT())) {
692 if (idc[
x - 1] != 0) {
694 }
else if (idp[
x - 1] != 0) {
696 }
else if (idp[
x] != 0) {
698 }
else if (idp[
x + 1] != 0) {
713 if (idp[
x + 1] != 0 && idp[
x + 1] !=
id) {
716 idc[
x] =
id = idp[
x + 1];
719 if (!good && inFootprint(pixVal, varPtr, polarity, includeThreshold, ThresholdTraitT())) {
732 for (
auto & span : spans) {
738 if (spans.
size() > 0) {
745 if (spans.
size() > 0) {
748 for (
unsigned int i = 0; i <= spans.
size(); i++) {
749 if (i == spans.
size() || spans[i].id !=
id) {
752 for (; i0 < i; i0++) {
753 good |= spans[i0].good;
754 tempSpanList.
emplace_back(spans[i0].
y + row0, spans[i0].x0 + col0, spans[i0].x1 + col0);
756 auto tempSpanSet = std::make_shared<geom::SpanSet>(
std::move(tempSpanList));
757 auto fp = std::make_shared<Footprint>(tempSpanSet, peakSchema, _region);
759 if (good && fp->getArea() >=
static_cast<std::size_t>(npixMin)) {
760 _footprints->push_back(fp);
764 if (i < spans.
size()) {
773 using fiterator = FootprintSet::FootprintList::iterator;
774 for (
auto const &_footprint : *_footprints) {
775 findPeaks(_footprint, img, polarity, ThresholdTraitT());
780template <
typename ImagePixelT>
782 int const npixMin,
bool const setPeaks, table::Schema
const &peakSchema)
783 : _footprints(new FootprintList()), _region(img.getBBox()) {
784 using VariancePixelT =
float;
786 findFootprints<ImagePixelT, image::MaskPixel, VariancePixelT, ThresholdLevel_traits>(
787 _footprints.get(), _region, img,
nullptr, threshold.getValue(img),
788 threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin, setPeaks, peakSchema);
793template <
typename MaskPixelT>
795 : _footprints(new FootprintList()), _region(msk.getBBox()) {
796 switch (threshold.getType()) {
797 case Threshold::BITMASK:
798 findFootprints<MaskPixelT, MaskPixelT, float, ThresholdBitmask_traits>(
799 _footprints.get(), _region, msk,
nullptr, threshold.getValue(),
800 threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin,
false);
803 case Threshold::VALUE:
804 findFootprints<MaskPixelT, MaskPixelT, float, ThresholdLevel_traits>(
805 _footprints.get(), _region, msk,
nullptr, threshold.getValue(),
806 threshold.getIncludeMultiplier(), threshold.getPolarity(), npixMin,
false);
810 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
811 "You must specify a numerical threshold value with a Mask");
815template <
typename ImagePixelT,
typename MaskPixelT>
817 Threshold
const &threshold,
std::string const &planeName,
int const npixMin,
819 : _footprints(new FootprintList()),
824 switch (threshold.getType()) {
825 case Threshold::PIXEL_STDEV:
826 findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdPixelLevel_traits>(
828 threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
832 findFootprints<ImagePixelT, MaskPixelT, VariancePixelT, ThresholdLevel_traits>(
834 threshold.getValue(maskedImg), threshold.getIncludeMultiplier(), threshold.getPolarity(),
839 if (planeName ==
"") {
846 mask->addMaskPlane(planeName);
848 MaskPixelT
const bitPlane =
mask->getPlaneBitMask(planeName);
852 for (
auto const &fIter : *_footprints) {
853 fIter->getSpans()->setMask(*(maskedImg.
getMask()), bitPlane);
858 : _footprints(
std::
make_shared<FootprintList>()), _region(region) {}
860FootprintSet::FootprintSet(FootprintSet
const &rhs) : _footprints(new FootprintList), _region(rhs._region) {
861 _footprints->reserve(rhs._footprints->size());
862 for (FootprintSet::FootprintList::const_iterator
ptr = rhs._footprints->begin(),
863 end = rhs._footprints->end();
865 _footprints->push_back(std::make_shared<Footprint>(**
ptr));
870FootprintSet::FootprintSet(FootprintSet &&rhs) : FootprintSet(rhs) {}
872FootprintSet &FootprintSet::operator=(FootprintSet
const &rhs) {
873 FootprintSet tmp(rhs);
879FootprintSet &FootprintSet::operator=(FootprintSet &&rhs) {
return *
this = rhs; }
881FootprintSet::~FootprintSet() =
default;
883void FootprintSet::merge(FootprintSet
const &rhs,
int tGrow,
int rGrow,
bool isotropic) {
884 FootprintControl
const ctrl(
true, isotropic);
885 FootprintSet fs = mergeFootprintSets(*
this, tGrow, rhs, rGrow, ctrl);
892 for (
auto const &
ptr : *_footprints) {
893 ptr->setRegion(region);
897FootprintSet::FootprintSet(FootprintSet
const &rhs,
int r,
bool isotropic)
898 : _footprints(new FootprintList), _region(rhs._region) {
900 FootprintSet fs = rhs;
904 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
905 (boost::format(
"I cannot grow by negative numbers: %d") % r).str());
908 FootprintControl
const ctrl(
true, isotropic);
909 FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, r, ctrl);
913FootprintSet::FootprintSet(FootprintSet
const &rhs,
int ngrow, FootprintControl
const &ctrl)
914 : _footprints(new FootprintList), _region(rhs._region) {
916 FootprintSet fs = rhs;
919 }
else if (ngrow < 0) {
920 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
921 str(boost::format(
"I cannot grow by negative numbers: %d") % ngrow));
924 FootprintSet fs = mergeFootprintSets(FootprintSet(rhs.getRegion()), 0, rhs, ngrow, ctrl);
928FootprintSet::FootprintSet(FootprintSet
const &fs1, FootprintSet
const &fs2,
bool const)
929 : _footprints(new FootprintList()), _region(fs1._region) {
931 throw LSST_EXCEPT(pex::exceptions::LogicError,
"NOT IMPLEMENTED");
935 auto im = std::make_shared<image::Image<FootprintIdPixel>>(_region);
939 for (
auto const &fIter : *_footprints) {
941 fIter->getSpans()->applyFunctor(setIdImage<FootprintIdPixel>(
id), *im);
947template <
typename ImagePixelT,
typename MaskPixelT>
949 HeavyFootprintCtrl
const *ctrl) {
950 HeavyFootprintCtrl ctrl_s = HeavyFootprintCtrl();
956 for (FootprintList::iterator
ptr = _footprints->begin(),
end = _footprints->end();
ptr !=
end; ++
ptr) {
957 ptr->reset(
new HeavyFootprint<ImagePixelT, MaskPixelT>(**
ptr, mimg, ctrl));
961void FootprintSet::makeSources(afw::table::SourceCatalog &cat)
const {
962 cat.reserve(cat.size() + _footprints->size());
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.
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)
int resolve_alias(const std::vector< int > &aliases, int id)
Follow a chain of aliases, returning the final resolved value.
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.