LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
Footprint.cc
Go to the documentation of this file.
1 /*
2  * LSST Data Management System
3  * Copyright 2008-2015 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 void Footprint::include(std::vector<PTR(Footprint)> const & others) {
637  if (others.empty()) return;
638  geom::Box2I bbox(getBBox());
639  for (std::vector<PTR(Footprint)>::const_iterator i = others.begin(); i != others.end(); ++i) {
640  bbox.include((**i).getBBox());
641  }
642  boost::uint16_t bits = 0x1;
643  image::Mask<boost::uint16_t> mask(bbox);
644  setMaskFromFootprint(&mask, *this, bits);
645  for (std::vector<PTR(Footprint)>::const_iterator i = others.begin(); i != others.end(); ++i) {
646  setMaskFromFootprint(&mask, **i, bits);
647  }
648  FootprintSet fpSet(mask, Threshold(bits, Threshold::BITMASK));
649  if (fpSet.getFootprints()->size() != 1u) {
650  throw LSST_EXCEPT(
651  pex::exceptions::RuntimeError,
652  (boost::format("Footprint::include() result is disjoint; got %d distinct Footprints")
653  % fpSet.getFootprints()->size()).str()
654  );
655  }
656  _spans.swap(fpSet.getFootprints()->front()->getSpans());
657  _normalized = false;
658  normalize();
659 }
660 
661 // Factory class used for table-based persistence; invoked via registry in afw::table::io
662 class FootprintFactory : public table::io::PersistableFactory {
663 public:
664 
665  virtual PTR(table::io::Persistable)
666  read(InputArchive const & archive, CatalogVector const & catalogs) const {
667  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
668  PTR(Footprint) result = boost::make_shared<Footprint>();
669  result->readSpans(catalogs.front());
670  result->readPeaks(catalogs.back());
671  return result;
672  }
673 
674  explicit FootprintFactory(std::string const & name) : table::io::PersistableFactory(name) {}
675 
676 };
677 
678 namespace {
679 
680 // Singleton helper class that manages the schema and keys for persisting a Footprint
681 class FootprintPersistenceHelper : private boost::noncopyable {
682 public:
683  table::Schema spanSchema;
684  table::Key<int> spanY;
685  table::Key<int> spanX0;
686  table::Key<int> spanX1;
687  table::Schema peakSchema;
688  table::Key<float> peakX;
689  table::Key<float> peakY;
690  table::Key<float> peakValue;
691 
692  static FootprintPersistenceHelper const & get() {
693  static FootprintPersistenceHelper instance;
694  return instance;
695  }
696 
697 private:
698  FootprintPersistenceHelper() :
699  spanSchema(),
700  spanY(spanSchema.addField<int>("y", "row position of span", "pixels")),
701  spanX0(spanSchema.addField<int>("x0", "first column of span (inclusive)", "pixels")),
702  spanX1(spanSchema.addField<int>("x1", "first column of span (inclusive)", "pixels")),
703  peakSchema(),
704  peakX(peakSchema.addField<float>("x", "column position of peak", "pixels")),
705  peakY(peakSchema.addField<float>("y", "column position of peak", "pixels")),
706  peakValue(peakSchema.addField<float>("value", "value of peak pixel", "pixels"))
707  {
708  spanSchema.getCitizen().markPersistent();
709  peakSchema.getCitizen().markPersistent();
710  }
711 };
712 
713 std::string getFootprintPersistenceName() { return "Footprint"; }
714 
715 // Insert the factory into the registry (instantiating an instance is sufficient, because
716 // the code that does the work is in the base class ctor)
717 FootprintFactory registration(getFootprintPersistenceName());
718 
719 } // anonymous
720 
721 std::string Footprint::getPersistenceName() const { return getFootprintPersistenceName(); }
722 
723 std::string Footprint::getPythonModule() const { return "lsst.afw.detection"; }
724 
725 void Footprint::write(OutputArchiveHandle & handle) const {
726  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
727  afw::table::BaseCatalog spanCat = handle.makeCatalog(keys.spanSchema);
728  spanCat.reserve(_spans.size());
729  for (SpanList::const_iterator i = _spans.begin(); i != _spans.end(); ++i) {
730  PTR(afw::table::BaseRecord) record = spanCat.addNew();
731  record->set(keys.spanY, (**i).getY());
732  record->set(keys.spanX0, (**i).getX0());
733  record->set(keys.spanX1, (**i).getX1());
734  }
735  handle.saveCatalog(spanCat);
736  afw::table::BaseCatalog peakCat = handle.makeCatalog(keys.peakSchema);
737  peakCat.reserve(_peaks.size());
738  for (PeakList::const_iterator i = _peaks.begin(); i != _peaks.end(); ++i) {
739  PTR(afw::table::BaseRecord) record = peakCat.addNew();
740  record->set(keys.peakX, (**i).getFx());
741  record->set(keys.peakY, (**i).getFy());
742  record->set(keys.peakValue, (**i).getPeakValue());
743  }
744  handle.saveCatalog(peakCat);
745 }
746 
747 void Footprint::readSpans(afw::table::BaseCatalog const & spanCat) {
748  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
749  for (afw::table::BaseCatalog::const_iterator i = spanCat.begin(); i != spanCat.end(); ++i) {
750  addSpan(i->get(keys.spanY), i->get(keys.spanX0), i->get(keys.spanX1));
751  }
752 }
753 
754 void Footprint::readPeaks(afw::table::BaseCatalog const & peakCat) {
755  FootprintPersistenceHelper const & keys = FootprintPersistenceHelper::get();
756  for (afw::table::BaseCatalog::const_iterator i = peakCat.begin(); i != peakCat.end(); ++i) {
757  _peaks.push_back(
758  boost::make_shared<Peak>(i->get(keys.peakX), i->get(keys.peakY), i->get(keys.peakValue))
759  );
760  }
761 }
762 
763 template <typename Archive>
764 void Footprint::serialize(Archive & ar, const unsigned int version) {
765  ar & make_nvp("spans", _spans);
766  ar & make_nvp("peaks", _peaks);
767  ar & make_nvp("area", _area);
768  ar & make_nvp("normalized", _normalized);
769 
770  int x0, y0, width, height;
771  int rx0, ry0, rwidth, rheight;
772  if(Archive::is_saving::value) {
773  geom::Box2I const & bbox = getBBox();
774  x0 = bbox.getMinX();
775  y0 = bbox.getMinY();
776  width = bbox.getWidth();
777  height = bbox.getHeight();
778 
779  geom::Box2I const & region = getRegion();
780  rx0 = region.getMinX();
781  ry0 = region.getMinY();
782  rwidth = region.getWidth();
783  rheight = region.getHeight();
784  }
785 
786  ar & make_nvp("x0", x0) & make_nvp("y0", y0) & make_nvp("width", width) & make_nvp("height", height);
787  ar & make_nvp("rx0", rx0) & make_nvp("ry0", ry0) & make_nvp("rwidth", rwidth)
788  & make_nvp("rheight", rheight);
789 
790  if(Archive::is_loading::value) {
791  _bbox = geom::BoxI(geom::Point2I(x0, y0), geom::Extent2I(width, height));
792  _region = geom::BoxI(geom::Point2I(rx0, ry0), geom::Extent2I(rwidth, rheight));
793  }
794 }
795 
796 template void Footprint::serialize(boost::archive::text_oarchive &, unsigned int const);
797 template void Footprint::serialize(boost::archive::text_iarchive &, unsigned int const);
798 template void Footprint::serialize(boost::archive::xml_oarchive &, unsigned int const);
799 template void Footprint::serialize(boost::archive::xml_iarchive &, unsigned int const);
800 template void Footprint::serialize(boost::archive::binary_oarchive &, unsigned int const);
801 template void Footprint::serialize(boost::archive::binary_iarchive &, unsigned int const);
802 
803 Footprint & Footprint::operator=(Footprint & other) {
804  _region = other._region;
805 
806  //deep copy spans
807  _spans = SpanList();
808  _spans.reserve(other._spans.size());
809  for(SpanList::const_iterator i(other._spans.begin());
810  i != other._spans.end(); ++i
811  ) {
812  addSpan(**i);
813  }
814  _area = other._area;
815  _normalized = other._normalized;
816  _bbox = other._bbox;
817 
818  //deep copy peaks
819  _peaks = PeakList();
820  _peaks.reserve(other._peaks.size());
821  for(PeakList::iterator i(other._peaks.begin()); i != other._peaks.end(); ++i) {
822  _peaks.push_back(PTR(Peak)(new Peak(**i)));
823  }
824  return *this;
825 }
826 
827 template<typename MaskT>
828 void Footprint::intersectMask(
829  lsst::afw::image::Mask<MaskT> const & mask,
830  MaskT const bitmask
831 ) {
832  geom::Box2I maskBBox = mask.getBBox();
833 
834  //this operation makes no sense on non-normalized footprints.
835  //make sure this is normalized
836  normalize();
837 
838  SpanList::iterator s(_spans.begin());
839  while((*s)->getY() < maskBBox.getMinY() && s != _spans.end()){
840  ++s;
841  }
842 
843 
844  int x0, x1, y;
845  SpanList maskedSpans;
846  int maskedArea=0;
847  for( ; s != _spans.end(); ++s) {
848  y = (*s)->getY();
849 
850  if (y > maskBBox.getMaxY())
851  break;
852 
853  x0 = (*s)->getX0();
854  x1 = (*s)->getX1();
855 
856  if(x1 < maskBBox.getMinX() || x0 > maskBBox.getMaxX()) {
857  //span is entirely outside the image mask. cannot be used
858  continue;
859  }
860 
861  //clip the span to be within the mask
862  if(x0 < maskBBox.getMinX()) x0 = maskBBox.getMinX();
863  if(x1 > maskBBox.getMaxX()) x1 = maskBBox.getMaxX();
864 
865  //Image iterators are always specified with respect to (0,0)
866  //regardless what the image::XY0 is set to.
867  typename image::Mask<MaskT>::const_x_iterator mIter = mask.x_at(
868  x0 - maskBBox.getMinX(), y - maskBBox.getMinY()
869  );
870 
871  //loop over all span locations, slicing the span at maskedPixels
872  for(int x = x0; x <= x1; ++x, ++mIter) {
873  if((*mIter & bitmask) != 0) {
874  //masked pixel found within span
875  if (x > x0) {
876  //add beginning of span to the output
877  //the fixed span contains all the unmasked pixels up to,
878  //but not including this masked pixel
879  PTR(Span) maskedSpan(new Span(y, x0, x- 1));
880  maskedSpans.push_back(maskedSpan);
881  maskedArea += maskedSpan->getWidth();
882  }
883  //set the next Span to start after this pixel
884  x0 = x + 1;
885  }
886  }
887 
888  //add last section of span
889  if(x0 <= x1) {
890  PTR(Span) maskedSpan(new Span(y, x0, x1));
891  maskedSpans.push_back(maskedSpan);
892  maskedArea += maskedSpan->getWidth();
893  }
894  }
895  _area = maskedArea;
896  _spans = maskedSpans;
897  _bbox.clip(maskBBox);
898 }
899 
900 
901 PTR(Footprint) Footprint::transform(
902  image::Wcs const& source,
903  image::Wcs const& target,
904  geom::Box2I const& region,
905  bool doClip
906 ) const {
907  // Transform the original bounding box
908  geom::Box2I const& fpBox = getBBox(); // Original bounding box
909  geom::Box2D tBoxD;
910  // If slow, could consider linearising the WCSes and combining the
911  // linear versions to a single transform, and then using that to
912  // transform all the points.
913  tBoxD.include(transformPoint(fpBox.getMinX(), fpBox.getMinY(), source, target));
914  tBoxD.include(transformPoint(fpBox.getMinX(), fpBox.getMaxY(), source, target));
915  tBoxD.include(transformPoint(fpBox.getMaxX(), fpBox.getMinY(), source, target));
916  tBoxD.include(transformPoint(fpBox.getMaxX(), fpBox.getMaxY(), source, target));
917  geom::Box2I tBoxI(tBoxD);
918 
919  // enumerate points in the new bbox that, when reverse-transformed, are within the given footprint.
920  PTR(Footprint) fpNew = boost::make_shared<Footprint>(0, region);
921 
922  for (int y = tBoxI.getBeginY(); y < tBoxI.getEndY(); ++y) {
923  bool inSpan = false; // Are we in a span?
924  int start = -1; // Start of span
925 
926  for (int x = tBoxI.getBeginX(); x < tBoxI.getEndX(); ++x) {
927  lsst::afw::geom::Point2D const& p = transformPoint(x, y, target, source);
928  int const xSource = std::floor(0.5 + p.getX());
929  int const ySource = std::floor(0.5 + p.getY());
930 
931  if (contains(lsst::afw::geom::Point2I(xSource, ySource))) {
932  if (!inSpan) {
933  inSpan = true;
934  start = x;
935  }
936  } else if (inSpan) {
937  inSpan = false;
938  fpNew->addSpan(y, start, x - 1);
939  }
940  }
941  if (inSpan) {
942  fpNew->addSpan(y, start, tBoxI.getMaxX());
943  }
944  }
945  if (doClip) {
946  fpNew->clipTo(region);
947  }
948  return fpNew;
949 }
950 
956 bool _checkNormalized(Footprint const& foot) {
957  Footprint copy(foot);
958  copy.normalize();
959  if (copy.getArea() != foot.getArea()) {
960  return false;
961  }
962  if (copy.getSpans().size() != foot.getSpans().size()) {
963  return false;
964  }
965  Footprint::SpanList const& spansa = foot.getSpans();
966  Footprint::SpanList const& spansb = copy.getSpans();
967  Footprint::SpanList::const_iterator spa = spansa.begin();
968  Footprint::SpanList::const_iterator spb = spansb.begin();
969  for (; spa != spansa.end(); ++spa, ++spb) {
970  if ((*spa)->getY() != (*spb)->getY()) {
971  return false;
972  }
973  if ((*spa)->getX0() != (*spb)->getX0()) {
974  return false;
975  }
976  if ((*spa)->getX1() != (*spb)->getX1()) {
977  return false;
978  }
979  }
980  return true;
981 }
982 
983 /************************************************************************************************************/
984 
985 template<typename MaskT>
986 PTR(Footprint) footprintAndMask(
987  PTR(Footprint) const& fp,
988  typename lsst::afw::image::Mask<MaskT>::Ptr const& mask,
989  MaskT const bitmask
990 ) {
991  PTR(Footprint) newFp(new Footprint());
992  return newFp;
993 }
994 
995 /************************************************************************************************************/
996 
997 template<typename MaskT>
999  image::Mask<MaskT> *mask,
1000  Footprint const& foot,
1001  MaskT const bitmask
1002 ) {
1003 
1004  int const width = static_cast<int>(mask->getWidth());
1005  int const height = static_cast<int>(mask->getHeight());
1006 
1007  for (Footprint::SpanList::const_iterator siter = foot.getSpans().begin();
1008  siter != foot.getSpans().end(); ++siter) {
1009  CONST_PTR(Span) span = *siter;
1010  int const y = span->getY() - mask->getY0();
1011  if (y < 0 || y >= height) {
1012  continue;
1013  }
1014 
1015  int x0 = span->getX0() - mask->getX0();
1016  int x1 = span->getX1() - mask->getX0();
1017  x0 = (x0 < 0) ? 0 : (x0 >= width ? width - 1 : x0);
1018  x1 = (x1 < 0) ? 0 : (x1 >= width ? width - 1 : x1);
1019 
1020  for (typename image::Image<MaskT>::x_iterator ptr = mask->x_at(x0, y),
1021  end = mask->x_at(x1 + 1, y); ptr != end; ++ptr) {
1022  *ptr |= bitmask;
1023  }
1024  }
1025 
1026  return bitmask;
1027 }
1028 
1029 /************************************************************************************************************/
1030 
1031 template<typename MaskT>
1033  image::Mask<MaskT> *mask,
1034  Footprint const& foot,
1035  MaskT const bitmask
1036 ) {
1037  int const width = static_cast<int>(mask->getWidth());
1038  int const height = static_cast<int>(mask->getHeight());
1039 
1040  for (Footprint::SpanList::const_iterator siter = foot.getSpans().begin();
1041  siter != foot.getSpans().end(); ++siter) {
1042  CONST_PTR(Span) span = *siter;
1043  int const y = span->getY() - mask->getY0();
1044  if (y < 0 || y >= height) {
1045  continue;
1046  }
1047 
1048  int x0 = span->getX0() - mask->getX0();
1049  int x1 = span->getX1() - mask->getX0();
1050  x0 = (x0 < 0) ? 0 : (x0 >= width ? width - 1 : x0);
1051  x1 = (x1 < 0) ? 0 : (x1 >= width ? width - 1 : x1);
1052 
1053  for (typename image::Image<MaskT>::x_iterator ptr = mask->x_at(x0, y),
1054  end = mask->x_at(x1 + 1, y); ptr != end; ++ptr) {
1055  *ptr &= ~bitmask;
1056  }
1057  }
1058 
1059  return bitmask;
1060 }
1061 
1062 /************************************************************************************************************/
1063 
1064 template<typename MaskT>
1066  image::Mask<MaskT> *mask,
1067  std::vector<PTR(Footprint)> const& footprints,
1068  MaskT const bitmask
1069 ) {
1070  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1071  fiter != footprints.end(); ++fiter) {
1072  (void)setMaskFromFootprint(mask, **fiter, bitmask);
1073  }
1074 
1075  return bitmask;
1076 }
1077 
1078 /************************************************************************************************************/
1079 
1080 template<typename MaskT>
1082  image::Mask<MaskT> *mask,
1083  CONST_PTR(std::vector<PTR(Footprint)>) const & footprints,
1084  MaskT const bitmask
1085  ) {
1086  return setMaskFromFootprintList(mask, *footprints, bitmask);
1087 }
1088 
1089 /************************************************************************************************************/
1090 namespace {
1091 template<typename ImageT>
1092 class SetFootprint : public FootprintFunctor<ImageT> {
1093 public:
1094  SetFootprint(ImageT const& image,
1095  typename ImageT::Pixel value) :
1096  FootprintFunctor<ImageT>(image), _value(value) {}
1097 
1098 
1099  void operator()(typename ImageT::xy_locator loc, int, int) {
1100  *loc = _value;
1101  }
1102 private:
1103  typename ImageT::Pixel _value;
1104 };
1105 }
1106 
1107 template<typename ImageT>
1108 typename ImageT::Pixel setImageFromFootprint(
1109  ImageT *image,
1110  Footprint const& foot,
1111  typename ImageT::Pixel const value
1112 ) {
1113  SetFootprint<ImageT> setit(*image, value);
1114  setit.apply(foot);
1115 
1116  return value;
1117 }
1118 
1119 template<typename ImageT>
1120 typename ImageT::Pixel setImageFromFootprintList(
1121  ImageT *image,
1122  CONST_PTR(std::vector<PTR(Footprint)>) footprints,
1123  typename ImageT::Pixel const value
1124  ) {
1125  return setImageFromFootprintList(image, *footprints, value);
1126 }
1127 
1128 template<typename ImageT>
1129 typename ImageT::Pixel setImageFromFootprintList(
1130  ImageT *image,
1131  std::vector<PTR(Footprint)> const& footprints,
1132  typename ImageT::Pixel const value
1133 ) {
1134  SetFootprint<ImageT> setit(*image, value);
1135  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin(),
1136  end = footprints.end(); fiter != end; ++fiter) {
1137  setit.apply(**fiter);
1138  }
1139 
1140  return value;
1141 }
1142 
1143 /************************************************************************************************************/
1144 /*
1145  * Worker routine for the pmSetFootprintArrayIDs/pmSetFootprintID (and pmMergeFootprintArrays)
1146  */
1147 template <typename IDPixelT>
1148 static void set_footprint_id(
1149  typename image::Image<IDPixelT>::Ptr idImage, // the image to set
1150  Footprint const& foot, // the footprint to insert
1151  int const id, // the desired ID
1152  int dx=0, int dy=0 // Add these to all x/y in the Footprint
1153 ) {
1154  for (Footprint::SpanList::const_iterator i = foot.getSpans().begin();
1155  i != foot.getSpans().end(); ++i) {
1156  CONST_PTR(Span) span = *i;
1157  for (typename image::Image<IDPixelT>::x_iterator ptr =
1158  idImage->x_at(span->getX0() + dx, span->getY() + dy),
1159  end = ptr + span->getWidth(); ptr != end; ++ptr) {
1160  *ptr = id;
1161  }
1162  }
1163 }
1164 
1165 template <typename IDPixelT>
1166 static void
1167 set_footprint_array_ids(
1168  typename image::Image<IDPixelT>::Ptr idImage, // the image to set
1169  std::vector<PTR(Footprint)> const& footprints, // the footprints to insert
1170  bool const relativeIDs // show IDs starting at 0, not Footprint->id
1171 ) {
1172  int id = 0; // first index will be 1
1173 
1174  for (std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1175  fiter != footprints.end(); ++fiter) {
1176  CONST_PTR(Footprint) foot = *fiter;
1177 
1178  if (relativeIDs) {
1179  ++id;
1180  } else {
1181  id = foot->getId();
1182  }
1183 
1184  set_footprint_id<IDPixelT>(idImage, *foot, id);
1185  }
1186 }
1187 
1188 template void set_footprint_array_ids<int>(
1189  image::Image<int>::Ptr idImage,
1190  std::vector<PTR(Footprint)> const& footprints,
1191  bool const relativeIDs);
1192 
1193 /******************************************************************************/
1194 /*
1195  * Set an image to the value of footprint's ID wherever they may fall
1196  *
1197  * @param footprints the footprints to insert
1198  * @param relativeIDs show the IDs starting at 1, not pmFootprint->id
1199  */
1200 template <typename IDImageT>
1201 typename boost::shared_ptr<image::Image<IDImageT> > setFootprintArrayIDs(
1202  std::vector<PTR(Footprint)> const& footprints,
1203  bool const relativeIDs
1204 ) {
1205  std::vector<PTR(Footprint)>::const_iterator fiter = footprints.begin();
1206  if (fiter == footprints.end()) {
1207  throw LSST_EXCEPT(
1208  lsst::pex::exceptions::InvalidParameterError,
1209  "You didn't provide any footprints"
1210  );
1211  }
1212  CONST_PTR(Footprint) foot = *fiter;
1213 
1214  typename image::Image<IDImageT>::Ptr idImage(
1215  new image::Image<IDImageT>(foot->getRegion())
1216  );
1217  *idImage = 0;
1218  /*
1219  * do the work
1220  */
1221  set_footprint_array_ids<IDImageT>(idImage, footprints, relativeIDs);
1222 
1223  return idImage;
1224 }
1225 
1226 template image::Image<int>::Ptr setFootprintArrayIDs(
1227  std::vector<PTR(Footprint)> const& footprints,
1228  bool const relativeIDs);
1229 /*
1230  * Set an image to the value of Footprint's ID wherever it may fall
1231  */
1232 template <typename IDImageT>
1233 typename boost::shared_ptr<image::Image<IDImageT> > setFootprintID(
1234  CONST_PTR(Footprint)& foot, // the Footprint to insert
1235  int const id // the desired ID
1236  ) {
1237  typename image::Image<IDImageT>::Ptr idImage(new image::Image<IDImageT>(foot->getBBox()));
1238  *idImage = 0;
1239  /*
1240  * do the work
1241  */
1242  set_footprint_id<IDImageT>(idImage, *foot, id);
1243 
1244  return idImage;
1245 }
1246 
1247 template image::Image<int>::Ptr setFootprintID(CONST_PTR(Footprint)& foot, int const id);
1248 
1249 namespace {
1256 class StructuringElement
1257 {
1258 public:
1259  enum class Shape { CIRCLE, DIAMOND };
1260  typedef std::vector<Span>::const_iterator const_iterator;
1261  StructuringElement(Shape shape, int radius);
1262  StructuringElement(int left, int right, int up, int down);
1263  const_iterator begin() const { return _widths.begin(); }
1264  const_iterator end() const { return _widths.end(); }
1265  int getYRange() const { return _yRange; }
1266 
1267 private:
1268  std::vector<Span> _widths;
1269  int _yRange;
1270 };
1271 
1277 StructuringElement::StructuringElement(Shape shape, int radius) {
1278  _yRange = 2*radius + 1;
1279  _widths.reserve(_yRange);
1280  switch (shape) {
1281  case Shape::CIRCLE:
1282  for (auto dy = -radius; dy <= radius; dy++) {
1283  int dx = static_cast<int>(sqrt(radius*radius - dy*dy));
1284  _widths.push_back(Span(dy, -dx, dx));
1285  }
1286  break;
1287  case Shape::DIAMOND:
1288  for (auto dy = -radius; dy <= radius; dy++) {
1289  int dx = radius - abs(dy);
1290  _widths.push_back(Span(dy, -dx, dx));
1291  }
1292  break;
1293  }
1294 }
1295 
1300 StructuringElement::StructuringElement(int left, int right, int up, int down) {
1301  _yRange = up + down + 1;
1302  _widths.reserve(_yRange);
1303  for (auto dy = 1; dy <= up; dy++) {
1304  _widths.push_back(Span(dy, 0, 0));
1305  }
1306  for (auto dy = -1; dy >= -down; dy--) {
1307  _widths.push_back(Span(dy, 0, 0));
1308  }
1309  _widths.push_back(Span(0, -left, right));
1310 }
1311 
1316 PTR(Footprint) growFootprintImpl(
1317  Footprint const& foot,
1318  StructuringElement const& element
1319 ) {
1320  // Create an empty footprint covering foot's region.
1321  PTR(Footprint) grown(new Footprint(0, foot.getRegion()));
1322 
1323  // Iterate over foot & structuring element adding spans to the empty
1324  // footprint.
1325  for (auto spanIter = foot.getSpans().begin(); spanIter != foot.getSpans().end(); spanIter++) {
1326  for (auto it = element.begin(); it != element.end(); it++) {
1327  int xmin = (*spanIter)->getX0() + it->getX0();
1328  int xmax = (*spanIter)->getX1() + it->getX1();
1329  grown->addSpan((*spanIter)->getY() + it->getY(), xmin, xmax);
1330  }
1331  }
1332 
1333  grown->normalize();
1334 
1335  return grown;
1336 }
1337 
1347 struct PrimaryRun {
1348  int m, y, xmin, xmax;
1349 };
1350 
1355 bool comparePrimaryRun(PrimaryRun const& first, PrimaryRun const& second) {
1356  if (first.y != second.y) {
1357  return first.y < second.y;
1358  } else if (first.m != second.m) {
1359  return first.m < second.m;
1360  } else {
1361  return first.xmin < second.xmin;
1362  }
1363 }
1364 
1365 class ComparePrimaryRunY{
1366 public:
1367  bool operator()(PrimaryRun const& pr, int yval) {
1368  return pr.y < yval;
1369  }
1370  bool operator()(int yval, PrimaryRun const& pr) {
1371  return yval < pr.y;
1372  }
1373 };
1374 
1375 class ComparePrimaryRunM{
1376 public:
1377  bool operator()(PrimaryRun const& pr, int mval) {
1378  return pr.m < mval;
1379  }
1380  bool operator()(int mval, PrimaryRun const& pr) {
1381  return mval < pr.m;
1382  }
1383 };
1384 
1389 PTR(Footprint) shrinkFootprintImpl(
1390  Footprint const& foot,
1391  StructuringElement const& element
1392 ) {
1393  // Create an empty FootprintSet covering the input region
1394  PTR(Footprint) shrunk(new Footprint(0, foot.getRegion()));
1395 
1396  // Calculate all possible primary runs.
1397  std::vector<PrimaryRun> primaryRuns;
1398  for (auto spanIter = foot.getSpans().begin(); spanIter != foot.getSpans().end(); ++spanIter) {
1399  int m = 0;
1400  for (auto it = element.begin(); it != element.end(); ++it, ++m) {
1401  if ((it->getX1() - it->getX0()) <= ((*spanIter)->getX1() - (*spanIter)->getX0())) {
1402  int xmin = (*spanIter)->getX0() - it->getX0();
1403  int xmax = (*spanIter)->getX1() - it->getX1();
1404  int y = (*spanIter)->getY() - it->getY();
1405  primaryRuns.push_back(PrimaryRun({m, y, xmin, xmax}));
1406  }
1407  }
1408  }
1409 
1410  // Iterate over the primary runs in such a way that we consider all values
1411  // of m for given y, then all m for y+1, etc.
1412  std::sort(primaryRuns.begin(), primaryRuns.end(), comparePrimaryRun);
1413 
1414  for (int y = primaryRuns.front().y; y <= primaryRuns.back().y; ++y) {
1415  auto yRange = std::equal_range(primaryRuns.begin(), primaryRuns.end(), y, ComparePrimaryRunY());
1416 
1417  // Discard runs for any value of y for which we find fewer groups than
1418  // M, the total Y range of the structuring element. This is step 3.1
1419  // of the Kim et al. algorithm.
1420  if (std::distance(yRange.first, yRange.second) < element.getYRange()) {
1421  continue;
1422  }
1423 
1424  // "good" runs are those which are covered by each value of m, ie by
1425  // each row in the structuring element. Our algorithm will consider
1426  // each value of m in turn, gradually whittling down the list of good
1427  // runs, then finally convert the remainder into Spans and add them to
1428  // the shrunken Footprint.
1429  std::list<PrimaryRun> goodRuns;
1430 
1431  for (int m = 0; m < element.getYRange(); ++m) {
1432  auto mRange = std::equal_range(yRange.first, yRange.second, m, ComparePrimaryRunM());
1433  if (mRange.first == mRange.second) {
1434  // If a particular m is missing, we know that this y contains
1435  // no good runs; this is equivalent to Kim et al. step 3.2.
1436  goodRuns.clear();
1437  } else {
1438  // Consolidate all primary runs at this m so that they
1439  // don't overlap.
1440  std::list<PrimaryRun> candidateRuns;
1441  int start_x = mRange.first->xmin;
1442  int end_x = mRange.first->xmax;
1443  for (auto run = mRange.first+1; run != mRange.second; ++run) {
1444  if (run->xmin > end_x) {
1445  // Start of a new run
1446  candidateRuns.push_back(PrimaryRun{m, y, start_x, end_x});
1447  start_x = run->xmin;
1448  end_x = run->xmax;
1449  } else {
1450  // Continuation of an existing run
1451  end_x = run->xmax;
1452  }
1453  }
1454  candidateRuns.push_back(PrimaryRun{m, y, start_x, end_x});
1455 
1456  // Otherwise, calculate the intersection of candidate runs at
1457  // this m with good runs from all previous m.
1458  if (m == 0) {
1459  // For m = 0 we have nothing to compare to; all runs are
1460  // accepted.
1461  std::swap(goodRuns, candidateRuns);
1462  } else {
1463  std::list<PrimaryRun> newlist;
1464  for (auto good = goodRuns.begin(); good != goodRuns.end(); ++good) {
1465  for (auto cand = candidateRuns.begin(); cand != candidateRuns.end(); ++cand) {
1466  int start = std::max(good->xmin, cand->xmin);
1467  int end = std::min(good->xmax, cand->xmax);
1468  if (end >= start) {
1469  newlist.push_back(PrimaryRun({m, y, start, end}));
1470  }
1471  }
1472  }
1473  std::swap(newlist, goodRuns);
1474  }
1475  }
1476  }
1477  for (auto run = goodRuns.begin(); run != goodRuns.end(); ++run) {
1478  shrunk->addSpan(run->y, run->xmin, run->xmax);
1479  }
1480  }
1481 
1482  shrunk->normalize();
1483  return shrunk;
1484 }
1485 }
1486 
1487 /************************************************************************************************************/
1488 
1489 namespace {
1490  PTR(Footprint) _mergeFootprints(Footprint const& aFoot, Footprint const& bFoot) {
1491  PTR(Footprint) foot(new Footprint());
1492 
1493  Footprint::PeakList const& aPeaks = aFoot.getPeaks();
1494  Footprint::PeakList const& bPeaks = bFoot.getPeaks();
1495  Footprint::PeakList & peaks = foot->getPeaks();
1496  peaks.reserve(aPeaks.size() + bPeaks.size());
1497  peaks.insert(peaks.begin(), aPeaks.begin(), aPeaks.end());
1498  peaks.insert(peaks.end()-1, bPeaks.begin(), bPeaks.end());
1499  assert(peaks.size() == (aPeaks.size() + bPeaks.size()));
1500 
1501  Footprint::SpanList const& aSpans = aFoot.getSpans();
1502  Footprint::SpanList const& bSpans = bFoot.getSpans();
1503  Footprint::SpanList::const_iterator aSpan = aSpans.begin();
1504  Footprint::SpanList::const_iterator bSpan = bSpans.begin();
1505  Footprint::SpanList::const_iterator aEnd = aSpans.end();
1506  Footprint::SpanList::const_iterator bEnd = bSpans.end();
1507 
1508  foot->getSpans().reserve(std::max(aSpans.size(), bSpans.size()));
1509 
1510  while ((aSpan != aEnd) && (bSpan != bEnd)) {
1511  int y = (*aSpan)->getY();
1512  int x0 = (*aSpan)->getX0();
1513  int x1 = (*aSpan)->getX1();
1514  int yb = (*bSpan)->getY();
1515  int xb0 = (*bSpan)->getX0();
1516  int xb1 = (*bSpan)->getX1();
1517 
1518  if ((y < yb) || (y == yb && (x1 < (xb0-1)))) {
1519  // A is earlier -- add A
1520  foot->addSpanInSeries(y, x0, x1);
1521  ++aSpan;
1522  continue;
1523  }
1524  if ((yb < y) || (y == yb && (xb1 < (x0-1)))) {
1525  // B is earlier -- add B
1526  foot->addSpanInSeries(yb, xb0, xb1);
1527  ++bSpan;
1528  continue;
1529  }
1530 
1531  assert(yb == y);
1532  // Overlap -- find connected spans from both iterators.
1533  x0 = std::min(x0, xb0);
1534  x1 = std::max(x1, xb1);
1535  // Union all connected spans
1536  ++aSpan;
1537  ++bSpan;
1538  while (true) {
1539  if ((aSpan != aEnd) &&
1540  ((*aSpan)->getY() == y) &&
1541  ((*aSpan)->getX0() <= (x1+1))) {
1542  // *aSpan continues this span.
1543  x1 = std::max(x1, (*aSpan)->getX1());
1544  ++aSpan;
1545  continue;
1546  }
1547  if ((bSpan != bEnd) &&
1548  ((*bSpan)->getY() == y) &&
1549  ((*bSpan)->getX0() <= (x1+1))) {
1550  // *bSpan continues this span.
1551  x1 = std::max(x1, (*bSpan)->getX1());
1552  ++bSpan;
1553  continue;
1554  }
1555  break;
1556  }
1557  foot->addSpanInSeries(y, x0, x1);
1558  }
1559  // At this point either "aSpan" or "bSpan" is at the end.
1560 
1561  // Add any remaining spans from "A".
1562  for (; aSpan != aEnd; ++aSpan) {
1563  foot->addSpanInSeries((*aSpan)->getY(), (*aSpan)->getX0(), (*aSpan)->getX1());
1564  }
1565  // Add any remaining spans from "B".
1566  for (; bSpan != bEnd; ++bSpan) {
1567  foot->addSpanInSeries((*bSpan)->getY(), (*bSpan)->getX0(), (*bSpan)->getX1());
1568  }
1569  return foot;
1570  }
1571 }
1572 
1573 PTR(Footprint) mergeFootprints(Footprint& foot1, Footprint& foot2) {
1574  foot1.normalize();
1575  foot2.normalize();
1576  return _mergeFootprints(foot1, foot2);
1577 }
1578 
1579 PTR(Footprint) mergeFootprints(Footprint const& foot1, Footprint const& foot2) {
1580  if (!foot1.isNormalized() || !foot2.isNormalized()) {
1581  throw LSST_EXCEPT(
1582  lsst::pex::exceptions::InvalidParameterError,
1583  "mergeFootprints(const Footprints) requires normalize()d Footprints.");
1584  }
1585  return _mergeFootprints(foot1, foot2);
1586 }
1587 
1588 /************************************************************************************************************/
1589 
1590 void nearestFootprint(std::vector<PTR(Footprint)> const& foots,
1593 {
1594  /*
1595  * insert the footprints into an image, set all the pixels to the
1596  * Manhattan distance from the nearest set pixel.
1597  */
1598  typedef boost::uint16_t dtype;
1599  typedef boost::uint16_t itype;
1600 
1601  const itype nil = 0xffff;
1602 
1603  const geom::Box2I bbox = argmin->getBBox();
1604  *argmin = 0;
1605  *dist = 0;
1606 
1607  int const x0 = bbox.getMinX();
1608  int const y0 = bbox.getMinY();
1609 
1610  for (size_t i=0; i<foots.size(); ++i) {
1611  // Set all the pixels in the footprint to 1
1612  set_footprint_id<itype>(argmin, *foots[i], i, -x0, -y0);
1613  set_footprint_id<dtype>(dist, *foots[i], 1, -x0, -y0);
1614  }
1615 
1616  int const height = dist->getHeight();
1617  int const width = dist->getWidth();
1618 
1619  // traverse from bottom left to top right
1620  for (int y = 0; y != height; ++y) {
1621  image::Image<dtype>::xy_locator dim = dist->xy_at(0, y);
1622  image::Image<itype>::xy_locator aim = argmin->xy_at(0, y);
1623  for (int x = 0; x != width; ++x, ++dim.x(), ++aim.x()) {
1624  if (dim(0, 0) == 1) {
1625  // first pass and pixel was on, it gets a zero
1626  dim(0, 0) = 0;
1627  // its argmin is already set
1628  } else {
1629  // pixel was off. It is at most the sum of lengths of
1630  // the array away from a pixel that is on
1631  dim(0, 0) = width + height;
1632  aim(0, 0) = nil;
1633  // or one more than the pixel to the north
1634  if (y > 0) {
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 west
1642  if (x > 0) {
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  // traverse from top right to bottom left
1653  for (int y = height - 1; y >= 0; --y) {
1654  image::Image<dtype>::xy_locator dim = dist->xy_at(width-1, y);
1655  image::Image<itype>::xy_locator aim = argmin->xy_at(width-1, y);
1656  for (int x = width - 1; x >= 0; --x, --dim.x(), --aim.x()) {
1657  // either what we had on the first pass or one more than
1658  // the pixel to the south
1659  if (y + 1 < height) {
1660  dtype ndist = dim(0,1) + 1;
1661  if (ndist < dim(0,0)) {
1662  dim(0,0) = ndist;
1663  aim(0,0) = aim(0,1);
1664  }
1665  }
1666  // or one more than the pixel to the east
1667  if (x + 1 < width) {
1668  dtype ndist = dim(1,0) + 1;
1669  if (ndist < dim(0,0)) {
1670  dim(0,0) = ndist;
1671  aim(0,0) = aim(1,0);
1672  }
1673  }
1674  }
1675  }
1676 }
1677 
1678 PTR(Footprint) growFootprint(
1679  Footprint const& foot,
1680  int nGrow,
1681  bool isotropic
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 
1688  // An isotropic grow is equivalent to growing with a circular structuring
1689  // element, while a Manhattan grow is equivalent to growing with a
1690  // diamond-shaped element.
1692  Shape shape = isotropic ? Shape::CIRCLE : Shape::DIAMOND;
1693  return growFootprintImpl(foot, StructuringElement(shape, nGrow));
1694 }
1695 
1696 PTR(Footprint) growFootprint(PTR(Footprint) const& foot, int nGrow, bool isotropic) {
1697  return growFootprint(*foot, nGrow, isotropic);
1698 }
1699 
1700 PTR(Footprint) growFootprint(Footprint const& foot,
1701  int nGrow,
1702  bool left,
1703  bool right,
1704  bool up,
1705  bool down
1706  )
1707 {
1708  if (nGrow <= 0 || foot.getNpix() == 0 ) {
1709  // Return a new footprint equal to the input.
1710  return PTR(Footprint)(new Footprint(foot));
1711  }
1712  return growFootprintImpl(foot, StructuringElement(left ? nGrow: 0, right ? nGrow : 0,
1713  up ? nGrow : 0, down ? nGrow : 0));
1714 }
1715 
1716 PTR(Footprint) shrinkFootprint(
1717  Footprint const& foot,
1718  int nShrink,
1719  bool isotropic
1720 ) {
1722  Shape shape = isotropic ? Shape::CIRCLE : Shape::DIAMOND;
1723  return shrinkFootprintImpl(foot, StructuringElement(shape, nShrink));
1724 }
1725 
1726 /************************************************************************************************************/
1727 
1728 std::vector<geom::Box2I> footprintToBBoxList(Footprint const& foot) {
1729  typedef boost::uint16_t ImageT;
1730  geom::Box2I fpBBox = foot.getBBox();
1731  image::Image<ImageT>::Ptr idImage(
1732  new image::Image<ImageT>(fpBBox.getDimensions())
1733  );
1734  *idImage = 0;
1735  int const height = fpBBox.getHeight();
1736  geom::Extent2I shift(fpBBox.getMinX(), fpBBox.getMinY());
1737  foot.insertIntoImage(*idImage, 1, fpBBox);
1738 
1739  std::vector<geom::Box2I> bboxes;
1740  /*
1741  * Our strategy is to find a row of pixels in the Footprint and interpret it as the first
1742  * row of a rectangular set of pixels. We then extend this rectangle upwards as far as it
1743  * will go, and define that as a BBox. We clear all those pixels, and repeat until there
1744  * are none left. I.e. a Footprint will get cut up like this:
1745  *
1746  * .555...
1747  * 22.3314
1748  * 22.331.
1749  * .000.1.
1750  * (as shown in Footprint_1.py)
1751  */
1752 
1753  int y0 = 0; // the first row with non-zero pixels in it
1754  while (y0 < height) {
1755  geom::Box2I bbox; // our next BBox
1756  for (int y = y0; y != height; ++y) {
1757  // Look for a set pixel in this row
1758  image::Image<ImageT>::x_iterator begin = idImage->row_begin(y), end = idImage->row_end(y);
1759  image::Image<ImageT>::x_iterator first = std::find(begin, end, 1);
1760 
1761  if (first != end) { // A pixel is set in this row
1762  image::Image<ImageT>::x_iterator last = std::find(first, end, 0) - 1;
1763  int const x0 = first - begin;
1764  int const x1 = last - begin;
1765 
1766  std::fill(first, last + 1, 0); // clear pixels; we don't want to see them again
1767 
1768  bbox.include(geom::Point2I(x0, y)); // the LLC
1769  bbox.include(geom::Point2I(x1, y)); // the LRC; initial guess for URC
1770 
1771  // we found at least one pixel so extend the BBox upwards
1772  for (++y; y != height; ++y) {
1773  if (std::find(idImage->at(x0, y), idImage->at(x1 + 1, y), 0) != idImage->at(x1 + 1, y)) {
1774  break; // some pixels weren't set, so the BBox stops here, (actually in previous row)
1775  }
1776  std::fill(idImage->at(x0, y), idImage->at(x1 + 1, y), 0);
1777 
1778  bbox.include(geom::Point2I(x1, y)); // the new URC
1779  }
1780 
1781  bbox.shift(shift);
1782  bboxes.push_back(bbox);
1783  } else {
1784  y0 = y + 1;
1785  }
1786  break;
1787  }
1788  }
1789 
1790  return bboxes;
1791 }
1792 
1793 
1794 template<typename ImageOrMaskedImageT>
1795 void
1796 copyWithinFootprint(Footprint const& foot,
1797  PTR(ImageOrMaskedImageT) const input,
1798  PTR(ImageOrMaskedImageT) output) {
1799  Footprint::SpanList spans = foot.getSpans();
1800  for (Footprint::SpanList::iterator sp = spans.begin();
1801  sp != spans.end(); ++sp) {
1802  int y = (*sp)->getY();
1803  int x0 = (*sp)->getX0();
1804  int x1 = (*sp)->getX1();
1805  typename ImageOrMaskedImageT::const_x_iterator initer = input->x_at(
1806  x0 - input->getX0(), y - input->getY0());
1807  typename ImageOrMaskedImageT::x_iterator outiter = output->x_at(
1808  x0 - output->getX0(), y - output->getY0());
1809  for (int x=x0; x <= x1; ++x, ++initer, ++outiter) {
1810  *outiter = *initer;
1811  }
1812  }
1813 }
1814 
1815 
1816 
1817 
1818 #if 0
1819 
1820 /************************************************************************************************************/
1821 /*
1822  * Grow a psArray of pmFootprints isotropically by r pixels, returning a new psArray of new pmFootprints
1823  */
1824 psArray *pmGrowFootprintArray(psArray const *footprints, // footprints to grow
1825  int r) { // how much to grow each footprint
1826  assert (footprints->n == 0 || pmIsFootprint(footprints->data[0]));
1827 
1828  if (footprints->n == 0) { // we don't know the size of the footprint's region
1829  return psArrayAlloc(0);
1830  }
1831  /*
1832  * We'll insert the footprints into an image, then convolve with a disk,
1833  * then extract a footprint from the result --- this is magically what we want.
1834  */
1835  psImage *idImage = pmSetFootprintArrayIDs(footprints, true);
1836  if (r <= 0) {
1837  r = 1; // r == 1 => no grow
1838  }
1839  psKernel *circle = psKernelAlloc(-r, r, -r, r);
1840  assert (circle->image->numRows == 2*r + 1 && circle->image->numCols == circle->image->numRows);
1841  for (int i = 0; i <= r; ++i) {
1842  for (int j = 0; j <= r; ++j) {
1843  if (i*i + j*j <= r*r) {
1844  circle->kernel[i][j] =
1845  circle->kernel[i][-j] =
1846  circle->kernel[-i][j] =
1847  circle->kernel[-i][-j] = 1;
1848  }
1849  }
1850  }
1851 
1852  psImage *grownIdImage = psImageConvolveDirect(idImage, circle); // Here's the actual grow step
1853  psFree(circle);
1854 
1855  psArray *grown = pmFindFootprints(grownIdImage, 0.5, 1); // and here we rebuild the grown footprints
1856  assert (grown != NULL);
1857  psFree(idImage);
1858  psFree(grownIdImage);
1859  /*
1860  * Now assign the peaks appropriately. We could do this more efficiently
1861  * using grownIdImage (which we just freed), but this is easy and probably fast enough
1862  */
1863  psArray const *peaks = pmFootprintArrayToPeaks(footprints);
1864  pmPeaksAssignToFootprints(grown, peaks);
1865  psFree((psArray *)peaks);
1866 
1867  return grown;
1868 }
1869 
1870 /************************************************************************************************************/
1871 /*
1872  * Merge together two psArrays of pmFootprints neither of which is damaged.
1873  *
1874  * The returned psArray may contain elements of the inital psArrays (with
1875  * their reference counters suitable incremented)
1876  */
1877 psArray *pmMergeFootprintArrays(psArray const *footprints1, // one set of footprints
1878  psArray const *footprints2, // the other set
1879  int const includePeaks) { // which peaks to set? 0x1 => footprints1, 0x2 => 2
1880  assert (footprints1->n == 0 || pmIsFootprint(footprints1->data[0]));
1881  assert (footprints2->n == 0 || pmIsFootprint(footprints2->data[0]));
1882 
1883  if (footprints1->n == 0 || footprints2->n == 0) { // nothing to do but put copies on merged
1884  psArray const *old = (footprints1->n == 0) ? footprints2 : footprints1;
1885 
1886  psArray *merged = psArrayAllocEmpty(old->n);
1887  for (int i = 0; i < old->n; ++i) {
1888  psArrayAdd(merged, 1, old->data[i]);
1889  }
1890 
1891  return merged;
1892  }
1893  /*
1894  * We have real work to do as some pmFootprints in footprints2 may overlap
1895  * with footprints1
1896  */
1897  {
1898  pmFootprint *fp1 = footprints1->data[0];
1899  pmFootprint *fp2 = footprints2->data[0];
1900  if (fp1->region.x0 != fp2->region.x0 ||
1901  fp1->region.x1 != fp2->region.x1 ||
1902  fp1->region.y0 != fp2->region.y0 ||
1903  fp1->region.y1 != fp2->region.y1) {
1904  psError(PS_ERR_BAD_PARAMETER_SIZE, true,
1905  "The two pmFootprint arrays correspnond to different-sized regions");
1906  return NULL;
1907  }
1908  }
1909  /*
1910  * We'll insert first one set of footprints then the other into an image, then
1911  * extract a footprint from the result --- this is magically what we want.
1912  */
1913  psImage *idImage = pmSetFootprintArrayIDs(footprints1, true);
1914  set_footprint_array_ids(idImage, footprints2, true);
1915 
1916  psArray *merged = pmFindFootprints(idImage, 0.5, 1);
1917  assert (merged != NULL);
1918  psFree(idImage);
1919  /*
1920  * Now assign the peaks appropriately. We could do this more efficiently
1921  * using idImage (which we just freed), but this is easy and probably fast enough
1922  */
1923  if (includePeaks & 0x1) {
1924  psArray const *peaks = pmFootprintArrayToPeaks(footprints1);
1925  pmPeaksAssignToFootprints(merged, peaks);
1926  psFree((psArray *)peaks);
1927  }
1928 
1929  if (includePeaks & 0x2) {
1930  psArray const *peaks = pmFootprintArrayToPeaks(footprints2);
1931  pmPeaksAssignToFootprints(merged, peaks);
1932  psFree((psArray *)peaks);
1933  }
1934 
1935  return merged;
1936 }
1937 
1938 /************************************************************************************************************/
1939 /*
1940  * Given a psArray of pmFootprints and another of pmPeaks, assign the peaks to the
1941  * footprints in which that fall; if they _don't_ fall in a footprint, add a suitable
1942  * one to the list.
1943  */
1944 psErrorCode
1945 pmPeaksAssignToFootprints(psArray *footprints, // the pmFootprints
1946  psArray const *peaks) { // the pmPeaks
1947  assert (footprints != NULL);
1948  assert (footprints->n == 0 || pmIsFootprint(footprints->data[0]));
1949  assert (peaks != NULL);
1950  assert (peaks->n == 0 || pmIsPeak(peaks->data[0]));
1951 
1952  if (footprints->n == 0) {
1953  if (peaks->n > 0) {
1954  return psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Your list of footprints is empty");
1955  }
1956  return PS_ERR_NONE;
1957  }
1958  /*
1959  * Create an image filled with the object IDs, and use it to assign pmPeaks to the
1960  * objects
1961  */
1962  psImage *ids = pmSetFootprintArrayIDs(footprints, true);
1963  assert (ids != NULL);
1964  assert (ids->type.type == PS_TYPE_S32);
1965  int const y0 = ids->y0;
1966  int const x0 = ids->x0;
1967  int const numRows = ids->numRows;
1968  int const numCols = ids->numCols;
1969 
1970  for (int i = 0; i < peaks->n; ++i) {
1971  pmPeak *peak = peaks->data[i];
1972  int const x = peak->x - x0;
1973  int const y = peak->y - y0;
1974 
1975  assert (x >= 0 && x < numCols && y >= 0 && y < numRows);
1976  int id = ids->data.S32[y][x - x0];
1977 
1978  if (id == 0) { // peak isn't in a footprint, so make one for it
1979  pmFootprint *nfp = pmFootprintAlloc(1, ids);
1980  pmFootprintAddSpan(nfp, y, x, x);
1981  psArrayAdd(footprints, 1, nfp);
1982  psFree(nfp);
1983  id = footprints->n;
1984  }
1985 
1986  assert (id >= 1 && id <= footprints->n);
1987  pmFootprint *fp = footprints->data[id - 1];
1988  psArrayAdd(fp->peaks, 5, peak);
1989  }
1990 
1991  psFree(ids);
1992  //
1993  // Make sure that peaks within each footprint are sorted and unique
1994  //
1995  for (int i = 0; i < footprints->n; ++i) {
1996  pmFootprint *fp = footprints->data[i];
1997  fp->peaks = psArraySort(fp->peaks, pmPeakSortBySN);
1998 
1999  for (int j = 1; j < fp->peaks->n; ++j) { // check for duplicates
2000  if (fp->peaks->data[j] == fp->peaks->data[j-1]) {
2001  (void)psArrayRemoveIndex(fp->peaks, j);
2002  j--; // we moved everything down one
2003  }
2004  }
2005  }
2006 
2007  return PS_ERR_NONE;
2008 }
2009 
2010 /************************************************************************************************************/
2011  /*
2012  * Examine the peaks in a pmFootprint, and throw away the ones that are not sufficiently
2013  * isolated. More precisely, for each peak find the highest coll that you'd have to traverse
2014  * to reach a still higher peak --- and if that coll's more than nsigma DN below your
2015  * starting point, discard the peak.
2016  */
2017 psErrorCode pmFootprintCullPeaks(psImage const *img, // the image wherein lives the footprint
2018  psImage const *weight, // corresponding variance image
2019  pmFootprint *fp, // Footprint containing mortal peaks
2020  float const nsigma_delta, // how many sigma above local background a peak
2021  // needs to be to survive
2022  float const min_threshold) { // minimum permitted coll height
2023  assert (img != NULL);
2024  assert (img->type.type == PS_TYPE_F32);
2025  assert (weight != NULL);
2026  assert (weight->type.type == PS_TYPE_F32);
2027  assert (img->y0 == weight->y0 && img->x0 == weight->x0);
2028  assert (fp != NULL);
2029 
2030  if (fp->peaks == NULL || fp->peaks->n == 0) { // nothing to do
2031  return PS_ERR_NONE;
2032  }
2033 
2034  psRegion subRegion; // desired subregion; 1 larger than bounding box (grr)
2035  subRegion.x0 = fp->bbox.x0;
2036  subRegion.x1 = fp->bbox.x1 + 1;
2037  subRegion.y0 = fp->bbox.y0;
2038  subRegion.y1 = fp->bbox.y1 + 1;
2039  psImage const *subImg = psImageSubset((psImage *)img, subRegion);
2040  psImage const *subWt = psImageSubset((psImage *)weight, subRegion);
2041  assert (subImg != NULL && subWt != NULL);
2042  //
2043  // We need a psArray of peaks brighter than the current peak. We'll fake this
2044  // by reusing the fp->peaks but lying about n.
2045  //
2046  // We do this for efficiency (otherwise I'd need two peaks lists), and we are
2047  // rather too chummy with psArray in consequence. But it works.
2048  //
2049  psArray *brightPeaks = psArrayAlloc(0);
2050  psFree(brightPeaks->data);
2051  brightPeaks->data = psMemIncrRefCounter(fp->peaks->data);// use the data from fp->peaks
2052  //
2053  // The brightest peak is always safe; go through other peaks trying to cull them
2054  //
2055  for (int i = 1; i < fp->peaks->n; ++i) { // n.b. fp->peaks->n can change within the loop
2056  pmPeak const *peak = fp->peaks->data[i];
2057  int x = peak->x - subImg->x0;
2058  int y = peak->y - subImg->y0;
2059  //
2060  // Find the level nsigma below the peak that must separate the peak
2061  // from any of its friends
2062  //
2063  assert (x >= 0 && x < subImg->numCols && y >= 0 && y < subImg->numRows);
2064  float const stdev = std::sqrt(subWt->data.F32[y][x]);
2065  float threshold = subImg->data.F32[y][x] - nsigma_delta*stdev;
2066  if (lsst::utils::isnan(threshold) || threshold < min_threshold) {
2067 #if 1 // min_threshold is assumed to be below the detection threshold,
2068  // so all the peaks are pmFootprint, and this isn't the brightest
2069  (void)psArrayRemoveIndex(fp->peaks, i);
2070  i--; // we moved everything down one
2071  continue;
2072 #else
2073 #error n.b. We will be running LOTS of checks at this threshold, so only find the footprint once
2074  threshold = min_threshold;
2075 #endif
2076  }
2077  if (threshold > subImg->data.F32[y][x]) {
2078  threshold = subImg->data.F32[y][x] - 10*FLT_EPSILON;
2079  }
2080 
2081  int const peak_id = 1; // the ID for the peak of interest
2082  brightPeaks->n = i; // only stop at a peak brighter than we are
2083  pmFootprint *peakFootprint = pmFindFootprintAtPoint(subImg, threshold, brightPeaks, peak->y, peak->x);
2084  brightPeaks->n = 0; // don't double free
2085  psImage *idImg = pmSetFootprintID(peakFootprint, peak_id);
2086  psFree(peakFootprint);
2087 
2088  int j;
2089  for (j = 0; j < i; ++j) {
2090  pmPeak const *peak2 = fp->peaks->data[j];
2091  int x2 = peak2->x - subImg->x0;
2092  int y2 = peak2->y - subImg->y0;
2093  int const peak2_id = idImg->data.S32[y2][x2]; // the ID for some other peak
2094 
2095  if (peak2_id == peak_id) { // There's a brighter peak within the footprint above
2096  ; // threshold; so cull our initial peak
2097  (void)psArrayRemoveIndex(fp->peaks, i);
2098  i--; // we moved everything down one
2099  break;
2100  }
2101  }
2102  if (j == i) {
2103  ++j;
2104  }
2105 
2106  psFree(idImg);
2107  }
2108 
2109  brightPeaks->n = 0;
2110  psFree(brightPeaks);
2111  psFree((psImage *)subImg);
2112  psFree((psImage *)subWt);
2113 
2114  return PS_ERR_NONE;
2115 }
2116 
2117 /*
2118  * Cull an entire psArray of pmFootprints
2119  */
2120 psErrorCode
2121 pmFootprintArrayCullPeaks(psImage const *img, // the image wherein lives the footprint
2122  psImage const *weight, // corresponding variance image
2123  psArray *footprints, // array of pmFootprints
2124  float const nsigma_delta, // how many sigma above local background a peak
2125  // needs to be to survive
2126  float const min_threshold) { // minimum permitted coll height
2127  for (int i = 0; i < footprints->n; ++i) {
2128  pmFootprint *fp = footprints->data[i];
2129  if (pmFootprintCullPeaks(img, weight, fp, nsigma_delta, min_threshold) != PS_ERR_NONE) {
2130  return psError(PS_ERR_UNKNOWN, false, "Culling pmFootprint %d", fp->id);
2131  }
2132  }
2133 
2134  return PS_ERR_NONE;
2135 }
2136 
2137 /************************************************************************************************************/
2138 /*
2139  * Extract the peaks in a psArray of pmFootprints, returning a psArray of pmPeaks
2140  */
2141 psArray *pmFootprintArrayToPeaks(psArray const *footprints) {
2142  assert(footprints != NULL);
2143  assert(footprints->n == 0 || pmIsFootprint(footprints->data[0]));
2144 
2145  int npeak = 0;
2146  for (int i = 0; i < footprints->n; ++i) {
2147  pmFootprint const *fp = footprints->data[i];
2148  npeak += fp->peaks->n;
2149  }
2150 
2151  psArray *peaks = psArrayAllocEmpty(npeak);
2152 
2153  for (int i = 0; i < footprints->n; ++i) {
2154  pmFootprint const *fp = footprints->data[i];
2155  for (int j = 0; j < fp->peaks->n; ++j) {
2156  psArrayAdd(peaks, 1, fp->peaks->data[j]);
2157  }
2158  }
2159 
2160  return peaks;
2161 }
2162 #endif
2163 
2164 /************************************************************************************************************/
2165 //
2166 // Explicit instantiations
2167 // \cond
2168 //
2169 //
2170 template
2171 void Footprint::intersectMask(
2172  image::Mask<image::MaskPixel> const& mask,
2173  image::MaskPixel bitMask);
2174 
2175 template
2176 PTR(Footprint) footprintAndMask(
2177  PTR(Footprint) const& foot,
2178  image::Mask<image::MaskPixel>::Ptr const& mask,
2179  image::MaskPixel bitMask);
2180 
2181 template
2183  image::Mask<image::MaskPixel> *mask,
2184  CONST_PTR(std::vector<PTR(Footprint)>) const& footprints,
2185  image::MaskPixel const bitmask);
2186 template
2188  image::Mask<image::MaskPixel> *mask,
2189  std::vector<PTR(Footprint)> const& footprints,
2190  image::MaskPixel const bitmask);
2191 template image::MaskPixel setMaskFromFootprint(
2192  image::Mask<image::MaskPixel> *mask,
2193  Footprint const& foot, image::MaskPixel const bitmask);
2194 template image::MaskPixel clearMaskFromFootprint(
2195  image::Mask<image::MaskPixel> *mask,
2196  Footprint const& foot, image::MaskPixel const bitmask);
2197 
2198 #define INSTANTIATE_NUMERIC(TYPE) \
2199 template \
2200 TYPE setImageFromFootprint(image::Image<TYPE> *image, \
2201  Footprint const& footprint, \
2202  TYPE const value); \
2203 template \
2204 TYPE setImageFromFootprintList(image::Image<TYPE> *image, \
2205  std::vector<PTR(Footprint)> const& footprints, \
2206  TYPE const value); \
2207 template \
2208 TYPE setImageFromFootprintList(image::Image<TYPE> *image, \
2209  CONST_PTR(std::vector<PTR(Footprint)>) footprints, \
2210  TYPE const value); \
2211 template \
2212 void copyWithinFootprint(Footprint const&, \
2213  PTR(lsst::afw::image::Image<TYPE>) const, \
2214  PTR(lsst::afw::image::Image<TYPE>)); \
2215 template \
2216 void copyWithinFootprint(Footprint const&, \
2217  PTR(lsst::afw::image::MaskedImage<TYPE>) const, \
2218  PTR(lsst::afw::image::MaskedImage<TYPE>)); \
2219 template \
2220  void Footprint::clipToNonzero(lsst::afw::image::Image<TYPE> const&); \
2221 
2222 
2223 INSTANTIATE_NUMERIC(float);
2224 INSTANTIATE_NUMERIC(double);
2225 // There's no reason these shouldn't have setImageFromFootprint(), etc, instantiated
2226 INSTANTIATE_NUMERIC(boost::uint16_t);
2227 INSTANTIATE_NUMERIC(int);
2228 INSTANTIATE_NUMERIC(boost::uint64_t);
2229 
2230 
2231 #define INSTANTIATE_MASK(PIXEL) \
2232 template \
2233 void Footprint::insertIntoImage( \
2234  lsst::afw::image::Image<PIXEL>& idImage, \
2235  boost::uint64_t const id, \
2236  geom::Box2I const& region=geom::Box2I() \
2237  ) const; \
2238 template \
2239 void Footprint::insertIntoImage( \
2240  lsst::afw::image::Image<PIXEL>& idImage, \
2241  boost::uint64_t const id, \
2242  bool const overwriteId, long const idMask, \
2243  std::set<boost::uint64_t> *oldIds, \
2244  geom::Box2I const& region=geom::Box2I() \
2245  ) const; \
2246 
2247 INSTANTIATE_MASK(boost::uint16_t);
2248 INSTANTIATE_MASK(int);
2249 INSTANTIATE_MASK(boost::uint64_t);
2250 
2251 
2252 }}}
2253 // \endcond
2254 
2255 // LocalWords: SpanList
int y
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)
boost::uint16_t MaskPixel
#define CONST_PTR(...)
Definition: base.h:47
Declare the Kernel class and subclasses.
double dx
Definition: ImageUtils.cc:90
Represent a set of pixels of an arbitrary shape and size.
x_iterator row_begin(int y) const
Definition: Image.h:319
for(FootprintList::const_iterator ptr=feet->begin(), end=feet->end();ptr!=end;++ptr)
Definition: saturated.cc:82
void swap(ImageBase< PixelT > &a, ImageBase< PixelT > &b)
Definition: Image.cc:291
CatalogT< BaseRecord > BaseCatalog
Definition: fwd.h:61
Box2I BoxI
Definition: Box.h:479
geom::Point2D skyToPixel(geom::Angle sky1, geom::Angle sky2) const
Convert from sky coordinates (e.g. RA/dec) to pixel positions.
Definition: Wcs.cc:788
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.
lsst::afw::detection::Footprint Footprint
Definition: Source.h:61
definition of the Trace messaging facilities
float _value
Definition: saturated.cc:31
SelectEigenView< T >::Type copy(Eigen::EigenBase< T > const &other)
Copy an arbitrary Eigen expression into a new EigenView.
Definition: eigen.h:390
void include(Point2D const &point)
Expand this to ensure that this-&gt;contains(point).
int getMinY() const
Definition: Box.h:125
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
#define PTR(...)
Definition: base.h:41
Point< int, 2 > Point2I
Definition: PSF.h:39
double dy
Definition: ImageUtils.cc:90
boost::shared_ptr< coord::Coord > pixelToSky(double pix1, double pix2) const
Convert from pixel position to sky coordinates (e.g. RA/dec)
Definition: Wcs.cc:864
int getMaxX() const
Definition: Box.h:128
int isnan(T t)
Definition: ieee.h:110
int const x0
Definition: saturated.cc:45
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:58
void shift(Extent2I const &offset)
Shift the position of the box by the given offset.
int getWidth() const
Return the number of columns in the image.
Definition: Image.h:237
double max
Definition: attributes.cc:218
int getHeight() const
Definition: Box.h:155
boost::shared_ptr< Footprint > growFootprint(Footprint const &foot, int nGrow, bool isotropic=true)
CatalogIterator< typename Internal::const_iterator > const_iterator
Definition: Catalog.h:108
Extent2I const getDimensions() const
Definition: Box.h:153
Represent a 2-dimensional array of bitmask pixels.
Definition: Mask.h:93
geom::Box2I const & _bbox
Definition: fits_io_mpl.h:80
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
double _y
Definition: ImageUtils.cc:328
if(width!=gim.getWidth()||height!=gim.getHeight()||x0!=gim.getX0()||y0!=gim.getY0())
Definition: saturated.cc:47
Footprint(int nspan=0, geom::Box2I const &region=geom::Box2I())
bool contains(Point2I const &point) const
Return true if the box contains the point.
int getMaxY() const
Definition: Box.h:129
int getMinX() const
Definition: Box.h:124
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
int getY0() const
Definition: Image.h:255
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.
int x
ImageT::Pixel setImageFromFootprint(ImageT *image, Footprint const &footprint, typename ImageT::Pixel const value)
Set all image pixels in a Footprint to a given value.
Support for peaks in images.
A coordinate class intended to represent absolute positions.
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
std::vector< lsst::afw::geom::Box2I > footprintToBBoxList(Footprint const &foot)
bool isEmpty() const
Return true if the box contains no points.
Definition: Box.h:166
Extent< int, N > floor(Extent< double, N > const &input)
geom::Box2I getBBox(ImageOrigin origin=PARENT) const
Definition: Image.h:377
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.
def run
Exit with the status code resulting from running the provided test suite.
Definition: tests.py:51
int id
Definition: CR.cc:151
afw::table::Key< double > b
Point< double, 2 > Point2D
Definition: Point.h:286
xy_locator xy_at(int x, int y) const
Definition: Image.h:351
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:47
int getHeight() const
Return the number of rows in the image.
Definition: Image.h:239
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
boost::shared_ptr< Footprint > mergeFootprints(Footprint const &foot1, Footprint const &foot2)
int const y0
Definition: saturated.cc:45
ImageT val
Definition: CR.cc:154
Extent< int, 2 > Extent2I
Definition: Extent.h:355
int getWidth() const
Definition: Box.h:154
Include files required for standard LSST Exception handling.
Utility functions for kernels.
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 getX0() const
Definition: Image.h:247