lsst.afw  22.0.1-31-gd62ef0f05+23bd69c089
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 
24 
28 
29 namespace lsst {
30 namespace afw {
31 namespace detection {
32 
34 public:
35  using KeyTuple = FootprintMergeList::KeyTuple;
37 
41  afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys)
42  : _footprints(1, footprint), _source(sourceTable->makeRecord()) {
43  std::shared_ptr<Footprint> newFootprint = std::make_shared<Footprint>(*footprint);
44 
45  _source->set(keys.footprint, true);
46  // Replace all the Peaks in the merged Footprint with new ones that include the origin flags
47  newFootprint->getPeaks() = PeakCatalog(peakTable);
48  for (PeakCatalog::iterator iter = footprint->getPeaks().begin(); iter != footprint->getPeaks().end();
49  ++iter) {
50  std::shared_ptr<PeakRecord> newPeak = peakTable->copyRecord(*iter, peakSchemaMapper);
51  newPeak->set(keys.peak, true);
52  newFootprint->getPeaks().push_back(newPeak);
53  }
54  _source->setFootprint(newFootprint);
55  }
56 
57  ~FootprintMerge() = default;
58  FootprintMerge(FootprintMerge const &) = default;
62 
63  /*
64  * Does this Footprint overlap the merged Footprint.
65  *
66  * The current implementation just builds an image from the two Footprints and
67  * detects the number of peaks. This is not very efficient and will be changed
68  * within the Footprint class in the future.
69  */
70  bool overlaps(Footprint const &rhs) const {
71  return getMergedFootprint()->getSpans()->overlaps(*(rhs.getSpans()));
72  }
73 
74  /*
75  * Add this Footprint to the merge.
76  *
77  * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
78  * that are greater than minNewPeakDist away from the closest peak in the existing list.
79  * If minNewPeakDist < 0, no peaks will be added from the footprint.
80  *
81  * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
82  * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
83  * be added and the closest peak will be flagged as detected by the filter defined in keys.
84  *
85  * If the footprint does not overlap it will do nothing.
86  */
87  void add(std::shared_ptr<Footprint> footprint, afw::table::SchemaMapper const &peakSchemaMapper,
88  KeyTuple const &keys, float minNewPeakDist = -1., float maxSamePeakDist = -1.) {
89  if (addSpans(footprint)) {
90  _footprints.push_back(footprint);
91  _source->set(keys.footprint, true);
92  _addPeaks(footprint->getPeaks(), &peakSchemaMapper, &keys, minNewPeakDist, maxSamePeakDist, nullptr);
93  }
94  }
95 
96  /*
97  * Merge an already-merged clump of Footprints into this
98  *
99  * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
100  * that are greater than minNewPeakDist away from the closest peak in the existing list.
101  * If minNewPeakDist < 0, no peaks will be added from the footprint.
102  *
103  * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
104  * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
105  * be added to the list and the flags from the closest peak will be set to the OR of the two.
106  *
107  * If the FootprintMerge does not overlap it will do nothing.
108  */
109  void add(FootprintMerge const &other, FilterMap const &keys, float minNewPeakDist = -1.,
110  float maxSamePeakDist = -1.) {
111  if (addSpans(other.getMergedFootprint())) {
112  _footprints.insert(_footprints.end(), other._footprints.begin(), other._footprints.end());
113  // Set source flags to the OR of the flags of the two inputs
114  for (auto const &key : keys) {
115  afw::table::Key<afw::table::Flag> const &flagKey = key.second.footprint;
116  _source->set(flagKey, _source->get(flagKey) || other._source->get(flagKey));
117  }
118  _addPeaks(other.getMergedFootprint()->getPeaks(), nullptr, nullptr, minNewPeakDist, maxSamePeakDist,
119  &keys);
120  }
121  }
122 
123  // Get the bounding box of the merge
124  lsst::geom::Box2I getBBox() const { return getMergedFootprint()->getBBox(); }
125 
126  std::shared_ptr<Footprint> getMergedFootprint() const { return _source->getFootprint(); }
127 
129 
130  // Implementation helper for add() methods; returns true if the Footprint actually overlapped
131  // and was merged, and false otherwise.
133  if (!getMergedFootprint()->getSpans()->overlaps(*(footprint->getSpans()))) return false;
134  getMergedFootprint()->setSpans(getMergedFootprint()->getSpans()->union_(*(footprint->getSpans())));
135  return true;
136  }
137 
138 private:
139  /*
140  * Add new peaks to the list of peaks of the merged footprint.
141  * This function handles two different cases:
142  * - The peaks come from a single footprint. In this case, the peakSchemaMapper
143  * and keys should be defined so that it can create a new peak, copy the appropriate
144  * data, and set the peak flag defined in keys.
145  * - The peaks come from another FootprintMerge. In this case, filterMap should
146  * be defined so that the information from the other peaks can be propagated.
147  */
148  void _addPeaks(PeakCatalog const &otherPeaks, afw::table::SchemaMapper const *peakSchemaMapper,
149  KeyTuple const *keys, float minNewPeakDist, float maxSamePeakDist,
150  FilterMap const *filterMap) {
151  if (minNewPeakDist < 0 && maxSamePeakDist < 0) return;
152 
153  assert(peakSchemaMapper || filterMap);
154 
155  PeakCatalog &currentPeaks = getMergedFootprint()->getPeaks();
156  std::shared_ptr<PeakRecord> nearestPeak;
157  // Create new list of peaks
158  PeakCatalog newPeaks(currentPeaks.getTable());
159  float minNewPeakDist2 = minNewPeakDist * minNewPeakDist;
160  float maxSamePeakDist2 = maxSamePeakDist * maxSamePeakDist;
161  for (PeakCatalog::const_iterator otherIter = otherPeaks.begin(); otherIter != otherPeaks.end();
162  ++otherIter) {
163  float minDist2 = std::numeric_limits<float>::infinity();
164 
165  for (PeakCatalog::const_iterator currentIter = currentPeaks.begin();
166  currentIter != currentPeaks.end(); ++currentIter) {
167  float dist2 = otherIter->getI().distanceSquared(currentIter->getI());
168 
169  if (dist2 < minDist2) {
170  minDist2 = dist2;
171  nearestPeak = currentIter;
172  }
173  }
174 
175  if (minDist2 < maxSamePeakDist2 && nearestPeak && maxSamePeakDist > 0) {
176  if (peakSchemaMapper) {
177  nearestPeak->set(keys->peak, true);
178  } else {
179  for (auto const &i : *filterMap) {
180  afw::table::Key<afw::table::Flag> const &flagKey = i.second.peak;
181  nearestPeak->set(flagKey, nearestPeak->get(flagKey) || otherIter->get(flagKey));
182  }
183  }
184  } else if (minDist2 > minNewPeakDist2 && !(minNewPeakDist < 0)) {
185  if (peakSchemaMapper) {
186  std::shared_ptr<PeakRecord> newPeak = newPeaks.addNew();
187  newPeak->assign(*otherIter, *peakSchemaMapper);
188  newPeak->set(keys->peak, true);
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 
224 void 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,...)
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)
FootprintMergeList::FilterMap FilterMap
std::shared_ptr< afw::table::SourceRecord > getSource() const
std::shared_ptr< Footprint > getMergedFootprint() const
FootprintMerge(FootprintMerge &&)=default
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 &&)=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
FootprintMerge & operator=(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:98
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:480
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
bool overlaps(Box2I const &other) const noexcept
void grow(int buffer)
T empty(T... args)
T end(T... args)
T erase(T... args)
T find(T... args)
T get(T... args)
T infinity(T... args)
def iter(self)
def keys(self)
afw::table::CatalogT< PeakRecord > PeakCatalog
Definition: Peak.h:244
A base class for image defects.
T push_back(T... args)