LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
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 #include "boost/bind.hpp"
25 
29 
30 namespace lsst { namespace afw { namespace detection {
31 
32 namespace {
33 
34 FootprintSet mergeFootprintPair(Footprint const &foot1, Footprint const &foot2) {
35 
36  geom::Box2I bbox(foot1.getBBox());
37  bbox.include(foot2.getBBox());
38 
39  std::uint16_t bits = 0x1;
40  image::Mask<std::uint16_t> mask(bbox);
41  setMaskFromFootprint(&mask, foot1, bits);
42  setMaskFromFootprint(&mask, foot2, bits);
43  FootprintSet fpSet(mask, Threshold(bits, Threshold::BITMASK));
44  return fpSet;
45 }
46 
47 } // anonymous namespace
48 
50 public:
51 
54 
55  explicit FootprintMerge(
56  PTR(Footprint) footprint,
57  PTR(afw::table::SourceTable) sourceTable,
58  PTR(PeakTable) peakTable,
59  afw::table::SchemaMapper const & peakSchemaMapper,
60  KeyTuple const & keys
61  ) :
62  _footprints(1, footprint),
63  _source(sourceTable->makeRecord())
64  {
65  PTR(Footprint) newFootprint = std::make_shared<Footprint>(*footprint);
66 
67  _source->set(keys.footprint, true);
68  // Replace all the Peaks in the merged Footprint with new ones that include the origin flags
69  newFootprint->getPeaks() = PeakCatalog(peakTable);
70  for (
71  PeakCatalog::iterator iter = footprint->getPeaks().begin();
72  iter != footprint->getPeaks().end();
73  ++iter
74  ) {
75  PTR(PeakRecord) newPeak = peakTable->copyRecord(*iter, peakSchemaMapper);
76  newPeak->set(keys.peak, true);
77  newFootprint->getPeaks().push_back(newPeak);
78  }
79  _source->setFootprint(newFootprint);
80  }
81 
82  /*
83  * Does this Footprint overlap the merged Footprint.
84  *
85  * The current implementation just builds an image from the two Footprints and
86  * detects the number of peaks. This is not very efficient and will be changed
87  * within the Footprint class in the future.
88  */
89  bool overlaps(Footprint const &rhs) const {
90  return mergeFootprintPair(*getMergedFootprint(), rhs).getFootprints()->size() == 1u;
91  }
92 
93  /*
94  * Add this Footprint to the merge.
95  *
96  * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
97  * that are greater than minNewPeakDist away from the closest peak in the existing list.
98  * If minNewPeakDist < 0, no peaks will be added from the footprint.
99  *
100  * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
101  * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
102  * be added and the closest peak will be flagged as detected by the filter defined in keys.
103  *
104  * If the footprint does not overlap it will do nothing.
105  */
106  void add(
107  PTR(Footprint) footprint,
108  afw::table::SchemaMapper const & peakSchemaMapper,
109  KeyTuple const & keys,
110  float minNewPeakDist=-1.,
111  float maxSamePeakDist=-1.
112  ) {
113  if (_addSpans(footprint)) {
114  _footprints.push_back(footprint);
115  _source->set(keys.footprint, true);
116  _addPeaks(footprint->getPeaks(), &peakSchemaMapper, &keys, minNewPeakDist,
117  maxSamePeakDist, NULL);
118  }
119  }
120 
121  /*
122  * Merge an already-merged clump of Footprints into this
123  *
124  * If minNewPeakDist >= 0, it will add all peaks from the footprint to the merged Footprint
125  * that are greater than minNewPeakDist away from the closest peak in the existing list.
126  * If minNewPeakDist < 0, no peaks will be added from the footprint.
127  *
128  * If maxSamePeakDist >= 0, it will find the closest peak in the existing list for every peak
129  * in the footprint. If the closest peak is less than maxSamePeakDist, the peak will not
130  * be added to the list and the flags from the closest peak will be set to the OR of the two.
131  *
132  * If the FootprintMerge does not overlap it will do nothing.
133  */
134  void add(
135  FootprintMerge const & other,
136  FilterMap const & keys,
137  float minNewPeakDist=-1.,
138  float maxSamePeakDist=-1.
139  ) {
140  if (_addSpans(other.getMergedFootprint())) {
141  _footprints.insert(_footprints.end(), other._footprints.begin(), other._footprints.end());
142  // Set source flags to the OR of the flags of the two inputs
143  for (FilterMap::const_iterator i = keys.begin(); i != keys.end(); ++i) {
144  afw::table::Key<afw::table::Flag> const & flagKey = i->second.footprint;
145  _source->set(flagKey, _source->get(flagKey) || other._source->get(flagKey));
146  }
147  _addPeaks(other.getMergedFootprint()->getPeaks(), NULL, NULL, minNewPeakDist,
148  maxSamePeakDist, &keys);
149  }
150  }
151 
152  // Get the bounding box of the merge
153  afw::geom::Box2I getBBox() const { return getMergedFootprint()->getBBox(); }
154 
155  PTR(Footprint) getMergedFootprint() const { return _source->getFootprint(); }
156 
158 
159 private:
160 
161  // Implementation helper for add() methods; returns true if the Footprint actually overlapped
162  // and was merged, and false otherwise.
163  bool _addSpans(PTR(Footprint) footprint) {
164  FootprintSet fpSet = mergeFootprintPair(*getMergedFootprint(), *footprint);
165  if (fpSet.getFootprints()->size() != 1u) return false;
166  getMergedFootprint()->_bbox.include(footprint->getBBox());
167  getMergedFootprint()->getSpans().swap(fpSet.getFootprints()->front()->getSpans());
168  return true;
169  }
170 
171  /*
172  * Add new peaks to the list of peaks of the merged footprint.
173  * This function handles two different cases:
174  * - The peaks come from a single footprint. In this case, the peakSchemaMapper
175  * and keys should be defined so that it can create a new peak, copy the appropriate
176  * data, and set the peak flag defined in keys.
177  * - The peaks come from another FootprintMerge. In this case, filterMap should
178  * be defined so that the information from the other peaks can be propagated.
179  */
180  void _addPeaks(
181  PeakCatalog const & otherPeaks,
182  afw::table::SchemaMapper const * peakSchemaMapper,
183  KeyTuple const * keys,
184  float minNewPeakDist,
185  float maxSamePeakDist,
186  FilterMap const * filterMap
187  ) {
188  if (minNewPeakDist < 0 && maxSamePeakDist < 0) return;
189 
190  assert(peakSchemaMapper || filterMap);
191 
192  PeakCatalog & currentPeaks = getMergedFootprint()->getPeaks();
193  PTR(PeakRecord) nearestPeak;
194  // Create new list of peaks
195  PeakCatalog newPeaks(currentPeaks.getTable());
196  float minNewPeakDist2 = minNewPeakDist*minNewPeakDist;
197  float maxSamePeakDist2 = maxSamePeakDist*maxSamePeakDist;
198  for (PeakCatalog::const_iterator otherIter = otherPeaks.begin();
199  otherIter != otherPeaks.end(); ++otherIter) {
200 
201  float minDist2 = std::numeric_limits<float>::infinity();
202 
203  for (PeakCatalog::const_iterator currentIter = currentPeaks.begin();
204  currentIter != currentPeaks.end(); ++currentIter) {
205  float dist2 = otherIter->getI().distanceSquared(currentIter->getI());
206 
207  if (dist2 < minDist2) {
208  minDist2 = dist2;
209  nearestPeak = currentIter;
210  }
211  }
212 
213  if (minDist2 < maxSamePeakDist2 && nearestPeak && maxSamePeakDist > 0) {
214  if (peakSchemaMapper) {
215  nearestPeak->set(keys->peak, true);
216  } else {
217  for (FilterMap::const_iterator i = filterMap->begin(); i != filterMap->end(); ++i) {
218  afw::table::Key<afw::table::Flag> const & flagKey = i->second.peak;
219  nearestPeak->set(flagKey, nearestPeak->get(flagKey) || otherIter->get(flagKey));
220  }
221  }
222  } else if (minDist2 > minNewPeakDist2 && !(minNewPeakDist < 0)) {
223  if (peakSchemaMapper) {
224  PTR(PeakRecord) newPeak = newPeaks.addNew();
225  newPeak->assign(*otherIter, *peakSchemaMapper);
226  newPeak->set(keys->peak, true);
227  } else {
228  newPeaks.push_back(otherIter);
229  }
230  }
231  }
232 
233  getMergedFootprint()->getPeaks().insert(
234  getMergedFootprint()->getPeaks().end(),
235  newPeaks.begin(), newPeaks.end(),
236  true // deep-copy
237  );
238  }
239 
240  std::vector<PTR(Footprint)> _footprints;
242 };
243 
244 
246  afw::table::Schema & sourceSchema,
247  std::vector<std::string> const & filterList,
248  afw::table::Schema const & initialPeakSchema
249 ) : _peakSchemaMapper(initialPeakSchema) {
250  _initialize(sourceSchema, filterList);
251 }
252 
254  afw::table::Schema & sourceSchema,
255  std::vector<std::string> const & filterList
256 ) : _peakSchemaMapper(PeakTable::makeMinimalSchema()) {
257  _initialize(sourceSchema, filterList);
258 }
259 
261  afw::table::Schema & sourceSchema,
262  std::vector<std::string> const & filterList
263 ) {
265  // Add Flags for the filters
266  for (
267  std::vector<std::string>::const_iterator iter=filterList.begin();
268  iter != filterList.end();
269  ++iter
270  ) {
271  KeyTuple & keys = _filterMap[*iter];
272  keys.footprint = sourceSchema.addField<afw::table::Flag>(
273  "merge_footprint_" + *iter,
274  "Detection footprint overlapped with a detection from filter " + *iter
275  );
276  keys.peak = _peakSchemaMapper.editOutputSchema().addField<afw::table::Flag>(
277  "merge_peak_" + *iter,
278  "Peak detected in filter " + *iter
279  );
280  }
282 }
283 
285  PTR(afw::table::SourceTable) sourceTable,
286  afw::table::SourceCatalog const &inputCat,
287  std::string const & filter,
288  float minNewPeakDist, bool doMerge, float maxSamePeakDist
289 ) {
290  FilterMap::const_iterator keyIter = _filterMap.find(filter);
291  if (keyIter == _filterMap.end()) {
292  throw LSST_EXCEPT(
293  pex::exceptions::LogicError,
294  (boost::format("Filter %s not in original list") % filter).str()
295  );
296  }
297 
298  // If list is empty don't check for any matches, just add all the objects
299  bool checkForMatches = (_mergeList.size() > 0);
300 
301  for (afw::table::SourceCatalog::const_iterator srcIter = inputCat.begin(); srcIter != inputCat.end();
302  ++srcIter) {
303 
304  // Only consider unblended objects
305  if (srcIter->getParent() != 0) continue;
306 
307  PTR(Footprint) foot = srcIter->getFootprint();
308 
309  // Empty pointer to account for the first match in the catalog. If there is more than one
310  // match, subsequent matches will be merged with this one
311  PTR(FootprintMerge) first = PTR(FootprintMerge)();
312 
313  if (checkForMatches) {
314  FootprintMergeVec::iterator iter = _mergeList.begin();
315  while (iter != _mergeList.end()) {
316  // Grow by one pixel to allow for touching
317  geom::Box2I box((**iter).getBBox());
318  box.grow(geom::Extent2I(1,1));
319  if (box.overlaps(foot->getBBox()) && (**iter).overlaps(*foot)) {
320  if (!first) {
321  first = *iter;
322  // Add Footprint to existing merge and set flag for this band
323  if (doMerge) {
324  first->add(foot, _peakSchemaMapper, keyIter->second, minNewPeakDist,
325  maxSamePeakDist);
326  }
327  } else {
328  // Add merged Footprint to first
329  if (doMerge) {
330  first->add(**iter, _filterMap, minNewPeakDist, maxSamePeakDist);
331  iter = _mergeList.erase(iter);
332  continue;
333  }
334  }
335  }
336  ++iter;
337  }
338  }
339 
340  if (!first) {
341  _mergeList.push_back(
342  std::make_shared<FootprintMerge>(
343  foot, sourceTable, _peakTable, _peakSchemaMapper, keyIter->second
344  )
345  );
346  }
347  }
348 }
349 
351 {
352  // Now set the merged footprint as the footprint of the SourceRecord
353  for (FootprintMergeVec::iterator iter = _mergeList.begin(); iter != _mergeList.end(); ++iter) {
354  if (doNorm) (**iter).getMergedFootprint()->normalize();
355  outputCat.push_back((**iter).getSource());
356  }
357 }
358 
359 }}} // namespace lsst::afw::detection
boost::shared_ptr< Table > getTable() const
Return the table associated with the catalog.
Definition: Catalog.h:112
int iter
Defines the fields and offsets for a table.
Definition: Schema.h:44
void push_back(Record const &r)
Add a copy of the given record to the end of the catalog.
Definition: Catalog.h:459
std::map< std::string, KeyTuple > FilterMap
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
bool _addSpans(boost::shared_ptr< Footprint > footprint)
boost::shared_ptr< FootprintList > getFootprints()
: Return the Footprints of detected objects
Definition: FootprintSet.h:148
FootprintMergeList::FilterMap FilterMap
FootprintMerge(boost::shared_ptr< Footprint > footprint, boost::shared_ptr< afw::table::SourceTable > sourceTable, boost::shared_ptr< PeakTable > peakTable, afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys)
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
boost::shared_ptr< Footprint > getMergedFootprint() const
Table class for Peaks in Footprints.
Definition: Peak.h:84
lsst::afw::detection::Footprint Footprint
Definition: Source.h:60
static boost::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:154
void _initialize(afw::table::Schema &sourceSchema, std::vector< std::string > const &filterList)
Use (pixels &amp; (given mask))
Definition: Threshold.h:49
void addMinimalSchema(Schema const &minimal, bool doMap=true)
Add the given minimal schema to the output schema.
An integer coordinate rectangle.
Definition: Box.h:53
Represent a collections of footprints associated with image data.
bool overlaps(Footprint const &rhs) const
void addCatalog(boost::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.
iterator begin()
Iterator access.
Definition: Catalog.h:392
FootprintMergeList::KeyTuple KeyTuple
afw::table::CatalogT< PeakRecord > PeakCatalog
Definition: Peak.h:225
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:92
boost::shared_ptr< PeakRecord > copyRecord(afw::table::BaseRecord const &other)
Definition: Peak.h:158
Iterator class for CatalogT.
Definition: Catalog.h:35
void grow(int buffer)
Increase the size of the box by the given buffer amount in all directions.
Definition: Box.h:193
void add(boost::shared_ptr< Footprint > footprint, afw::table::SchemaMapper const &peakSchemaMapper, KeyTuple const &keys, float minNewPeakDist=-1., float maxSamePeakDist=-1.)
boost::shared_ptr< afw::table::SourceRecord > getSource() const
void _addPeaks(PeakCatalog const &otherPeaks, afw::table::SchemaMapper const *peakSchemaMapper, KeyTuple const *keys, float minNewPeakDist, float maxSamePeakDist, FilterMap const *filterMap)
afw::geom::Box2I getBBox() const
A set of pixels in an Image.
Definition: Footprint.h:62
void getFinalSources(afw::table::SourceCatalog &outputCat, bool doNorm=true)
Get SourceCatalog with entries that contain the final Footprint and SourceRecord for each entry...
Schema const getOutputSchema() const
Return the output schema (copy-on-write).
Definition: SchemaMapper.h:26
Table class that contains measurements made on a single exposure.
Definition: Source.h:202
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Definition: Exception.h:46
A set of Footprints, associated with a MaskedImage.
Definition: FootprintSet.h:55
Schema & editOutputSchema()
Return a reference to the output schema that allows it to be modified in place.
Definition: SchemaMapper.h:29
boost::shared_ptr< PeakTable > _peakTable
#define PTR(...)
Definition: base.h:41
std::vector< boost::shared_ptr< Footprint > > _footprints
iterator end()
Iterator access.
Definition: Catalog.h:393
MaskT setMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels that are in the Footprint.
afw::table::SchemaMapper _peakSchemaMapper
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.
boost::shared_ptr< afw::table::SourceRecord > _source
Key< T > addField(Field< T > const &field, bool doReplace=false)
Add a new field to the Schema, and return the associated Key.
Record class that contains measurements made on a single exposure.
Definition: Source.h:80
void add(FootprintMerge const &other, FilterMap const &keys, float minNewPeakDist=-1., float maxSamePeakDist=-1.)
Record class that represents a peak in a Footprint.
Definition: Peak.h:40
PeakCatalog & getPeaks()
Return the Peaks contained in this Footprint.
Definition: Footprint.h:123
geom::Box2I getBBox() const
Return the Footprint&#39;s bounding box.
Definition: Footprint.h:206
Schema const getInputSchema() const
Return the input schema (copy-on-write).
Definition: SchemaMapper.h:23