LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
Footprint.cc
Go to the documentation of this file.
1 /*
2  * LSST Data Management System
3  * Copyright 2008, 2009, 2010 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 
23 /*****************************************************************************/
28 #include <cassert>
29 #include <string>
30 #include <typeinfo>
31 #include <algorithm>
32 #include "boost/format.hpp"
33 #include "lsst/pex/logging/Trace.h"
34 #include "lsst/pex/exceptions.h"
36 #include "lsst/afw/image/Mask.h"
37 #include "lsst/afw/math/Kernel.h"
42 #include "lsst/afw/geom/Point.h"
47 #include "lsst/utils/ieee.h"
48 
49 #include <boost/archive/text_iarchive.hpp>
50 #include <boost/archive/text_oarchive.hpp>
51 #include <boost/archive/xml_iarchive.hpp>
52 #include <boost/archive/xml_oarchive.hpp>
53 #include <boost/archive/binary_iarchive.hpp>
54 #include <boost/archive/binary_oarchive.hpp>
55 #include <boost/serialization/shared_ptr.hpp>
56 #include <boost/serialization/vector.hpp>
57 
58 using boost::serialization::make_nvp;
59 
60 namespace lsst {
61 namespace afw {
62 namespace detection {
63 
64 // anonymous namespace
65 namespace {
66 
67 /*
68  * Compare two Span%s by y, then x0, then x1
69  *
70  * A utility functor passed to sort; needed to dereference the boost::shared_ptrs.
71  */
72  struct compareSpanByYX :
73  public std::binary_function<Span::ConstPtr, Span::ConstPtr, bool> {
74  int operator()(Span::ConstPtr a, Span::ConstPtr b) {
75  return (*a) < (*b);
76  }
77  };
78 
80 geom::Point2D transformPoint(double x, double y,
81  image::Wcs const& source,
82  image::Wcs const& target){
83  return target.skyToPixel(*source.pixelToSky(x, y));
84 }
85 
86 
87 } //end namespace
88 
89 /*****************************************************************************/
91 int Footprint::id = 0;
92 
94  int nspan,
95  geom::Box2I const & region
96 ) : lsst::daf::base::Citizen(typeid(this)),
97  _fid(++id),
98  _area(0),
99  _bbox(geom::Box2I()),
100  _region(region),
101  _normalized(true)
102 {
103  if (nspan < 0) {
104  throw LSST_EXCEPT(
105  lsst::pex::exceptions::InvalidParameterError,
106  str(boost::format("Number of spans requested is -ve: %d") % nspan));
107  }
108 }
109 
111  geom::Box2I const& bbox,
112  geom::Box2I const& region
113 ) : lsst::daf::base::Citizen(typeid(this)),
114  _fid(++id),
115  _area(0),
116  _bbox(bbox),
117  _region(region)
118 {
119  int const x0 = bbox.getMinX();
120  int const y0 = bbox.getMinY();
121  int const x1 = bbox.getMaxX();
122  int const y1 = bbox.getMaxY();
123 
124  for (int i = y0; i <= y1; ++i) {
125  addSpan(i, x0, x1);
126  }
127  _normalized=true;
128 }
129 
131  geom::Point2I const & center,
132  double const radius,
133  geom::BoxI const & region
134 ) : lsst::daf::base::Citizen(typeid(this)),
135  _fid(++id),
136  _area(0),
137  _bbox(geom::BoxI()),
138  _region(region)
139 {
140  int const r2 = static_cast<int>(radius*radius + 0.5); // rounded radius^2
141  int const r = static_cast<int>(std::sqrt(static_cast<double>(r2))); // truncated radius; r*r <= r2
142 
143  for (int i = -r; i <= r; ++i) {
144  int hlen = static_cast<int>(std::sqrt(static_cast<double>(r2 - i*i)));
145  addSpan(center.getY() + i, center.getX() - hlen, center.getX() + hlen);
146  }
147  _normalized = true;
148 }
149 
151  geom::ellipses::Ellipse const & ellipse,
152  geom::Box2I const & region
153 ) : lsst::daf::base::Citizen(typeid(this)),
154  _fid(++id),
155  _area(0),
156  _bbox(geom::Box2I()),
157  _region(region),
158  _normalized(true)
159 {
160  geom::ellipses::PixelRegion pr(ellipse);
161  for (
162  geom::ellipses::PixelRegion::Iterator spanIter = pr.begin(), end = pr.end();
163  spanIter != end;
164  ++spanIter
165  ) {
166  if (!spanIter->isEmpty()) {
167  addSpan(*spanIter);
168  }
169  }
170  _normalized = true;
171 }
172 
174  Footprint::SpanList const & spans,
175  geom::Box2I const & region
176 ) : lsst::daf::base::Citizen(typeid(this)),
177  _fid(++id),
178  _area(0),
179  _bbox(geom::Box2I()),
180  _region(region),
181  _normalized(false)
182 {
183  _spans.reserve(spans.size());
184  for(SpanList::const_iterator i(spans.begin()); i != spans.end(); ++i) {
185  addSpan(**i);
186  }
187 }
188 
189 Footprint::Footprint(Footprint const & other)
190  : lsst::daf::base::Citizen(typeid(this)),
191  _fid(++id),
192  _bbox(other._bbox),
193  _region(other._region)
194 {
195  //deep copy spans
196  _spans.reserve(other._spans.size());
197  for(SpanList::const_iterator i(other._spans.begin());
198  i != other._spans.end(); ++i
199  ) {
200  addSpan(**i);
201  }
202  _area = other._area;
203  _normalized = other._normalized;
204 
205  //deep copy peaks
206  _peaks.reserve(other._peaks.size());
207  for(PeakList::const_iterator i(other._peaks.begin()); i != other._peaks.end(); ++i) {
208  _peaks.push_back(PTR(Peak)(new Peak(**i)));
209  }
210 }
211 
212 Footprint::~Footprint() {
213 }
214 
215 bool Footprint::contains(
216  lsst::afw::geom::Point2I const& pix
217 ) const
218 {
219  if (_bbox.contains(pix)) {
220  for (Footprint::SpanList::const_iterator siter = _spans.begin(); siter != _spans.end(); ++siter) {
221  if ((*siter)->contains(pix.getX(), pix.getY())) {
222  return true;
223  }
224  }
225  }
226 
227  return false;
228 }
229 
230 void Footprint::clipTo(geom::Box2I const& bbox) {
231  Footprint::SpanList::iterator it = _spans.begin();
232  for (; it != _spans.end();) {
233  Span *sp = it->get();
234  if ((sp->getY() < bbox.getMinY()) ||
235  (sp->getY() > bbox.getMaxY())) {
236  // out of bounds in y -- erase
237  it = _spans.erase(it);
238  continue;
239  }
240  if ((sp->getX0() > bbox.getMaxX()) ||
241  (sp->getX1() < bbox.getMinX())) {
242  // out of bounds in x; erase
243  it = _spans.erase(it);
244  continue;
245  }
246  // clip
247  if (sp->getX0() < bbox.getMinX()) {
248  // clip span x0 to bbox
249  sp->_x0 = bbox.getMinX();
250  }
251  if (sp->getX1() > bbox.getMaxX()) {
252  // clip span x1 to bbox
253  sp->_x1 = bbox.getMaxX();
254  }
255  ++it;
256  }
257 
258  Footprint::PeakList::iterator pit = _peaks.begin();
259  for (; pit != _peaks.end();) {
260  Peak *pk = pit->get();
261  if (!bbox.contains(geom::Point2I(pk->getIx(), pk->getIy()))) {
262  pit = _peaks.erase(pit);
263  continue;
264  }
265  ++pit;
266  }
267 
268  if (_spans.empty()) {
269  _bbox = geom::Box2I();
270  _area = 0;
271  _normalized = true;
272  } else {
273  _normalized = false;
274  normalize();
275  }
276 }
277 
278 void Footprint::normalize() {
279  if (_normalized) {
280  return;
281  }
282  assert(!_spans.empty());
283  //
284  // Check that the spans are sorted, and (more importantly) that each pixel appears
285  // in only one span
286  //
287  sort(_spans.begin(), _spans.end(), compareSpanByYX());
288 
289  Footprint::SpanList::iterator ptr = _spans.begin(), end = _spans.end();
290  Span *lspan = ptr->get(); // Left span
291  int y = lspan->_y;
292  int x1 = lspan->_x1;
293  _area = lspan->getWidth();
294  int minX = lspan->_x0, minY=y, maxX=x1;
295 
296  ++ptr;
297 
298  for (; ptr != end; ++ptr) {
299  Span *rspan = ptr->get(); // Right span
300  if (rspan->_y == y) {
301  if (rspan->_x0 <= x1 + 1) { // Spans overlap or touch
302  if (rspan->_x1 > x1) { // right span extends left span
303  //update area
304  _area += rspan->_x1 - x1;
305  //update end of current span
306  x1 = lspan->_x1 = rspan->_x1;
307  //update bounds
308  if(x1 > maxX) maxX = x1;
309  }
310 
311  ptr = _spans.erase(ptr);
312  end = _spans.end(); // delete the right span
313  if (ptr == end) {
314  break;
315  }
316 
317  --ptr;
318  continue;
319  }
320  else{
321  _area += rspan->getWidth();
322  if(rspan->_x1 > maxX) maxX = rspan->_x1;
323  }
324  } else {
325  _area += rspan->getWidth();
326  }
327 
328  y = rspan->_y;
329  x1 = rspan->_x1;
330 
331  lspan = rspan;
332  if(lspan->_x0 < minX) minX = lspan->_x0;
333  if(x1 > maxX) maxX = x1;
334  }
335  _bbox = geom::Box2I(geom::Point2I(minX, minY), geom::Point2I(maxX, y));
336 
337  _normalized = true;
338 }
339 
340 Span const& Footprint::addSpan(
341  int const y,
342  int const x0,
343  int const x1
344 ) {
345  if (x1 < x0) {
346  return addSpan(y, x1, x0);
347  }
348 
349  PTR(Span) sp(new Span(y, x0, x1));
350  _spans.push_back(sp);
351 
352  _area += sp->getWidth();
353  _normalized = false;
354 
355  _bbox.include(geom::Point2I(x0, y));
356  _bbox.include(geom::Point2I(x1, y));
357 
358  return *sp.get();
359 }
360 
361 const Span& Footprint::addSpan(
362  Span const& span
363 ) {
364  return addSpan(span._y, span._x0, span._x1);
365 }
366 
367 const Span& Footprint::addSpan(
368  Span const& span,
369  int dx,
370  int dy
371 ) {
372  return addSpan(span._y + dy, span._x0 + dx, span._x1 + dx);
373 }
374 
375 const Span& Footprint::addSpanInSeries(
376  int const y,
377  int const x0,
378  int const x1
379 ) {
380  if (x1 < x0) {
381  return addSpanInSeries(y, x1, x0);
382  }
383  if (_spans.size() == 0) {
384  const Span& s = addSpan(y, x0, x1);
385  _normalized = true;
386  return s;
387  }
388  // merge contiguous spans
389  PTR(Span) lastspan = _spans.back();
390  if ((y == lastspan->getY()) &&
391  (x0 == (lastspan->getX1() + 1))) {
392  // contiguous.
393  lastspan->_x1 = x1;
394  _area += (1 + x1 - x0);
395  _bbox.include(geom::Point2I(x1,y));
396  return *lastspan;
397  }
398  if (!((y > lastspan->getY()) ||
399  (x0 > (lastspan->getX1() + 1)))) {
400  throw LSST_EXCEPT(
401  lsst::pex::exceptions::InvalidParameterError,
402  str(boost::format("addSpanInSeries: new span %i,[%i,%i] is NOT in series after last span "
403  "%i,[%i,%i]") %
404  y % x0 % x1 % lastspan->getY() % lastspan->getX0() % lastspan->getX1()));
405  }
406  const Span& s = addSpan(y, x0, x1);
407  _normalized = true;
408  return s;
409 }
410 
411 void Footprint::shift(
412  int dx,
413  int dy
414 ) {
415  for (SpanList::iterator i = _spans.begin(); i != _spans.end(); ++i){
416  PTR(Span) span = *i;
417 
418  span->_y += dy;
419  span->_x0 += dx;
420  span->_x1 += dx;
421  }
422 
423  _bbox.shift(geom::Extent2I(dx, dy));
424 }
425 
426 geom::Point2D
427 Footprint::getCentroid() const
428 {
429  int n = 0;
430  double xc = 0, yc = 0;
431  for (Footprint::SpanList::const_iterator siter = _spans.begin(); siter != _spans.end(); ++siter) {
432  CONST_PTR(Span) span = *siter;
433  int const y = span->getY();
434  int const x0 = span->getX0();
435  int const x1 = span->getX1();
436  int const npix = x1 - x0 + 1;
437 
438  n += npix;
439  xc += npix*0.5*(x1 + x0);
440  yc += npix*y;
441  }
442  assert(n == _area);
443 
444  return geom::Point2D(xc/_area, yc/_area);
445 }
446 
447 geom::ellipses::Quadrupole
448 Footprint::getShape() const
449 {
450  geom::Point2D cen = getCentroid();
451  double const xc = cen.getX();
452  double const yc = cen.getY();
453 
454  double sumxx = 0, sumxy = 0, sumyy = 0;
455  for (Footprint::SpanList::const_iterator siter = _spans.begin(); siter != _spans.end(); ++siter) {
456  CONST_PTR(Span) span = *siter;
457  int const y = span->getY();
458  int const x0 = span->getX0();
459  int const x1 = span->getX1();
460  int const npix = x1 - x0 + 1;
461 
462  for (int x = x0; x <= x1; ++x) {
463  sumxx += (x - xc)*(x - xc);
464  }
465  sumxy += npix*(0.5*(x1 + x0) - xc)*(y - yc);
466  sumyy += npix*(y - yc)*(y - yc);
467  }
468 
469  return geom::ellipses::Quadrupole(sumxx/_area, sumyy/_area, sumxy/_area);
470 }
471 
472 namespace {
473  /*
474  * Set the pixels in idImage which are in Footprint by adding or
475  * replacing the specified value to the Image
476  *
477  * The ids that are overwritten are returned for the callers deliction
478  */
479  template<bool overwriteId, typename PixelT>
480  void
481  doInsertIntoImage(geom::Box2I const& _region, // unpacked from Footprint
482  Footprint::SpanList const& _spans, // unpacked from Footprint
483  image::Image<PixelT>& idImage, // Image to contain the footprint
484  boost::uint64_t const id, // Add/replace id to idImage for pixels in Footprint
485  geom::Box2I const& region, // Footprint's region (default: getRegion())
486  long const mask=0x0, // Don't overwrite bits in this mask
487  std::set<boost::uint64_t> *oldIds=NULL // if non-NULL, set the IDs that were overwritten
488  )
489  {
490  int width, height, x0, y0;
491  if(!region.isEmpty()) {
492  height = region.getHeight();
493  width = region.getWidth();
494  x0 = region.getMinX();
495  y0 = region.getMinY();
496  } else {
497  height = _region.getHeight();
498  width = _region.getWidth();
499  x0 = _region.getMinX();
500  y0 = _region.getMinY();
501  }
502 
503  if (width != idImage.getWidth() || height != idImage.getHeight()) {
504  throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,
505  str(boost::format("Image of size (%dx%d) doesn't match "
506  "Footprint's host Image of size (%dx%d)") %
507  idImage.getWidth() % idImage.getHeight() % width % height));
508  }
509 
510  if (id & mask) {
511  throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,
512  str(boost::format("Id 0x%x sets bits in the protected mask 0x%x") % id % mask));
513  }
514 
515  typename std::set<boost::uint64_t>::const_iterator pos; // hint on where to insert into oldIds
516  if (oldIds) {
517  pos = oldIds->begin();
518  }
519  for (Footprint::SpanList::const_iterator spi = _spans.begin(); spi != _spans.end(); ++spi) {
520  CONST_PTR(Span) span = *spi;
521 
522  int const sy0 = span->getY() - y0;
523  if (sy0 < 0 || sy0 >= height) {
524  continue;
525  }
526 
527  int sx0 = span->getX0() - x0;
528  if (sx0 < 0) {
529  sx0 = 0;
530  }
531  int sx1 = span->getX1() - x0;
532  int const swidth = (sx1 >= width) ? width - sx0 : sx1 - sx0 + 1;
533 
534  for (typename image::Image<PixelT>::x_iterator ptr = idImage.x_at(sx0, sy0),
535  end = ptr + swidth; ptr != end; ++ptr) {
536  if (overwriteId) {
537  long val = *ptr & ~mask;
538  if (val != 0 and oldIds != NULL) {
539  pos = oldIds->insert(pos, val); // update our hint, pos
540  }
541  *ptr = (*ptr & mask) + id;
542  } else {
543  *ptr += id;
544  }
545  }
546  }
547  }
548 }
549 
550 template<typename PixelT>
551 void
552 Footprint::clipToNonzero(typename image::Image<PixelT> const& img) {
553  typedef lsst::afw::image::Image<PixelT> ImageT;
554  int const ix0 = img.getX0();
555  int const iy0 = img.getY0();
556  const PixelT zero = 0;
557 
558  for (SpanList::iterator s = _spans.begin(); s < _spans.end(); ++s) {
559  int const y = (*s)->getY();
560  int const x0 = (*s)->getX0();
561  int const x1 = (*s)->getX1();
562  typename ImageT::x_iterator img_it = img.row_begin(y - iy0) + (x0 - ix0);
563  int leftx, rightx;
564  // find zero pixels on the left...
565  for (leftx = x0; leftx <= x1; ++leftx, ++img_it) {
566  if (*img_it != zero) {
567  break;
568  }
569  }
570  if (leftx > x1) {
571  // whole span is zero; drop it.
572  _normalized = false;
573  _spans.erase(s);
574  s--;
575  continue;
576  }
577  // find zero pixels on the right...
578  img_it = img.row_begin(y - iy0) + (x1 - ix0);
579  for (rightx = x1; rightx >= leftx; --rightx, --img_it) {
580  if (*img_it != zero) {
581  break;
582  }
583  }
584  if (leftx != x0) {
585  (*s)->_x0 = leftx;
586  _normalized = false;
587  }
588  if (rightx != x1) {
589  (*s)->_x1 = rightx;
590  _normalized = false;
591  }
592  }
593 
594  if (_spans.empty()) {
595  _bbox = geom::Box2I();
596  _area = 0;
597  _normalized = true;
598  }
599 }
600 
601 template<typename PixelT>
602 void
603 Footprint::insertIntoImage(
604  typename image::Image<PixelT>& idImage,
605  boost::uint64_t const id,
606  geom::Box2I const& region
607 ) const
608 {
609  static_cast<void>(doInsertIntoImage<false>(_region, _spans, idImage, id, region));
610 }
611 
612 template<typename PixelT>
613 void
614 Footprint::insertIntoImage(
615  image::Image<PixelT>& idImage,
616  boost::uint64_t const id,
617  bool overwriteId,
618  long const mask,
619  std::set<boost::uint64_t> *oldIds,
620  geom::Box2I const& region
621 ) const
622 {
623  if (id > std::size_t(std::numeric_limits<PixelT>::max())) {
624  throw LSST_EXCEPT(
625  lsst::pex::exceptions::OutOfRangeError,
626  "id out of range for image type"
627  );
628  }
629  if (overwriteId) {
630  doInsertIntoImage<true>(_region, _spans, idImage, id, region, mask, oldIds);
631  } else {
632  doInsertIntoImage<false>(_region, _spans, idImage, id, region, mask, oldIds);
633  }
634 }
635 
636 // Factory class used for table-based persistence; invoked via registry in afw::table::io
637 class FootprintFactory : public table::io::PersistableFactory {
638 public:
639 
640  virtual PTR(table::io::Persistable)
641  read(InputArchive const & archive, CatalogVector const & catalogs) const {
642  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
643  PTR(Footprint) result = boost::make_shared<Footprint>();
644  result->readSpans(catalogs.front());
645  result->readPeaks(catalogs.back());
646  return result;
647  }
648 
649  explicit FootprintFactory(std::string const & name) : table::io::PersistableFactory(name) {}
650 
651 };
652 
653 namespace {
654 
655 // Singleton helper class that manages the schema and keys for persisting a Footprint
656 class FootprintPersistenceHelper : private boost::noncopyable {
657 public:
658  table::Schema spanSchema;
659  table::Key<int> spanY;
660  table::Key<int> spanX0;
661  table::Key<int> spanX1;
662  table::Schema peakSchema;
663  table::Key<float> peakX;
664  table::Key<float> peakY;
665  table::Key<float> peakValue;
666 
667  static FootprintPersistenceHelper const & get() {
668  static FootprintPersistenceHelper instance;
669  return instance;
670  }
671 
672 private:
673  FootprintPersistenceHelper() :
674  spanSchema(),
675  spanY(spanSchema.addField<int>("y", "row position of span", "pixels")),
676  spanX0(spanSchema.addField<int>("x0", "first column of span (inclusive)", "pixels")),
677  spanX1(spanSchema.addField<int>("x1", "first column of span (inclusive)", "pixels")),
678  peakSchema(),
679  peakX(peakSchema.addField<float>("x", "column position of peak", "pixels")),
680  peakY(peakSchema.addField<float>("y", "column position of peak", "pixels")),
681  peakValue(peakSchema.addField<float>("value", "value of peak pixel", "pixels"))
682  {
683  spanSchema.getCitizen().markPersistent();
684  peakSchema.getCitizen().markPersistent();
685  }
686 };
687 
688 std::string getFootprintPersistenceName() { return "Footprint"; }
689 
690 // Insert the factory into the registry (instantiating an instance is sufficient, because
691 // the code that does the work is in the base class ctor)
692 FootprintFactory registration(getFootprintPersistenceName());
693 
694 } // anonymous
695 
696 std::string Footprint::getPersistenceName() const { return getFootprintPersistenceName(); }
697 
698 std::string Footprint::getPythonModule() const { return "lsst.afw.detection"; }
699 
700 void Footprint::write(OutputArchiveHandle & handle) const {
701  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
702  afw::table::BaseCatalog spanCat = handle.makeCatalog(keys.spanSchema);
703  spanCat.reserve(_spans.size());
704  for (SpanList::const_iterator i = _spans.begin(); i != _spans.end(); ++i) {
705  PTR(afw::table::BaseRecord) record = spanCat.addNew();
706  record->set(keys.spanY, (**i).getY());
707  record->set(keys.spanX0, (**i).getX0());
708  record->set(keys.spanX1, (**i).getX1());
709  }
710  handle.saveCatalog(spanCat);
711  afw::table::BaseCatalog peakCat = handle.makeCatalog(keys.peakSchema);
712  peakCat.reserve(_peaks.size());
713  for (PeakList::const_iterator i = _peaks.begin(); i != _peaks.end(); ++i) {
714  PTR(afw::table::BaseRecord) record = peakCat.addNew();
715  record->set(keys.peakX, (**i).getFx());
716  record->set(keys.peakY, (**i).getFy());
717  record->set(keys.peakValue, (**i).getPeakValue());
718  }
719  handle.saveCatalog(peakCat);
720 }
721 
722 void Footprint::readSpans(afw::table::BaseCatalog const & spanCat) {
723  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
724  for (afw::table::BaseCatalog::const_iterator i = spanCat.begin(); i != spanCat.end(); ++i) {
725  addSpan(i->get(keys.spanY), i->get(keys.spanX0), i->get(keys.spanX1));
726  }
727 }
728 
729 void Footprint::readPeaks(afw::table::BaseCatalog const & peakCat) {
730  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
731  for (afw::table::BaseCatalog::const_iterator i = peakCat.begin(); i != peakCat.end(); ++i) {
732  _peaks.push_back(
733  boost::make_shared<Peak>(i->get(keys.peakX), i->get(keys.peakY), i->get(keys.peakValue))
734  );
735  }
736 }
737 
738 template <typename Archive>
739 void Footprint::serialize(Archive & ar, const unsigned int version) {
740  ar & make_nvp("spans", _spans);
741  ar & make_nvp("peaks", _peaks);
742  ar & make_nvp("area", _area);
743  ar & make_nvp("normalized", _normalized);
744 
745  int x0, y0, width, height;
746  int rx0, ry0, rwidth, rheight;
747  if(Archive::is_saving::value) {
748  geom::Box2I const & bbox = getBBox();
749  x0 = bbox.getMinX();
750  y0 = bbox.getMinY();
751  width = bbox.getWidth();
752  height = bbox.getHeight();
753 
754  geom::Box2I const & region = getRegion();
755  rx0 = region.getMinX();
756  ry0 = region.getMinY();
757  rwidth = region.getWidth();
758  rheight = region.getHeight();
759  }
760 
761  ar & make_nvp("x0", x0) & make_nvp("y0", y0) & make_nvp("width", width) & make_nvp("height", height);
762  ar & make_nvp("rx0", rx0) & make_nvp("ry0", ry0) & make_nvp("rwidth", rwidth)
763  & make_nvp("rheight", rheight);
764 
765  if(Archive::is_loading::value) {
766  _bbox = geom::BoxI(geom::Point2I(x0, y0), geom::Extent2I(width, height));
767  _region = geom::BoxI(geom::Point2I(rx0, ry0), geom::Extent2I(rwidth, rheight));
768  }
769 }
770 
771 template void Footprint::serialize(boost::archive::text_oarchive &, unsigned int const);
772 template void Footprint::serialize(boost::archive::text_iarchive &, unsigned int const);
773 template void Footprint::serialize(boost::archive::xml_oarchive &, unsigned int const);
774 template void Footprint::serialize(boost::archive::xml_iarchive &, unsigned int const);
775 template void Footprint::serialize(boost::archive::binary_oarchive &, unsigned int const);
776 template void Footprint::serialize(boost::archive::binary_iarchive &, unsigned int const);
777 
778 Footprint & Footprint::operator=(Footprint & other) {
779  _region = other._region;
780 
781  //deep copy spans
782  _spans = SpanList();
783  _spans.reserve(other._spans.size());
784  for(SpanList::const_iterator i(other._spans.begin());
785  i != other._spans.end(); ++i
786  ) {
787  addSpan(**i);
788  }
789  _area = other._area;
790  _normalized = other._normalized;
791  _bbox = other._bbox;
792 
793  //deep copy peaks
794  _peaks = PeakList();
795  _peaks.reserve(other._peaks.size());
796  for(PeakList::iterator i(other._peaks.begin()); i != other._peaks.end(); ++i) {
797  _peaks.push_back(PTR(Peak)(new Peak(**i)));
798  }
799  return *this;
800 }
801 
802 template<typename MaskT>
803 void Footprint::intersectMask(
804  lsst::afw::image::Mask<MaskT> const & mask,
805  MaskT const bitmask
806 ) {
807  geom::Box2I maskBBox = mask.getBBox();
808 
809  //this operation makes no sense on non-normalized footprints.
810  //make sure this is normalized
811  normalize();
812 
813  SpanList::iterator s(_spans.begin());
814  while((*s)->getY() < maskBBox.getMinY() && s != _spans.end()){
815  ++s;
816  }
817 
818 
819  int x0, x1, y;
820  SpanList maskedSpans;
821  int maskedArea=0;
822  for( ; s != _spans.end(); ++s) {
823  y = (*s)->getY();
824 
825  if (y > maskBBox.getMaxY())
826  break;
827 
828  x0 = (*s)->getX0();
829  x1 = (*s)->getX1();
830 
831  if(x1 < maskBBox.getMinX() || x0 > maskBBox.getMaxX()) {
832  //span is entirely outside the image mask. cannot be used
833  continue;
834  }
835 
836  //clip the span to be within the mask
837  if(x0 < maskBBox.getMinX()) x0 = maskBBox.getMinX();
838  if(x1 > maskBBox.getMaxX()) x1 = maskBBox.getMaxX();
839 
840  //Image iterators are always specified with respect to (0,0)
841  //regardless what the image::XY0 is set to.
842  typename image::Mask<MaskT>::const_x_iterator mIter = mask.x_at(
843  x0 - maskBBox.getMinX(), y - maskBBox.getMinY()
844  );
845 
846  //loop over all span locations, slicing the span at maskedPixels
847  for(int x = x0; x <= x1; ++x, ++mIter) {
848  if((*mIter & bitmask) != 0) {
849  //masked pixel found within span
850  if (x > x0) {
851  //add beginning of span to the output
852  //the fixed span contains all the unmasked pixels up to,
853  //but not including this masked pixel
854  PTR(Span) maskedSpan(new Span(y, x0, x- 1));
855  maskedSpans.push_back(maskedSpan);
856  maskedArea += maskedSpan->getWidth();
857  }
858  //set the next Span to start after this pixel
859  x0 = x + 1;
860  }
861  }
862 
863  //add last section of span
864  if(x0 <= x1) {
865  PTR(Span) maskedSpan(new Span(y, x0, x1));
866  maskedSpans.push_back(maskedSpan);
867  maskedArea += maskedSpan->getWidth();
868  }
869  }
870  _area = maskedArea;
871  _spans = maskedSpans;
872  _bbox.clip(maskBBox);
873 }
874 
875 
876 PTR(Footprint) Footprint::transform(
877  image::Wcs const& source,
878  image::Wcs const& target,
879  geom::Box2I const& region,
880  bool doClip
881 ) const {
882  // Transform the original bounding box
883  geom::Box2I const& fpBox = getBBox(); // Original bounding box
884  geom::Box2D tBoxD;
885  // If slow, could consider linearising the WCSes and combining the
886  // linear versions to a single transform, and then using that to
887  // transform all the points.
888  tBoxD.include(transformPoint(fpBox.getMinX(), fpBox.getMinY(), source, target));
889  tBoxD.include(transformPoint(fpBox.getMinX(), fpBox.getMaxY(), source, target));
890  tBoxD.include(transformPoint(fpBox.getMaxX(), fpBox.getMinY(), source, target));
891  tBoxD.include(transformPoint(fpBox.getMaxX(), fpBox.getMaxY(), source, target));
892  geom::Box2I tBoxI(tBoxD);
893 
894  // enumerate points in the new bbox that, when reverse-transformed, are within the given footprint.
895  PTR(Footprint) fpNew = boost::make_shared<Footprint>(0, region);
896 
897  for (int y = tBoxI.getBeginY(); y < tBoxI.getEndY(); ++y) {
898  bool inSpan = false; // Are we in a span?
899  int start = -1; // Start of span
900 
901  for (int x = tBoxI.getBeginX(); x < tBoxI.getEndX(); ++x) {
902  lsst::afw::geom::Point2D const& p = transformPoint(x, y, target, source);
903  int const xSource = std::floor(0.5 + p.getX());
904  int const ySource = std::floor(0.5 + p.getY());
905 
906  if (contains(lsst::afw::geom::Point2I(xSource, ySource))) {
907  if (!inSpan) {
908  inSpan = true;
909  start = x;
910  }
911  } else if (inSpan) {
912  inSpan = false;
913  fpNew->addSpan(y, start, x - 1);
914  }
915  }
916  if (inSpan) {
917  fpNew->addSpan(y, start, tBoxI.getMaxX());
918  }
919  }
920  if (doClip) {
921  fpNew->clipTo(region);
922  }
923  return fpNew;
924 }
925 
931 bool _checkNormalized(Footprint const& foot) {
932  Footprint copy(foot);
933  copy.normalize();
934  if (copy.getArea() != foot.getArea()) {
935  return false;
936  }
937  if (copy.getSpans().size() != foot.getSpans().size()) {
938  return false;
939  }
940  Footprint::SpanList const& spansa = foot.getSpans();
941  Footprint::SpanList const& spansb = copy.getSpans();
942  Footprint::SpanList::const_iterator spa = spansa.begin();
943  Footprint::SpanList::const_iterator spb = spansb.begin();
944  for (; spa != spansa.end(); ++spa, ++spb) {
945  if ((*spa)->getY() != (*spb)->getY()) {
946  return false;
947  }
948  if ((*spa)->getX0() != (*spb)->getX0()) {
949  return false;
950  }
951  if ((*spa)->getX1() != (*spb)->getX1()) {
952  return false;
953  }
954  }
955  return true;
956 }
957 
958 /************************************************************************************************************/
959 
960 template<typename MaskT>
961 PTR(Footprint) footprintAndMask(
962  PTR(Footprint) const& fp,
963  typename lsst::afw::image::Mask<MaskT>::Ptr const& mask,
964  MaskT const bitmask
965 ) {
966  PTR(Footprint) newFp(new Footprint());
967  return newFp;
968 }
969 
970 /************************************************************************************************************/
971 
972 template<typename MaskT>
974  image::Mask<MaskT> *mask,
975  Footprint const& foot,
976  MaskT const bitmask
977 ) {
978 
979  int const width = static_cast<int>(mask->getWidth());
980  int const height = static_cast<int>(mask->getHeight());
981 
982  for (Footprint::SpanList::const_iterator siter = foot.getSpans().begin();
983  siter != foot.getSpans().end(); ++siter) {
984  CONST_PTR(Span) span = *siter;
985  int const y = span->getY() - mask->getY0();
986  if (y < 0 || y >= height) {
987  continue;
988  }
989 
990  int x0 = span->getX0() - mask->getX0();
991  int x1 = span->getX1() - mask->getX0();
992  x0 = (x0 < 0) ? 0 : (x0 >= width ? width - 1 : x0);
993  x1 = (x1 < 0) ? 0 : (x1 >= width ? width - 1 : x1);
994 
995  for (typename image::Image<MaskT>::x_iterator ptr = mask->x_at(x0, y),
996  end = mask->x_at(x1 + 1, y); ptr != end; ++ptr) {
997  *ptr |= bitmask;
998  }
999  }
1000 
1001  return bitmask;
1002 }
1003 
1004 /************************************************************************************************************/
1005 
1006 template<typename MaskT>
1008  image::Mask<MaskT> *mask,
1009  Footprint const& foot,
1010  MaskT const bitmask
1011 ) {
1012  int const width = static_cast<int>(mask->getWidth());
1013  int const height = static_cast<int>(mask->getHeight());
1014 
1015  for (Footprint::SpanList::const_iterator siter = foot.getSpans().begin();
1016  siter != foot.getSpans().end(); ++siter) {
1017  CONST_PTR(Span) span = *siter;
1018  int const y = span->getY() - mask->getY0();
1019  if (y < 0 || y >= height) {
1020  continue;
1021  }
1022 
1023  int x0 = span->getX0() - mask->getX0();
1024  int x1 = span->getX1() - mask->getX0();
1025  x0 = (x0 < 0) ? 0 : (x0 >= width ? width - 1 : x0);
1026  x1 = (x1 < 0) ? 0 : (x1 >= width ? width - 1 : x1);
1027 
1028  for (typename image::Image<MaskT>::x_iterator ptr = mask->x_at(x0, y),
1029  end = mask->x_at(x1 + 1, y); ptr != end; ++ptr) {
1030  *ptr &= ~bitmask;
1031  }
1032  }
1033 
1034  return bitmask;
1035 }
1036 
1037 /************************************************************************************************************/
1038 
1039 template<typename MaskT>
1041  image::Mask<MaskT> *mask,
1042  std::vector<PTR(Footprint)> const& footprints,
1043  MaskT const bitmask
1044 ) {
1045  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1046  fiter != footprints.end(); ++fiter) {
1047  (void)setMaskFromFootprint(mask, **fiter, bitmask);
1048  }
1049 
1050  return bitmask;
1051 }
1052 
1053 /************************************************************************************************************/
1054 
1055 template<typename MaskT>
1057  image::Mask<MaskT> *mask,
1058  CONST_PTR(std::vector<PTR(Footprint)>) const & footprints,
1059  MaskT const bitmask
1060  ) {
1061  return setMaskFromFootprintList(mask, *footprints, bitmask);
1062 }
1063 
1064 /************************************************************************************************************/
1065 namespace {
1066 template<typename ImageT>
1067 class SetFootprint : public FootprintFunctor<ImageT> {
1068 public:
1069  SetFootprint(ImageT const& image,
1070  typename ImageT::Pixel value) :
1071  FootprintFunctor<ImageT>(image), _value(value) {}
1072 
1073 
1074  void operator()(typename ImageT::xy_locator loc, int, int) {
1075  *loc = _value;
1076  }
1077 private:
1078  typename ImageT::Pixel _value;
1079 };
1080 }
1081 
1082 template<typename ImageT>
1083 typename ImageT::Pixel setImageFromFootprint(
1084  ImageT *image,
1085  Footprint const& foot,
1086  typename ImageT::Pixel const value
1087 ) {
1088  SetFootprint<ImageT> setit(*image, value);
1089  setit.apply(foot);
1090 
1091  return value;
1092 }
1093 
1094 template<typename ImageT>
1095 typename ImageT::Pixel setImageFromFootprintList(
1096  ImageT *image,
1097  CONST_PTR(std::vector<PTR(Footprint)>) footprints,
1098  typename ImageT::Pixel const value
1099  ) {
1100  return setImageFromFootprintList(image, *footprints, value);
1101 }
1102 
1103 template<typename ImageT>
1104 typename ImageT::Pixel setImageFromFootprintList(
1105  ImageT *image,
1106  std::vector<PTR(Footprint)> const& footprints,
1107  typename ImageT::Pixel const value
1108 ) {
1109  SetFootprint<ImageT> setit(*image, value);
1110  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin(),
1111  end = footprints.end(); fiter != end; ++fiter) {
1112  setit.apply(**fiter);
1113  }
1114 
1115  return value;
1116 }
1117 
1118 /************************************************************************************************************/
1119 /*
1120  * Worker routine for the pmSetFootprintArrayIDs/pmSetFootprintID (and pmMergeFootprintArrays)
1121  */
1122 template <typename IDPixelT>
1123 static void set_footprint_id(
1124  typename image::Image<IDPixelT>::Ptr idImage, // the image to set
1125  Footprint const& foot, // the footprint to insert
1126  int const id, // the desired ID
1127  int dx=0, int dy=0 // Add these to all x/y in the Footprint
1128 ) {
1129  for (Footprint::SpanList::const_iterator i = foot.getSpans().begin();
1130  i != foot.getSpans().end(); ++i) {
1131  CONST_PTR(Span) span = *i;
1132  for (typename image::Image<IDPixelT>::x_iterator ptr =
1133  idImage->x_at(span->getX0() + dx, span->getY() + dy),
1134  end = ptr + span->getWidth(); ptr != end; ++ptr) {
1135  *ptr = id;
1136  }
1137  }
1138 }
1139 
1140 template <typename IDPixelT>
1141 static void
1142 set_footprint_array_ids(
1143  typename image::Image<IDPixelT>::Ptr idImage, // the image to set
1144  std::vector<PTR(Footprint)> const& footprints, // the footprints to insert
1145  bool const relativeIDs // show IDs starting at 0, not Footprint->id
1146 ) {
1147  int id = 0; // first index will be 1
1148 
1149  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1150  fiter != footprints.end(); ++fiter) {
1151  CONST_PTR(Footprint) foot = *fiter;
1152 
1153  if (relativeIDs) {
1154  ++id;
1155  } else {
1156  id = foot->getId();
1157  }
1158 
1159  set_footprint_id<IDPixelT>(idImage, *foot, id);
1160  }
1161 }
1162 
1163 template void set_footprint_array_ids<int>(
1164  image::Image<int>::Ptr idImage,
1165  std::vector<PTR(Footprint)> const& footprints,
1166  bool const relativeIDs);
1167 
1168 /******************************************************************************/
1169 /*
1170  * Set an image to the value of footprint's ID wherever they may fall
1171  *
1172  * @param footprints the footprints to insert
1173  * @param relativeIDs show the IDs starting at 1, not pmFootprint->id
1174  */
1175 template <typename IDImageT>
1176 typename boost::shared_ptr<image::Image<IDImageT> > setFootprintArrayIDs(
1177  std::vector<PTR(Footprint)> const& footprints,
1178  bool const relativeIDs
1179 ) {
1180  std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1181  if (fiter == footprints.end()) {
1182  throw LSST_EXCEPT(
1183  lsst::pex::exceptions::InvalidParameterError,
1184  "You didn't provide any footprints"
1185  );
1186  }
1187  CONST_PTR(Footprint) foot = *fiter;
1188 
1189  typename image::Image<IDImageT>::Ptr idImage(
1190  new image::Image<IDImageT>(foot->getRegion())
1191  );
1192  *idImage = 0;
1193  /*
1194  * do the work
1195  */
1196  set_footprint_array_ids<IDImageT>(idImage, footprints, relativeIDs);
1197 
1198  return idImage;
1199 }
1200 
1201 template image::Image<int>::Ptr setFootprintArrayIDs(
1202  std::vector<PTR(Footprint)> const& footprints,
1203  bool const relativeIDs);
1204 /*
1205  * Set an image to the value of Footprint's ID wherever it may fall
1206  */
1207 template <typename IDImageT>
1208 typename boost::shared_ptr<image::Image<IDImageT> > setFootprintID(
1209  CONST_PTR(Footprint)& foot, // the Footprint to insert
1210  int const id // the desired ID
1211  ) {
1212  typename image::Image<IDImageT>::Ptr idImage(new image::Image<IDImageT>(foot->getBBox()));
1213  *idImage = 0;
1214  /*
1215  * do the work
1216  */
1217  set_footprint_id<IDImageT>(idImage, *foot, id);
1218 
1219  return idImage;
1220 }
1221 
1222 template image::Image<int>::Ptr setFootprintID(CONST_PTR(Footprint)& foot, int const id);
1223 
1224 namespace {
1231 class StructuringElement
1232 {
1233 public:
1234  enum class Shape { CIRCLE, DIAMOND };
1235  typedef std::vector<Span>::const_iterator const_iterator;
1236  StructuringElement(Shape shape, int radius);
1237  StructuringElement(int left, int right, int up, int down);
1238  const_iterator begin() const { return _widths.begin(); }
1239  const_iterator end() const { return _widths.end(); }
1240  int getYRange() const { return _yRange; }
1241 
1242 private:
1243  std::vector<Span> _widths;
1244  int _yRange;
1245 };
1246 
1252 StructuringElement::StructuringElement(Shape shape, int radius) {
1253  _yRange = 2*radius + 1;
1254  _widths.reserve(_yRange);
1255  switch (shape) {
1256  case Shape::CIRCLE:
1257  for (auto dy = -radius; dy <= radius; dy++) {
1258  int dx = static_cast<int>(sqrt(radius*radius - dy*dy));
1259  _widths.push_back(Span(dy, -dx, dx));
1260  }
1261  break;
1262  case Shape::DIAMOND:
1263  for (auto dy = -radius; dy <= radius; dy++) {
1264  int dx = radius - abs(dy);
1265  _widths.push_back(Span(dy, -dx, dx));
1266  }
1267  break;
1268  }
1269 }
1270 
1275 StructuringElement::StructuringElement(int left, int right, int up, int down) {
1276  _yRange = up + down + 1;
1277  _widths.reserve(_yRange);
1278  for (auto dy = 1; dy <= up; dy++) {
1279  _widths.push_back(Span(dy, 0, 0));
1280  }
1281  for (auto dy = -1; dy >= -down; dy--) {
1282  _widths.push_back(Span(dy, 0, 0));
1283  }
1284  _widths.push_back(Span(0, -left, right));
1285 }
1286 
1291 PTR(Footprint) growFootprintImpl(
1292  Footprint const& foot,
1293  StructuringElement const& element
1294 ) {
1295  // Create an empty footprint covering foot's region.
1296  PTR(Footprint) grown(new Footprint(0, foot.getRegion()));
1297 
1298  // Iterate over foot & structuring element adding spans to the empty
1299  // footprint.
1300  for (auto spanIter = foot.getSpans().begin(); spanIter != foot.getSpans().end(); spanIter++) {
1301  for (auto it = element.begin(); it != element.end(); it++) {
1302  int xmin = (*spanIter)->getX0() + it->getX0();
1303  int xmax = (*spanIter)->getX1() + it->getX1();
1304  grown->addSpan((*spanIter)->getY() + it->getY(), xmin, xmax);
1305  }
1306  }
1307 
1308  grown->normalize();
1309 
1310  return grown;
1311 }
1312 
1322 struct PrimaryRun {
1323  int m, y, xmin, xmax;
1324 };
1325 
1330 bool comparePrimaryRun(PrimaryRun const& first, PrimaryRun const& second) {
1331  if (first.y != second.y) {
1332  return first.y < second.y;
1333  } else if (first.m != second.m) {
1334  return first.m < second.m;
1335  } else {
1336  return first.xmin < second.xmin;
1337  }
1338 }
1339 
1340 class ComparePrimaryRunY{
1341 public:
1342  bool operator()(PrimaryRun const& pr, int yval) {
1343  return pr.y < yval;
1344  }
1345  bool operator()(int yval, PrimaryRun const& pr) {
1346  return yval < pr.y;
1347  }
1348 };
1349 
1350 class ComparePrimaryRunM{
1351 public:
1352  bool operator()(PrimaryRun const& pr, int mval) {
1353  return pr.m < mval;
1354  }
1355  bool operator()(int mval, PrimaryRun const& pr) {
1356  return mval < pr.m;
1357  }
1358 };
1359 
1364 PTR(Footprint) shrinkFootprintImpl(
1365  Footprint const& foot,
1366  StructuringElement const& element
1367 ) {
1368  // Create an empty FootprintSet covering the input region
1369  PTR(Footprint) shrunk(new Footprint(0, foot.getRegion()));
1370 
1371  // Calculate all possible primary runs.
1372  std::vector<PrimaryRun> primaryRuns;
1373  for (auto spanIter = foot.getSpans().begin(); spanIter != foot.getSpans().end(); ++spanIter) {
1374  int m = 0;
1375  for (auto it = element.begin(); it != element.end(); ++it, ++m) {
1376  if ((it->getX1() - it->getX0()) <= ((*spanIter)->getX1() - (*spanIter)->getX0())) {
1377  int xmin = (*spanIter)->getX0() - it->getX0();
1378  int xmax = (*spanIter)->getX1() - it->getX1();
1379  int y = (*spanIter)->getY() - it->getY();
1380  primaryRuns.push_back(PrimaryRun({m, y, xmin, xmax}));
1381  }
1382  }
1383  }
1384 
1385  // Iterate over the primary runs in such a way that we consider all values
1386  // of m for given y, then all m for y+1, etc.
1387  std::sort(primaryRuns.begin(), primaryRuns.end(), comparePrimaryRun);
1388 
1389  for (int y = primaryRuns.front().y; y <= primaryRuns.back().y; ++y) {
1390  auto yRange = std::equal_range(primaryRuns.begin(), primaryRuns.end(), y, ComparePrimaryRunY());
1391 
1392  // Discard runs for any value of y for which we find fewer groups than
1393  // M, the total Y range of the structuring element. This is step 3.1
1394  // of the Kim et al. algorithm.
1395  if (std::distance(yRange.first, yRange.second) < element.getYRange()) {
1396  continue;
1397  }
1398 
1399  // "good" runs are those which are covered by each value of m, ie by
1400  // each row in the structuring element. Our algorithm will consider
1401  // each value of m in turn, gradually whittling down the list of good
1402  // runs, then finally convert the remainder into Spans and add them to
1403  // the shrunken Footprint.
1404  std::list<PrimaryRun> goodRuns;
1405 
1406  for (int m = 0; m < element.getYRange(); ++m) {
1407  auto mRange = std::equal_range(yRange.first, yRange.second, m, ComparePrimaryRunM());
1408  if (mRange.first == mRange.second) {
1409  // If a particular m is missing, we know that this y contains
1410  // no good runs; this is equivalent to Kim et al. step 3.2.
1411  goodRuns.clear();
1412  } else {
1413  // Consolidate all primary runs at this m so that they
1414  // don't overlap.
1415  std::list<PrimaryRun> candidateRuns;
1416  int start_x = mRange.first->xmin;
1417  int end_x = mRange.first->xmax;
1418  for (auto run = mRange.first+1; run != mRange.second; ++run) {
1419  if (run->xmin > end_x) {
1420  // Start of a new run
1421  candidateRuns.push_back(PrimaryRun{m, y, start_x, end_x});
1422  start_x = run->xmin;
1423  end_x = run->xmax;
1424  } else {
1425  // Continuation of an existing run
1426  end_x = run->xmax;
1427  }
1428  }
1429  candidateRuns.push_back(PrimaryRun{m, y, start_x, end_x});
1430 
1431  // Otherwise, calculate the intersection of candidate runs at
1432  // this m with good runs from all previous m.
1433  if (m == 0) {
1434  // For m = 0 we have nothing to compare to; all runs are
1435  // accepted.
1436  std::swap(goodRuns, candidateRuns);
1437  } else {
1438  std::list<PrimaryRun> newlist;
1439  for (auto good = goodRuns.begin(); good != goodRuns.end(); ++good) {
1440  for (auto cand = candidateRuns.begin(); cand != candidateRuns.end(); ++cand) {
1441  int start = std::max(good->xmin, cand->xmin);
1442  int end = std::min(good->xmax, cand->xmax);
1443  if (end >= start) {
1444  newlist.push_back(PrimaryRun({m, y, start, end}));
1445  }
1446  }
1447  }
1448  std::swap(newlist, goodRuns);
1449  }
1450  }
1451  }
1452  for (auto run = goodRuns.begin(); run != goodRuns.end(); ++run) {
1453  shrunk->addSpan(run->y, run->xmin, run->xmax);
1454  }
1455  }
1456 
1457  shrunk->normalize();
1458  return shrunk;
1459 }
1460 }
1461 
1462 /************************************************************************************************************/
1463 
1464 namespace {
1465  PTR(Footprint) _mergeFootprints(Footprint const& aFoot, Footprint const& bFoot) {
1466  PTR(Footprint) foot(new Footprint());
1467 
1468  Footprint::PeakList const& aPeaks = aFoot.getPeaks();
1469  Footprint::PeakList const& bPeaks = bFoot.getPeaks();
1470  Footprint::PeakList & peaks = foot->getPeaks();
1471  peaks.reserve(aPeaks.size() + bPeaks.size());
1472  peaks.insert(peaks.begin(), aPeaks.begin(), aPeaks.end());
1473  peaks.insert(peaks.end()-1, bPeaks.begin(), bPeaks.end());
1474  assert(peaks.size() == (aPeaks.size() + bPeaks.size()));
1475 
1476  Footprint::SpanList const& aSpans = aFoot.getSpans();
1477  Footprint::SpanList const& bSpans = bFoot.getSpans();
1478  Footprint::SpanList::const_iterator aSpan = aSpans.begin();
1479  Footprint::SpanList::const_iterator bSpan = bSpans.begin();
1480  Footprint::SpanList::const_iterator aEnd = aSpans.end();
1481  Footprint::SpanList::const_iterator bEnd = bSpans.end();
1482 
1483  foot->getSpans().reserve(std::max(aSpans.size(), bSpans.size()));
1484 
1485  while ((aSpan != aEnd) && (bSpan != bEnd)) {
1486  int y = (*aSpan)->getY();
1487  int x0 = (*aSpan)->getX0();
1488  int x1 = (*aSpan)->getX1();
1489  int yb = (*bSpan)->getY();
1490  int xb0 = (*bSpan)->getX0();
1491  int xb1 = (*bSpan)->getX1();
1492 
1493  if ((y < yb) || (y == yb && (x1 < (xb0-1)))) {
1494  // A is earlier -- add A
1495  foot->addSpanInSeries(y, x0, x1);
1496  ++aSpan;
1497  continue;
1498  }
1499  if ((yb < y) || (y == yb && (xb1 < (x0-1)))) {
1500  // B is earlier -- add B
1501  foot->addSpanInSeries(yb, xb0, xb1);
1502  ++bSpan;
1503  continue;
1504  }
1505 
1506  assert(yb == y);
1507  // Overlap -- find connected spans from both iterators.
1508  x0 = std::min(x0, xb0);
1509  x1 = std::max(x1, xb1);
1510  // Union all connected spans
1511  ++aSpan;
1512  ++bSpan;
1513  while (true) {
1514  if ((aSpan != aEnd) &&
1515  ((*aSpan)->getY() == y) &&
1516  ((*aSpan)->getX0() <= (x1+1))) {
1517  // *aSpan continues this span.
1518  x1 = std::max(x1, (*aSpan)->getX1());
1519  ++aSpan;
1520  continue;
1521  }
1522  if ((bSpan != bEnd) &&
1523  ((*bSpan)->getY() == y) &&
1524  ((*bSpan)->getX0() <= (x1+1))) {
1525  // *bSpan continues this span.
1526  x1 = std::max(x1, (*bSpan)->getX1());
1527  ++bSpan;
1528  continue;
1529  }
1530  break;
1531  }
1532  foot->addSpanInSeries(y, x0, x1);
1533  }
1534  // At this point either "aSpan" or "bSpan" is at the end.
1535 
1536  // Add any remaining spans from "A".
1537  for (; aSpan != aEnd; ++aSpan) {
1538  foot->addSpanInSeries((*aSpan)->getY(), (*aSpan)->getX0(), (*aSpan)->getX1());
1539  }
1540  // Add any remaining spans from "B".
1541  for (; bSpan != bEnd; ++bSpan) {
1542  foot->addSpanInSeries((*bSpan)->getY(), (*bSpan)->getX0(), (*bSpan)->getX1());
1543  }
1544  return foot;
1545  }
1546 }
1547 
1548 PTR(Footprint) mergeFootprints(Footprint& foot1, Footprint& foot2) {
1549  foot1.normalize();
1550  foot2.normalize();
1551  return _mergeFootprints(foot1, foot2);
1552 }
1553 
1554 PTR(Footprint) mergeFootprints(Footprint const& foot1, Footprint const& foot2) {
1555  if (!foot1.isNormalized() || !foot2.isNormalized()) {
1556  throw LSST_EXCEPT(
1557  lsst::pex::exceptions::InvalidParameterError,
1558  "mergeFootprints(const Footprints) requires normalize()d Footprints.");
1559  }
1560  return _mergeFootprints(foot1, foot2);
1561 }
1562 
1563 /************************************************************************************************************/
1564 
1565 void nearestFootprint(std::vector<PTR(Footprint)> const& foots,
1568 {
1569  /*
1570  * insert the footprints into an image, set all the pixels to the
1571  * Manhattan distance from the nearest set pixel.
1572  */
1573  typedef boost::uint16_t dtype;
1574  typedef boost::uint16_t itype;
1575 
1576  const itype nil = 0xffff;
1577 
1578  const geom::Box2I bbox = argmin->getBBox();
1579  *argmin = 0;
1580  *dist = 0;
1581 
1582  int const x0 = bbox.getMinX();
1583  int const y0 = bbox.getMinY();
1584 
1585  for (size_t i=0; i<foots.size(); ++i) {
1586  // Set all the pixels in the footprint to 1
1587  set_footprint_id<itype>(argmin, *foots[i], i, -x0, -y0);
1588  set_footprint_id<dtype>(dist, *foots[i], 1, -x0, -y0);
1589  }
1590 
1591  int const height = dist->getHeight();
1592  int const width = dist->getWidth();
1593 
1594  // traverse from bottom left to top right
1595  for (int y = 0; y != height; ++y) {
1596  image::Image<dtype>::xy_locator dim = dist->xy_at(0, y);
1597  image::Image<itype>::xy_locator aim = argmin->xy_at(0, y);
1598  for (int x = 0; x != width; ++x, ++dim.x(), ++aim.x()) {
1599  if (dim(0, 0) == 1) {
1600  // first pass and pixel was on, it gets a zero
1601  dim(0, 0) = 0;
1602  // its argmin is already set
1603  } else {
1604  // pixel was off. It is at most the sum of lengths of
1605  // the array away from a pixel that is on
1606  dim(0, 0) = width + height;
1607  aim(0, 0) = nil;
1608  // or one more than the pixel to the north
1609  if (y > 0) {
1610  dtype ndist = dim(0,-1) + 1;
1611  if (ndist < dim(0,0)) {
1612  dim(0,0) = ndist;
1613  aim(0,0) = aim(0,-1);
1614  }
1615  }
1616  // or one more than the pixel to the west
1617  if (x > 0) {
1618  dtype ndist = dim(-1,0) + 1;
1619  if (ndist < dim(0,0)) {
1620  dim(0,0) = ndist;
1621  aim(0,0) = aim(-1,0);
1622  }
1623  }
1624  }
1625  }
1626  }
1627  // traverse from top right to bottom left
1628  for (int y = height - 1; y >= 0; --y) {
1629  image::Image<dtype>::xy_locator dim = dist->xy_at(width-1, y);
1630  image::Image<itype>::xy_locator aim = argmin->xy_at(width-1, y);
1631  for (int x = width - 1; x >= 0; --x, --dim.x(), --aim.x()) {
1632  // either what we had on the first pass or one more than
1633  // the pixel to the south
1634  if (y + 1 < height) {
1635  dtype ndist = dim(0,1) + 1;
1636  if (ndist < dim(0,0)) {
1637  dim(0,0) = ndist;
1638  aim(0,0) = aim(0,1);
1639  }
1640  }
1641  // or one more than the pixel to the east
1642  if (x + 1 < width) {
1643  dtype ndist = dim(1,0) + 1;
1644  if (ndist < dim(0,0)) {
1645  dim(0,0) = ndist;
1646  aim(0,0) = aim(1,0);
1647  }
1648  }
1649  }
1650  }
1651 }
1652 
1653 PTR(Footprint) growFootprint(
1654  Footprint const& foot,
1655  int nGrow,
1656  bool isotropic
1657 ) {
1658  if (nGrow <= 0 || foot.getNpix() == 0 ) {
1659  // Return a new footprint equal to the input.
1660  return PTR(Footprint)(new Footprint(foot));
1661  }
1662 
1663  // An isotropic grow is equivalent to growing with a circular structuring
1664  // element, while a Manhattan grow is equivalent to growing with a
1665  // diamond-shaped element.
1667  Shape shape = isotropic ? Shape::CIRCLE : Shape::DIAMOND;
1668  return growFootprintImpl(foot, StructuringElement(shape, nGrow));
1669 }
1670 
1671 PTR(Footprint) growFootprint(PTR(Footprint) const& foot, int nGrow, bool isotropic) {
1672  return growFootprint(*foot, nGrow, isotropic);
1673 }
1674 
1675 PTR(Footprint) growFootprint(Footprint const& foot,
1676  int nGrow,
1677  bool left,
1678  bool right,
1679  bool up,
1680  bool down
1681  )
1682 {
1683  if (nGrow <= 0 || foot.getNpix() == 0 ) {
1684  // Return a new footprint equal to the input.
1685  return PTR(Footprint)(new Footprint(foot));
1686  }
1687  return growFootprintImpl(foot, StructuringElement(left ? nGrow: 0, right ? nGrow : 0,
1688  up ? nGrow : 0, down ? nGrow : 0));
1689 }
1690 
1691 PTR(Footprint) shrinkFootprint(
1692  Footprint const& foot,
1693  int nShrink,
1694  bool isotropic
1695 ) {
1697  Shape shape = isotropic ? Shape::CIRCLE : Shape::DIAMOND;
1698  return shrinkFootprintImpl(foot, StructuringElement(shape, nShrink));
1699 }
1700 
1701 /************************************************************************************************************/
1702 
1703 std::vector<geom::Box2I> footprintToBBoxList(Footprint const& foot) {
1704  typedef boost::uint16_t ImageT;
1705  geom::Box2I fpBBox = foot.getBBox();
1706  image::Image<ImageT>::Ptr idImage(
1707  new image::Image<ImageT>(fpBBox.getDimensions())
1708  );
1709  *idImage = 0;
1710  int const height = fpBBox.getHeight();
1711  geom::Extent2I shift(fpBBox.getMinX(), fpBBox.getMinY());
1712  foot.insertIntoImage(*idImage, 1, fpBBox);
1713 
1714  std::vector<geom::Box2I> bboxes;
1715  /*
1716  * Our strategy is to find a row of pixels in the Footprint and interpret it as the first
1717  * row of a rectangular set of pixels. We then extend this rectangle upwards as far as it
1718  * will go, and define that as a BBox. We clear all those pixels, and repeat until there
1719  * are none left. I.e. a Footprint will get cut up like this:
1720  *
1721  * .555...
1722  * 22.3314
1723  * 22.331.
1724  * .000.1.
1725  * (as shown in Footprint_1.py)
1726  */
1727 
1728  int y0 = 0; // the first row with non-zero pixels in it
1729  while (y0 < height) {
1730  geom::Box2I bbox; // our next BBox
1731  for (int y = y0; y != height; ++y) {
1732  // Look for a set pixel in this row
1733  image::Image<ImageT>::x_iterator begin = idImage->row_begin(y), end = idImage->row_end(y);
1734  image::Image<ImageT>::x_iterator first = std::find(begin, end, 1);
1735 
1736  if (first != end) { // A pixel is set in this row
1737  image::Image<ImageT>::x_iterator last = std::find(first, end, 0) - 1;
1738  int const x0 = first - begin;
1739  int const x1 = last - begin;
1740 
1741  std::fill(first, last + 1, 0); // clear pixels; we don't want to see them again
1742 
1743  bbox.include(geom::Point2I(x0, y)); // the LLC
1744  bbox.include(geom::Point2I(x1, y)); // the LRC; initial guess for URC
1745 
1746  // we found at least one pixel so extend the BBox upwards
1747  for (++y; y != height; ++y) {
1748  if (std::find(idImage->at(x0, y), idImage->at(x1 + 1, y), 0) != idImage->at(x1 + 1, y)) {
1749  break; // some pixels weren't set, so the BBox stops here, (actually in previous row)
1750  }
1751  std::fill(idImage->at(x0, y), idImage->at(x1 + 1, y), 0);
1752 
1753  bbox.include(geom::Point2I(x1, y)); // the new URC
1754  }
1755 
1756  bbox.shift(shift);
1757  bboxes.push_back(bbox);
1758  } else {
1759  y0 = y + 1;
1760  }
1761  break;
1762  }
1763  }
1764 
1765  return bboxes;
1766 }
1767 
1768 
1769 template<typename ImageOrMaskedImageT>
1770 void
1771 copyWithinFootprint(Footprint const& foot,
1772  PTR(ImageOrMaskedImageT) const input,
1773  PTR(ImageOrMaskedImageT) output) {
1774  Footprint::SpanList spans = foot.getSpans();
1775  for (Footprint::SpanList::iterator sp = spans.begin();
1776  sp != spans.end(); ++sp) {
1777  int y = (*sp)->getY();
1778  int x0 = (*sp)->getX0();
1779  int x1 = (*sp)->getX1();
1780  typename ImageOrMaskedImageT::const_x_iterator initer = input->x_at(
1781  x0 - input->getX0(), y - input->getY0());
1782  typename ImageOrMaskedImageT::x_iterator outiter = output->x_at(
1783  x0 - output->getX0(), y - output->getY0());
1784  for (int x=x0; x <= x1; ++x, ++initer, ++outiter) {
1785  *outiter = *initer;
1786  }
1787  }
1788 }
1789 
1790 
1791 
1792 
1793 #if 0
1794 
1795 /************************************************************************************************************/
1796 /*
1797  * Grow a psArray of pmFootprints isotropically by r pixels, returning a new psArray of new pmFootprints
1798  */
1799 psArray *pmGrowFootprintArray(psArray const *footprints, // footprints to grow
1800  int r) { // how much to grow each footprint
1801  assert (footprints->n == 0 || pmIsFootprint(footprints->data[0]));
1802 
1803  if (footprints->n == 0) { // we don't know the size of the footprint's region
1804  return psArrayAlloc(0);
1805  }
1806  /*
1807  * We'll insert the footprints into an image, then convolve with a disk,
1808  * then extract a footprint from the result --- this is magically what we want.
1809  */
1810  psImage *idImage = pmSetFootprintArrayIDs(footprints, true);
1811  if (r <= 0) {
1812  r = 1; // r == 1 => no grow
1813  }
1814  psKernel *circle = psKernelAlloc(-r, r, -r, r);
1815  assert (circle->image->numRows == 2*r + 1 && circle->image->numCols == circle->image->numRows);
1816  for (int i = 0; i <= r; ++i) {
1817  for (int j = 0; j <= r; ++j) {
1818  if (i*i + j*j <= r*r) {
1819  circle->kernel[i][j] =
1820  circle->kernel[i][-j] =
1821  circle->kernel[-i][j] =
1822  circle->kernel[-i][-j] = 1;
1823  }
1824  }
1825  }
1826 
1827  psImage *grownIdImage = psImageConvolveDirect(idImage, circle); // Here's the actual grow step
1828  psFree(circle);
1829 
1830  psArray *grown = pmFindFootprints(grownIdImage, 0.5, 1); // and here we rebuild the grown footprints
1831  assert (grown != NULL);
1832  psFree(idImage);
1833  psFree(grownIdImage);
1834  /*
1835  * Now assign the peaks appropriately. We could do this more efficiently
1836  * using grownIdImage (which we just freed), but this is easy and probably fast enough
1837  */
1838  psArray const *peaks = pmFootprintArrayToPeaks(footprints);
1839  pmPeaksAssignToFootprints(grown, peaks);
1840  psFree((psArray *)peaks);
1841 
1842  return grown;
1843 }
1844 
1845 /************************************************************************************************************/
1846 /*
1847  * Merge together two psArrays of pmFootprints neither of which is damaged.
1848  *
1849  * The returned psArray may contain elements of the inital psArrays (with
1850  * their reference counters suitable incremented)
1851  */
1852 psArray *pmMergeFootprintArrays(psArray const *footprints1, // one set of footprints
1853  psArray const *footprints2, // the other set
1854  int const includePeaks) { // which peaks to set? 0x1 => footprints1, 0x2 => 2
1855  assert (footprints1->n == 0 || pmIsFootprint(footprints1->data[0]));
1856  assert (footprints2->n == 0 || pmIsFootprint(footprints2->data[0]));
1857 
1858  if (footprints1->n == 0 || footprints2->n == 0) { // nothing to do but put copies on merged
1859  psArray const *old = (footprints1->n == 0) ? footprints2 : footprints1;
1860 
1861  psArray *merged = psArrayAllocEmpty(old->n);
1862  for (int i = 0; i < old->n; ++i) {
1863  psArrayAdd(merged, 1, old->data[i]);
1864  }
1865 
1866  return merged;
1867  }
1868  /*
1869  * We have real work to do as some pmFootprints in footprints2 may overlap
1870  * with footprints1
1871  */
1872  {
1873  pmFootprint *fp1 = footprints1->data[0];
1874  pmFootprint *fp2 = footprints2->data[0];
1875  if (fp1->region.x0 != fp2->region.x0 ||
1876  fp1->region.x1 != fp2->region.x1 ||
1877  fp1->region.y0 != fp2->region.y0 ||
1878  fp1->region.y1 != fp2->region.y1) {
1879  psError(PS_ERR_BAD_PARAMETER_SIZE, true,
1880  "The two pmFootprint arrays correspnond to different-sized regions");
1881  return NULL;
1882  }
1883  }
1884  /*
1885  * We'll insert first one set of footprints then the other into an image, then
1886  * extract a footprint from the result --- this is magically what we want.
1887  */
1888  psImage *idImage = pmSetFootprintArrayIDs(footprints1, true);
1889  set_footprint_array_ids(idImage, footprints2, true);
1890 
1891  psArray *merged = pmFindFootprints(idImage, 0.5, 1);
1892  assert (merged != NULL);
1893  psFree(idImage);
1894  /*
1895  * Now assign the peaks appropriately. We could do this more efficiently
1896  * using idImage (which we just freed), but this is easy and probably fast enough
1897  */
1898  if (includePeaks & 0x1) {
1899  psArray const *peaks = pmFootprintArrayToPeaks(footprints1);
1900  pmPeaksAssignToFootprints(merged, peaks);
1901  psFree((psArray *)peaks);
1902  }
1903 
1904  if (includePeaks & 0x2) {
1905  psArray const *peaks = pmFootprintArrayToPeaks(footprints2);
1906  pmPeaksAssignToFootprints(merged, peaks);
1907  psFree((psArray *)peaks);
1908  }
1909 
1910  return merged;
1911 }
1912 
1913 /************************************************************************************************************/
1914 /*
1915  * Given a psArray of pmFootprints and another of pmPeaks, assign the peaks to the
1916  * footprints in which that fall; if they _don't_ fall in a footprint, add a suitable
1917  * one to the list.
1918  */
1919 psErrorCode
1920 pmPeaksAssignToFootprints(psArray *footprints, // the pmFootprints
1921  psArray const *peaks) { // the pmPeaks
1922  assert (footprints != NULL);
1923  assert (footprints->n == 0 || pmIsFootprint(footprints->data[0]));
1924  assert (peaks != NULL);
1925  assert (peaks->n == 0 || pmIsPeak(peaks->data[0]));
1926 
1927  if (footprints->n == 0) {
1928  if (peaks->n > 0) {
1929  return psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Your list of footprints is empty");
1930  }
1931  return PS_ERR_NONE;
1932  }
1933  /*
1934  * Create an image filled with the object IDs, and use it to assign pmPeaks to the
1935  * objects
1936  */
1937  psImage *ids = pmSetFootprintArrayIDs(footprints, true);
1938  assert (ids != NULL);
1939  assert (ids->type.type == PS_TYPE_S32);
1940  int const y0 = ids->y0;
1941  int const x0 = ids->x0;
1942  int const numRows = ids->numRows;
1943  int const numCols = ids->numCols;
1944 
1945  for (int i = 0; i < peaks->n; ++i) {
1946  pmPeak *peak = peaks->data[i];
1947  int const x = peak->x - x0;
1948  int const y = peak->y - y0;
1949 
1950  assert (x >= 0 && x < numCols && y >= 0 && y < numRows);
1951  int id = ids->data.S32[y][x - x0];
1952 
1953  if (id == 0) { // peak isn't in a footprint, so make one for it
1954  pmFootprint *nfp = pmFootprintAlloc(1, ids);
1955  pmFootprintAddSpan(nfp, y, x, x);
1956  psArrayAdd(footprints, 1, nfp);
1957  psFree(nfp);
1958  id = footprints->n;
1959  }
1960 
1961  assert (id >= 1 && id <= footprints->n);
1962  pmFootprint *fp = footprints->data[id - 1];
1963  psArrayAdd(fp->peaks, 5, peak);
1964  }
1965 
1966  psFree(ids);
1967  //
1968  // Make sure that peaks within each footprint are sorted and unique
1969  //
1970  for (int i = 0; i < footprints->n; ++i) {
1971  pmFootprint *fp = footprints->data[i];
1972  fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
1973 
1974  for (int j = 1; j < fp->peaks->n; ++j) { // check for duplicates
1975  if (fp->peaks->data[j] == fp->peaks->data[j-1]) {
1976  (void)psArrayRemoveIndex(fp->peaks, j);
1977  j--; // we moved everything down one
1978  }
1979  }
1980  }
1981 
1982  return PS_ERR_NONE;
1983 }
1984 
1985 /************************************************************************************************************/
1986  /*
1987  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
1988  * isolated. More precisely, for each peak find the highest coll that you'd have to traverse
1989  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
1990  * starting point, discard the peak.
1991  */
1992 psErrorCode pmFootprintCullPeaks(psImage const *img, // the image wherein lives the footprint
1993  psImage const *weight, // corresponding variance image
1994  pmFootprint *fp, // Footprint containing mortal peaks
1995  float const nsigma_delta, // how many sigma above local background a peak
1996  // needs to be to survive
1997  float const min_threshold) { // minimum permitted coll height
1998  assert (img != NULL);
1999  assert (img->type.type == PS_TYPE_F32);
2000  assert (weight != NULL);
2001  assert (weight->type.type == PS_TYPE_F32);
2002  assert (img->y0 == weight->y0 && img->x0 == weight->x0);
2003  assert (fp != NULL);
2004 
2005  if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
2006  return PS_ERR_NONE;
2007  }
2008 
2009  psRegion subRegion; // desired subregion; 1 larger than bounding box (grr)
2010  subRegion.x0 = fp->bbox.x0;
2011  subRegion.x1 = fp->bbox.x1 + 1;
2012  subRegion.y0 = fp->bbox.y0;
2013  subRegion.y1 = fp->bbox.y1 + 1;
2014  psImage const *subImg = psImageSubset((psImage *)img, subRegion);
2015  psImage const *subWt = psImageSubset((psImage *)weight, subRegion);
2016  assert (subImg != NULL && subWt != NULL);
2017  //
2018  // We need a psArray of peaks brighter than the current peak. We'll fake this
2019  // by reusing the fp->peaks but lying about n.
2020  //
2021  // We do this for efficiency (otherwise I'd need two peaks lists), and we are
2022  // rather too chummy with psArray in consequence. But it works.
2023  //
2024  psArray *brightPeaks = psArrayAlloc(0);
2025  psFree(brightPeaks->data);
2026  brightPeaks->data = psMemIncrRefCounter(fp->peaks->data);// use the data from fp->peaks
2027  //
2028  // The brightest peak is always safe; go through other peaks trying to cull them
2029  //
2030  for (int i = 1; i < fp->peaks->n; ++i) { // n.b. fp->peaks->n can change within the loop
2031  pmPeak const *peak = fp->peaks->data[i];
2032  int x = peak->x - subImg->x0;
2033  int y = peak->y - subImg->y0;
2034  //
2035  // Find the level nsigma below the peak that must separate the peak
2036  // from any of its friends
2037  //
2038  assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
2039  float const stdev = std::sqrt(subWt->data.F32[y][x]);
2040  float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
2041  if (lsst::utils::isnan(threshold) || threshold < min_threshold) {
2042 #if 1 // min_threshold is assumed to be below the detection threshold,
2043  // so all the peaks are pmFootprint, and this isn't the brightest
2044  (void)psArrayRemoveIndex(fp->peaks, i);
2045  i--; // we moved everything down one
2046  continue;
2047 #else
2048 #error n.b. We will be running LOTS of checks at this threshold, so only find the footprint once
2049  threshold = min_threshold;
2050 #endif
2051  }
2052  if (threshold > subImg->data.F32[y][x]) {
2053  threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
2054  }
2055 
2056  int const peak_id = 1; // the ID for the peak of interest
2057  brightPeaks->n = i; // only stop at a peak brighter than we are
2058  pmFootprint *peakFootprint = pmFindFootprintAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
2059  brightPeaks->n = 0; // don't double free
2060  psImage *idImg = pmSetFootprintID(peakFootprint, peak_id);
2061  psFree(peakFootprint);
2062 
2063  int j;
2064  for (j = 0; j < i; ++j) {
2065  pmPeak const *peak2 = fp->peaks->data[j];
2066  int x2 = peak2->x - subImg->x0;
2067  int y2 = peak2->y - subImg->y0;
2068  int const peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
2069 
2070  if (peak2_id == peak_id) { // There's a brighter peak within the footprint above
2071  ; // threshold; so cull our initial peak
2072  (void)psArrayRemoveIndex(fp->peaks, i);
2073  i--; // we moved everything down one
2074  break;
2075  }
2076  }
2077  if (j == i) {
2078  ++j;
2079  }
2080 
2081  psFree(idImg);
2082  }
2083 
2084  brightPeaks->n = 0;
2085  psFree(brightPeaks);
2086  psFree((psImage *)subImg);
2087  psFree((psImage *)subWt);
2088 
2089  return PS_ERR_NONE;
2090 }
2091 
2092 /*
2093  * Cull an entire psArray of pmFootprints
2094  */
2095 psErrorCode
2096 pmFootprintArrayCullPeaks(psImage const *img, // the image wherein lives the footprint
2097  psImage const *weight, // corresponding variance image
2098  psArray *footprints, // array of pmFootprints
2099  float const nsigma_delta, // how many sigma above local background a peak
2100  // needs to be to survive
2101  float const min_threshold) { // minimum permitted coll height
2102  for (int i = 0; i < footprints->n; ++i) {
2103  pmFootprint *fp = footprints->data[i];
2104  if (pmFootprintCullPeaks(img, weight, fp, nsigma_delta, min_threshold) != PS_ERR_NONE) {
2105  return psError(PS_ERR_UNKNOWN, false, "Culling pmFootprint %d", fp->id);
2106  }
2107  }
2108 
2109  return PS_ERR_NONE;
2110 }
2111 
2112 /************************************************************************************************************/
2113 /*
2114  * Extract the peaks in a psArray of pmFootprints, returning a psArray of pmPeaks
2115  */
2116 psArray *pmFootprintArrayToPeaks(psArray const *footprints) {
2117  assert(footprints != NULL);
2118  assert(footprints->n == 0 || pmIsFootprint(footprints->data[0]));
2119 
2120  int npeak = 0;
2121  for (int i = 0; i < footprints->n; ++i) {
2122  pmFootprint const *fp = footprints->data[i];
2123  npeak += fp->peaks->n;
2124  }
2125 
2126  psArray *peaks = psArrayAllocEmpty(npeak);
2127 
2128  for (int i = 0; i < footprints->n; ++i) {
2129  pmFootprint const *fp = footprints->data[i];
2130  for (int j = 0; j < fp->peaks->n; ++j) {
2131  psArrayAdd(peaks, 1, fp->peaks->data[j]);
2132  }
2133  }
2134 
2135  return peaks;
2136 }
2137 #endif
2138 
2139 /************************************************************************************************************/
2140 //
2141 // Explicit instantiations
2142 // \cond
2143 //
2144 //
2145 template
2146 void Footprint::intersectMask(
2147  image::Mask<image::MaskPixel> const& mask,
2148  image::MaskPixel bitMask);
2149 
2150 template
2151 PTR(Footprint) footprintAndMask(
2152  PTR(Footprint) const& foot,
2153  image::Mask<image::MaskPixel>::Ptr const& mask,
2154  image::MaskPixel bitMask);
2155 
2156 template
2158  image::Mask<image::MaskPixel> *mask,
2159  CONST_PTR(std::vector<PTR(Footprint)>) const& footprints,
2160  image::MaskPixel const bitmask);
2161 template
2163  image::Mask<image::MaskPixel> *mask,
2164  std::vector<PTR(Footprint)> const& footprints,
2165  image::MaskPixel const bitmask);
2166 template image::MaskPixel setMaskFromFootprint(
2167  image::Mask<image::MaskPixel> *mask,
2168  Footprint const& foot, image::MaskPixel const bitmask);
2169 template image::MaskPixel clearMaskFromFootprint(
2170  image::Mask<image::MaskPixel> *mask,
2171  Footprint const& foot, image::MaskPixel const bitmask);
2172 
2173 #define INSTANTIATE_NUMERIC(TYPE) \
2174 template \
2175 TYPE setImageFromFootprint(image::Image<TYPE> *image, \
2176  Footprint const& footprint, \
2177  TYPE const value); \
2178 template \
2179 TYPE setImageFromFootprintList(image::Image<TYPE> *image, \
2180  std::vector<PTR(Footprint)> const& footprints, \
2181  TYPE const value); \
2182 template \
2183 TYPE setImageFromFootprintList(image::Image<TYPE> *image, \
2184  CONST_PTR(std::vector<PTR(Footprint)>) footprints, \
2185  TYPE const value); \
2186 template \
2187 void copyWithinFootprint(Footprint const&, \
2188  PTR(lsst::afw::image::Image<TYPE>) const, \
2189  PTR(lsst::afw::image::Image<TYPE>)); \
2190 template \
2191 void copyWithinFootprint(Footprint const&, \
2192  PTR(lsst::afw::image::MaskedImage<TYPE>) const, \
2193  PTR(lsst::afw::image::MaskedImage<TYPE>)); \
2194 template \
2195  void Footprint::clipToNonzero(lsst::afw::image::Image<TYPE> const&); \
2196 
2197 
2198 INSTANTIATE_NUMERIC(float);
2199 INSTANTIATE_NUMERIC(double);
2200 // There's no reason these shouldn't have setImageFromFootprint(), etc, instantiated
2201 INSTANTIATE_NUMERIC(boost::uint16_t);
2202 INSTANTIATE_NUMERIC(int);
2203 INSTANTIATE_NUMERIC(boost::uint64_t);
2204 
2205 
2206 #define INSTANTIATE_MASK(PIXEL) \
2207 template \
2208 void Footprint::insertIntoImage( \
2209  lsst::afw::image::Image<PIXEL>& idImage, \
2210  boost::uint64_t const id, \
2211  geom::Box2I const& region=geom::Box2I() \
2212  ) const; \
2213 template \
2214 void Footprint::insertIntoImage( \
2215  lsst::afw::image::Image<PIXEL>& idImage, \
2216  boost::uint64_t const id, \
2217  bool const overwriteId, long const idMask, \
2218  std::set<boost::uint64_t> *oldIds, \
2219  geom::Box2I const& region=geom::Box2I() \
2220  ) const; \
2221 
2222 INSTANTIATE_MASK(boost::uint16_t);
2223 INSTANTIATE_MASK(int);
2224 INSTANTIATE_MASK(boost::uint64_t);
2225 
2226 
2227 }}}
2228 // \endcond
2229 
2230 // LocalWords: SpanList
Footprint(int nspan=0, geom::Box2I const &region=geom::Box2I())
Defines the fields and offsets for a table.
Definition: Schema.h:46
void nearestFootprint(std::vector< boost::shared_ptr< Footprint >> const &foots, lsst::afw::image::Image< boost::uint16_t >::Ptr argmin, lsst::afw::image::Image< boost::uint16_t >::Ptr dist)
int getMaxY() const
Definition: Box.h:129
Declare the Kernel class and subclasses.
A coordinate class intended to represent absolute positions.
Extent2I const getDimensions() const
Definition: Box.h:153
#define PTR(...)
Definition: base.h:41
double dx
Definition: ImageUtils.cc:90
Represent a set of pixels of an arbitrary shape and size.
void shift(Extent2I const &offset)
Shift the position of the box by the given offset.
x_iterator x_at(int x, int y) const
Return an x_iterator to the point (x, y) in the image.
Definition: Image.h:329
void swap(ImageBase< PixelT > &a, ImageBase< PixelT > &b)
Definition: Image.cc:287
bool contains(Point2I const &point) const
Return true if the box contains the point.
CatalogT< BaseRecord > BaseCatalog
Definition: fwd.h:61
int y
Box2I BoxI
Definition: Box.h:479
boost::uint16_t MaskPixel
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
geom::Point2D skyToPixel(geom::Angle sky1, geom::Angle sky2) const
Convert from sky coordinates (e.g ra/dec) to pixel positions.
Definition: Wcs.cc:808
boost::shared_ptr< Footprint > footprintAndMask(boost::shared_ptr< Footprint > const &foot, typename image::Mask< MaskT >::Ptr const &mask, MaskT const bitmask)
Return a Footprint that&#39;s the intersection of a Footprint with a Mask.
definition of the Trace messaging facilities
SelectEigenView< T >::Type copy(Eigen::EigenBase< T > const &other)
Copy an arbitrary Eigen expression into a new EigenView.
Definition: eigen.h:390
Point< double, 2 > Point2D
Definition: Point.h:277
geom::Box2I getBBox(ImageOrigin origin=PARENT) const
Definition: Image.h:377
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
void shift(Vector< T, N > const &offset, Array< std::complex< T >, N, C > const &array, int const real_last_dim)
Perform a Fourier-space translation transform.
Definition: FourierOps.h:146
Point< int, 2 > Point2I
Definition: Point.h:274
double dy
Definition: ImageUtils.cc:90
boost::shared_ptr< coord::Coord > pixelToSky(double pix1, double pix2) const
Convert from celestial coordinates to pixel coordinates.
Definition: Wcs.cc:894
Extent< int, 2 > Extent2I
Definition: Extent.h:350
int isnan(T t)
Definition: ieee.h:110
int getX0() const
Definition: Image.h:247
An integer coordinate rectangle.
Definition: Box.h:53
double min
Definition: attributes.cc:216
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
Represent a collections of footprints associated with image data.
boost::shared_ptr< Footprint > shrinkFootprint(Footprint const &foot, int nGrow, bool isotropic)
afw::geom::ellipses::Quadrupole Shape
Definition: constants.h:56
double max
Definition: attributes.cc:218
boost::shared_ptr< Footprint > growFootprint(Footprint const &foot, int nGrow, bool isotropic=true)
Include files required for standard LSST Exception handling.
int x
int getMinY() const
Definition: Box.h:125
geom::Box2I const & _bbox
Definition: fits_io_mpl.h:80
double _y
Definition: ImageUtils.cc:328
void include(Point2D const &point)
Expand this to ensure that this-&gt;contains(point).
int getMinX() const
Definition: Box.h:124
int getHeight() const
Return the number of rows in the image.
Definition: Image.h:239
bool isEmpty() const
Return true if the box contains no points.
Definition: Box.h:166
int _x0
Definition: NaiveFlux.cc:119
#define CONST_PTR(...)
Definition: base.h:47
CatalogIterator< typename Internal::const_iterator > const_iterator
Definition: Catalog.h:108
MaskT clearMaskFromFootprint(lsst::afw::image::Mask< MaskT > *mask, Footprint const &footprint, MaskT const bitmask)
(AND ~bitmask) all the Mask&#39;s pixels that are in the Footprint; that is, set to zero in the Mask-inte...
LSST bitmasks.
ImageT::Pixel setImageFromFootprint(ImageT *image, Footprint const &footprint, typename ImageT::Pixel const value)
Set all image pixels in a Footprint to a given value.
int getWidth() const
Definition: Box.h:154
Support for peaks in images.
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
std::vector< lsst::afw::geom::Box2I > footprintToBBoxList(Footprint const &foot)
int getHeight() const
Definition: Box.h:155
lsst::afw::detection::Footprint Footprint
Definition: Source.h:61
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.
int id
Definition: CR.cc:151
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:47
afw::table::Key< double > b
xy_locator xy_at(int x, int y) const
Definition: Image.h:351
x_iterator row_begin(int y) const
Definition: Image.h:319
int getMaxX() const
Definition: Box.h:128
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
boost::shared_ptr< Footprint > mergeFootprints(Footprint const &foot1, Footprint const &foot2)
int getWidth() const
Return the number of columns in the image.
Definition: Image.h:237
ImageT val
Definition: CR.cc:154
A class to represent a 2-dimensional array of pixels.
Definition: Image.h:415
Utility functions for kernels.
afw::table::SourceRecord * record
void copyWithinFootprint(Footprint const &foot, boost::shared_ptr< ImageOrMaskedImageT > const input, boost::shared_ptr< ImageOrMaskedImageT > output)
ImageT::Pixel setImageFromFootprintList(ImageT *image, boost::shared_ptr< std::vector< boost::shared_ptr< Footprint >> const > footprints, typename ImageT::Pixel const value)
Set all image pixels in a set of Footprints to a given value.
MaskT setMaskFromFootprintList(lsst::afw::image::Mask< MaskT > *mask, std::vector< boost::shared_ptr< Footprint >> const &footprints, MaskT const bitmask)
OR bitmask into all the Mask&#39;s pixels which are in the set of Footprints.
int getY0() const
Definition: Image.h:255