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
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;
35
39 afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys)
40 : _footprints(1, footprint), _source(sourceTable->makeRecord()) {
41 std::shared_ptr<Footprint> newFootprint = std::make_shared<Footprint>(*footprint);
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
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<afw::table::Flag>(
231 "merge_footprint_" + iter,
232 "Detection footprint overlapped with a detection from filter " + iter);
233 keys.peak = _peakSchemaMapper.editOutputSchema().addField<afw::table::Flag>(
234 "merge_peak_" + iter, "Peak detected in filter " + iter);
235 }
236 _peakTable = PeakTable::make(_peakSchemaMapper.getOutputSchema());
237}
238
240 afw::table::SourceCatalog const &inputCat, std::string const &filter,
241 float minNewPeakDist, bool doMerge, float maxSamePeakDist) {
242 FilterMap::const_iterator keyIter = _filterMap.find(filter);
243 if (keyIter == _filterMap.end()) {
245 (boost::format("Filter %s not in original list") % filter).str());
246 }
247
248 // If list is empty or merging not requested, don't check for any matches, just add all the objects
249 bool checkForMatches = !_mergeList.empty() && doMerge;
250
251 for (afw::table::SourceCatalog::const_iterator srcIter = inputCat.begin(); srcIter != inputCat.end();
252 ++srcIter) {
253 // Only consider unblended objects
254 if (srcIter->getParent() != 0) continue;
255
256 std::shared_ptr<Footprint> foot = srcIter->getFootprint();
257
258 // Empty pointer to account for the first match in the catalog. If there is more than one
259 // match, subsequent matches will be merged with this one
261
262 if (checkForMatches) {
263 FootprintMergeVec::iterator iter = _mergeList.begin();
264 while (iter != _mergeList.end()) {
265 // Grow by one pixel to allow for touching
266 lsst::geom::Box2I box((**iter).getBBox());
267 box.grow(lsst::geom::Extent2I(1, 1));
268 if (box.overlaps(foot->getBBox()) && (**iter).overlaps(*foot)) {
269 if (!first) {
270 first = *iter;
271 // Spatially extend existing FootprintMerge in order to connect subsequent,
272 // now-overlapping FootprintMerges. If a subsequent FootprintMerge overlaps with
273 // the new footprint, it's now guaranteed to overlap with this first FootprintMerge.
274 // Hold off adding foot's lower-priority footprints and peaks until the
275 // higher-priority existing peaks are merged into this first FootprintMerge.
276 first->addSpans(foot);
277 } else {
278 // Add existing merged Footprint to first
279 first->add(**iter, _filterMap, minNewPeakDist, maxSamePeakDist);
280 iter = _mergeList.erase(iter);
281 continue;
282 }
283 }
284 ++iter;
285 } // while mergeList
286 } // if checkForMatches
287
288 if (first) {
289 // Now merge footprint including peaks into the newly-connected, higher-priority FootprintMerge
290 first->add(foot, _peakSchemaMapper, keyIter->second, minNewPeakDist, maxSamePeakDist);
291 } else {
292 // Footprint did not overlap with any existing FootprintMerges. Add to MergeList
293 _mergeList.push_back(std::make_shared<FootprintMerge>(foot, sourceTable, _peakTable,
294 _peakSchemaMapper, keyIter->second));
295 }
296 }
297}
298
300 // Now set the merged footprint as the footprint of the SourceRecord
301 for (auto const &iter : _mergeList) {
302 outputCat.push_back((*iter).getSource());
303 }
304}
305} // namespace detection
306} // namespace afw
307} // namespace lsst
int end
#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
Iterator class for CatalogT.
Definition: Catalog.h:40
iterator begin()
Iterator access.
Definition: Catalog.h:401
std::shared_ptr< Table > getTable() const
Return the table associated with the catalog.
Definition: Catalog.h:115
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.
Definition: SchemaMapper.h:21
Schema const getOutputSchema() const
Return the output schema (copy-on-write).
Definition: SchemaMapper.h:27
Schema & editOutputSchema()
Return a reference to the output schema that allows it to be modified in place.
Definition: SchemaMapper.h:30
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).
Definition: SchemaMapper.h:24
typename Base::const_iterator const_iterator
Definition: SortedCatalog.h:50
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 empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T get(T... args)
T infinity(T... args)
afw::table::CatalogT< PeakRecord > PeakCatalog
Definition: Peak.h:244
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
A base class for image defects.
T push_back(T... args)