Loading [MathJax]/extensions/tex2jax.js
LSST Applications g04a91732dc+7fec47d7bc,g07dc498a13+5ab4d22ec3,g0fba68d861+565de8e5d5,g1409bbee79+5ab4d22ec3,g1a7e361dbc+5ab4d22ec3,g1fd858c14a+11200c7927,g20f46db602+25d63fd678,g35bb328faa+fcb1d3bbc8,g4d2262a081+61302e889d,g4d39ba7253+d05e267ece,g4e0f332c67+5d362be553,g53246c7159+fcb1d3bbc8,g60b5630c4e+d05e267ece,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g8048e755c2+a1301e4c20,g8852436030+163ceb82d8,g89139ef638+5ab4d22ec3,g89e1512fd8+fbb808ebce,g8d6b6b353c+d05e267ece,g9125e01d80+fcb1d3bbc8,g989de1cb63+5ab4d22ec3,g9f33ca652e+8abe617c77,ga9baa6287d+d05e267ece,gaaedd4e678+5ab4d22ec3,gabe3b4be73+1e0a283bba,gb1101e3267+fefe9ce5b1,gb58c049af0+f03b321e39,gb90eeb9370+824c420ec4,gc741bbaa4f+77ddc33078,gcf25f946ba+163ceb82d8,gd315a588df+0f88d5218e,gd6cbbdb0b4+c8606af20c,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+e6bd566e97,ge278dab8ac+932305ba37,ge82c20c137+76d20ab76d,w.2025.10
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
FootprintMerge.cc
Go to the documentation of this file.
1/*
2 * LSST Data Management System
3 * Copyright 2008-2014 LSST Corporation.
4 *
5 * This product includes software developed by the
6 * LSST Project (http://www.lsst.org/).
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the LSST License Statement and
19 * the GNU General Public License along with this program. If not,
20 * see <http://www.lsstcorp.org/LegalNotices/>.
21 */
22#include <cstdint>
23
26
27namespace lsst {
28namespace afw {
29namespace detection {
30
32public:
33 using KeyTuple = FootprintMergeList::KeyTuple;
34 using FilterMap = FootprintMergeList::FilterMap;
35
39 afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys)
40 : _footprints(1, footprint), _source(sourceTable->makeRecord()) {
42
43 _source->set(keys.footprint, true);
44 // Replace all the Peaks in the merged Footprint with new ones that include the origin flags
45 newFootprint->getPeaks() = PeakCatalog(peakTable);
46 for (PeakCatalog::iterator iter = footprint->getPeaks().begin(); iter != footprint->getPeaks().end();
47 ++iter) {
48 std::shared_ptr<PeakRecord> newPeak = peakTable->copyRecord(*iter, peakSchemaMapper);
49 newPeak->set(keys.peak, true);
50 newPeak->setId((*newFootprint->getPeaks().getTable()->getIdFactory())());
51 newFootprint->getPeaks().push_back(newPeak);
52 }
53 _source->setFootprint(newFootprint);
54 }
55
56 ~FootprintMerge() = default;
57 FootprintMerge(FootprintMerge const &) = default;
61
62 /*
63 * Does this Footprint overlap the merged Footprint.
64 *
65 * The current implementation just builds an image from the two Footprints and
66 * detects the number of peaks. This is not very efficient and will be changed
67 * within the Footprint class in the future.
68 */
69 bool overlaps(Footprint const &rhs) const {
70 return getMergedFootprint()->getSpans()->overlaps(*(rhs.getSpans()));
71 }
72
73 /*
74 * Add this Footprint to the merge.
75 *
76 * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
77 * that are greater than minNewPeakDist away from the closest peak in the existing list.
78 * If minNewPeakDist < 0, no peaks will be added from the footprint.
79 *
80 * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
81 * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
82 * be added and the closest peak will be flagged as detected by the filter defined in keys.
83 *
84 * If the footprint does not overlap it will do nothing.
85 */
86 void add(std::shared_ptr<Footprint> footprint, afw::table::SchemaMapper const &peakSchemaMapper,
87 KeyTuple const &keys, float minNewPeakDist = -1., float maxSamePeakDist = -1.) {
88 if (addSpans(footprint)) {
89 _footprints.push_back(footprint);
90 _source->set(keys.footprint, true);
91 _addPeaks(footprint->getPeaks(), &peakSchemaMapper, &keys, minNewPeakDist, maxSamePeakDist, nullptr);
92 }
93 }
94
95 /*
96 * Merge an already-merged clump of Footprints into this
97 *
98 * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
99 * that are greater than minNewPeakDist away from the closest peak in the existing list.
100 * If minNewPeakDist < 0, no peaks will be added from the footprint.
101 *
102 * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
103 * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
104 * be added to the list and the flags from the closest peak will be set to the OR of the two.
105 *
106 * If the FootprintMerge does not overlap it will do nothing.
107 */
108 void add(FootprintMerge const &other, FilterMap const &keys, float minNewPeakDist = -1.,
109 float maxSamePeakDist = -1.) {
110 if (addSpans(other.getMergedFootprint())) {
111 _footprints.insert(_footprints.end(), other._footprints.begin(), other._footprints.end());
112 // Set source flags to the OR of the flags of the two inputs
113 for (auto const &key : keys) {
114 afw::table::Key<afw::table::Flag> const &flagKey = key.second.footprint;
115 _source->set(flagKey, _source->get(flagKey) || other._source->get(flagKey));
116 }
117 _addPeaks(other.getMergedFootprint()->getPeaks(), nullptr, nullptr, minNewPeakDist, maxSamePeakDist,
118 &keys);
119 }
120 }
121
122 // Get the bounding box of the merge
123 lsst::geom::Box2I getBBox() const { return getMergedFootprint()->getBBox(); }
124
125 std::shared_ptr<Footprint> getMergedFootprint() const { return _source->getFootprint(); }
126
128
129 // Implementation helper for add() methods; returns true if the Footprint actually overlapped
130 // and was merged, and false otherwise.
132 if (!getMergedFootprint()->getSpans()->overlaps(*(footprint->getSpans()))) return false;
133 getMergedFootprint()->setSpans(getMergedFootprint()->getSpans()->union_(*(footprint->getSpans())));
134 return true;
135 }
136
137private:
138 /*
139 * Add new peaks to the list of peaks of the merged footprint.
140 * This function handles two different cases:
141 * - The peaks come from a single footprint. In this case, the peakSchemaMapper
142 * and keys should be defined so that it can create a new peak, copy the appropriate
143 * data, and set the peak flag defined in keys.
144 * - The peaks come from another FootprintMerge. In this case, filterMap should
145 * be defined so that the information from the other peaks can be propagated.
146 */
147 void _addPeaks(PeakCatalog const &otherPeaks, afw::table::SchemaMapper const *peakSchemaMapper,
148 KeyTuple const *keys, float minNewPeakDist, float maxSamePeakDist,
149 FilterMap const *filterMap) {
150 if (minNewPeakDist < 0 && maxSamePeakDist < 0) return;
151
152 assert(peakSchemaMapper || filterMap);
153
154 PeakCatalog &currentPeaks = getMergedFootprint()->getPeaks();
155 std::shared_ptr<PeakRecord> nearestPeak;
156 // Create new list of peaks
157 PeakCatalog newPeaks(currentPeaks.getTable());
158 float minNewPeakDist2 = minNewPeakDist * minNewPeakDist;
159 float maxSamePeakDist2 = maxSamePeakDist * maxSamePeakDist;
160 for (PeakCatalog::const_iterator otherIter = otherPeaks.begin(); otherIter != otherPeaks.end();
161 ++otherIter) {
162 float minDist2 = std::numeric_limits<float>::infinity();
163
164 for (PeakCatalog::const_iterator currentIter = currentPeaks.begin();
165 currentIter != currentPeaks.end(); ++currentIter) {
166 float dist2 = otherIter->getI().distanceSquared(currentIter->getI());
167
168 if (dist2 < minDist2) {
169 minDist2 = dist2;
170 nearestPeak = currentIter;
171 }
172 }
173
174 if (minDist2 < maxSamePeakDist2 && nearestPeak && maxSamePeakDist > 0) {
175 if (peakSchemaMapper) {
176 nearestPeak->set(keys->peak, true);
177 } else {
178 for (auto const &i : *filterMap) {
179 afw::table::Key<afw::table::Flag> const &flagKey = i.second.peak;
180 nearestPeak->set(flagKey, nearestPeak->get(flagKey) || otherIter->get(flagKey));
181 }
182 }
183 } else if (minDist2 > minNewPeakDist2 && !(minNewPeakDist < 0)) {
184 if (peakSchemaMapper) {
185 std::shared_ptr<PeakRecord> newPeak = newPeaks.addNew();
186 newPeak->assign(*otherIter, *peakSchemaMapper);
187 newPeak->set(keys->peak, true);
188 newPeak->setId((*currentPeaks.getTable()->getIdFactory())());
189 } else {
190 newPeaks.push_back(otherIter);
191 }
192 }
193 }
194
195 getMergedFootprint()->getPeaks().insert(getMergedFootprint()->getPeaks().end(), newPeaks.begin(),
196 newPeaks.end(),
197 true // deep-copy
198 );
199 }
200
201 std::vector<std::shared_ptr<Footprint>> _footprints;
202 std::shared_ptr<afw::table::SourceRecord> _source;
203};
204
206 std::vector<std::string> const &filterList,
207 afw::table::Schema const &initialPeakSchema)
208 : _peakSchemaMapper(initialPeakSchema) {
209 _initialize(sourceSchema, filterList);
210}
211
213 std::vector<std::string> const &filterList)
214 : _peakSchemaMapper(PeakTable::makeMinimalSchema()) {
215 _initialize(sourceSchema, filterList);
216}
217
223
224void FootprintMergeList::_initialize(afw::table::Schema &sourceSchema,
225 std::vector<std::string> const &filterList) {
226 _peakSchemaMapper.addMinimalSchema(_peakSchemaMapper.getInputSchema(), true);
227 // Add Flags for the filters
228 for (auto const &iter : filterList) {
229 KeyTuple &keys = _filterMap[iter];
230 keys.footprint = sourceSchema.addField(
232 "merge_footprint_" + iter,
233 "Detection footprint overlapped with a detection from filter " + iter),
234 /*doReplace=*/true);
235 keys.peak = _peakSchemaMapper.editOutputSchema().addField<afw::table::Flag>(
236 "merge_peak_" + iter, "Peak detected in filter " + iter);
237 }
238 _peakTable = PeakTable::make(_peakSchemaMapper.getOutputSchema());
239}
240
242 afw::table::SourceCatalog const &inputCat, std::string const &filter,
243 float minNewPeakDist, bool doMerge, float maxSamePeakDist) {
244 FilterMap::const_iterator keyIter = _filterMap.find(filter);
245 if (keyIter == _filterMap.end()) {
247 (boost::format("Filter %s not in original list") % filter).str());
248 }
249
250 // If list is empty or merging not requested, don't check for any matches, just add all the objects
251 bool checkForMatches = !_mergeList.empty() && doMerge;
252
253 for (afw::table::SourceCatalog::const_iterator srcIter = inputCat.begin(); srcIter != inputCat.end();
254 ++srcIter) {
255 // Only consider unblended objects
256 if (srcIter->getParent() != 0) continue;
257
258 std::shared_ptr<Footprint> foot = srcIter->getFootprint();
259
260 // Empty pointer to account for the first match in the catalog. If there is more than one
261 // match, subsequent matches will be merged with this one
263
264 if (checkForMatches) {
265 FootprintMergeVec::iterator iter = _mergeList.begin();
266 while (iter != _mergeList.end()) {
267 // Grow by one pixel to allow for touching
268 lsst::geom::Box2I box((**iter).getBBox());
269 box.grow(lsst::geom::Extent2I(1, 1));
270 if (box.overlaps(foot->getBBox()) && (**iter).overlaps(*foot)) {
271 if (!first) {
272 first = *iter;
273 // Spatially extend existing FootprintMerge in order to connect subsequent,
274 // now-overlapping FootprintMerges. If a subsequent FootprintMerge overlaps with
275 // the new footprint, it's now guaranteed to overlap with this first FootprintMerge.
276 // Hold off adding foot's lower-priority footprints and peaks until the
277 // higher-priority existing peaks are merged into this first FootprintMerge.
278 first->addSpans(foot);
279 } else {
280 // Add existing merged Footprint to first
281 first->add(**iter, _filterMap, minNewPeakDist, maxSamePeakDist);
282 iter = _mergeList.erase(iter);
283 continue;
284 }
285 }
286 ++iter;
287 } // while mergeList
288 } // if checkForMatches
289
290 if (first) {
291 // Now merge footprint including peaks into the newly-connected, higher-priority FootprintMerge
292 first->add(foot, _peakSchemaMapper, keyIter->second, minNewPeakDist, maxSamePeakDist);
293 } else {
294 // Footprint did not overlap with any existing FootprintMerges. Add to MergeList
295 _mergeList.push_back(std::make_shared<FootprintMerge>(foot, sourceTable, _peakTable,
296 _peakSchemaMapper, keyIter->second));
297 }
298 }
299}
300
302 // Now set the merged footprint as the footprint of the SourceRecord
303 for (auto const &iter : _mergeList) {
304 outputCat.push_back((*iter).getSource());
305 }
306}
307} // namespace detection
308} // namespace afw
309} // namespace lsst
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
T begin(T... args)
Class to describe the properties of a detected object from an image.
Definition Footprint.h:63
std::shared_ptr< geom::SpanSet > getSpans() const
Return a shared pointer to the SpanSet.
Definition Footprint.h:115
bool overlaps(Footprint const &rhs) const
bool addSpans(std::shared_ptr< Footprint > footprint)
std::shared_ptr< afw::table::SourceRecord > getSource() const
FootprintMergeList::FilterMap FilterMap
FootprintMerge & operator=(FootprintMerge &&)=default
FootprintMerge(FootprintMerge &&)=default
std::shared_ptr< Footprint > getMergedFootprint() const
FootprintMergeList::KeyTuple KeyTuple
FootprintMerge(std::shared_ptr< Footprint > footprint, std::shared_ptr< afw::table::SourceTable > sourceTable, std::shared_ptr< PeakTable > peakTable, afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys)
FootprintMerge & operator=(FootprintMerge const &)=default
void add(std::shared_ptr< Footprint > footprint, afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys, float minNewPeakDist=-1., float maxSamePeakDist=-1.)
lsst::geom::Box2I getBBox() const
FootprintMerge(FootprintMerge const &)=default
void add(FootprintMerge const &other, FilterMap const &keys, float minNewPeakDist=-1., float maxSamePeakDist=-1.)
FootprintMergeList(afw::table::Schema &sourceSchema, std::vector< std::string > const &filterList, afw::table::Schema const &initialPeakSchema)
Initialize the merge with a custom initial peak schema.
void getFinalSources(afw::table::SourceCatalog &outputCat)
Get SourceCatalog with entries that contain the final Footprint and SourceRecord for each entry.
FootprintMergeList & operator=(FootprintMergeList const &)
void addCatalog(std::shared_ptr< afw::table::SourceTable > sourceTable, afw::table::SourceCatalog const &inputCat, std::string const &filter, float minNewPeakDist=-1., bool doMerge=true, float maxSamePeakDist=-1.)
Add objects from a SourceCatalog in the specified filter.
Table class for Peaks in Footprints.
Definition Peak.h:102
static std::shared_ptr< PeakTable > make(afw::table::Schema const &schema, bool forceNew=false)
Obtain a table that can be used to create records with given schema.
Definition Peak.cc:96
CatalogIterator< typename Internal::iterator > iterator
Definition Catalog.h:110
iterator begin()
Iterator access.
Definition Catalog.h:400
CatalogIterator< typename Internal::const_iterator > const_iterator
Definition Catalog.h:111
std::shared_ptr< Table > getTable() const
Return the table associated with the catalog.
Definition Catalog.h:114
A class used as a handle to a particular field in a table.
Definition Key.h:53
Defines the fields and offsets for a table.
Definition Schema.h:51
Key< T > addField(Field< T > const &field, bool doReplace=false)
Add a new field to the Schema, and return the associated Key.
Definition Schema.cc:479
A mapping between the keys of two Schemas, used to copy data between them.
Schema const getOutputSchema() const
Return the output schema (copy-on-write).
Schema & editOutputSchema()
Return a reference to the output schema that allows it to be modified in place.
void addMinimalSchema(Schema const &minimal, bool doMap=true)
Add the given minimal schema to the output schema.
Schema const getInputSchema() const
Return the input schema (copy-on-write).
typename Base::const_iterator const_iterator
An integer coordinate rectangle.
Definition Box.h:55
bool overlaps(Box2I const &other) const noexcept
Return true if any points in other are also in this.
Definition Box.cc:122
void grow(int buffer)
Increase the size of the box by the given buffer amount in all directions.
Definition Box.h:249
Reports errors in the logical structure of the program.
Definition Runtime.h:46
T end(T... args)
T get(T... args)
T infinity(T... args)
T make_shared(T... args)
afw::table::CatalogT< PeakRecord > PeakCatalog
Definition Peak.h:244
Extent< int, 2 > Extent2I
Definition Extent.h:397
A description of a field in a table.
Definition Field.h:24