lsst.meas.algorithms  14.0-18-gf7dca964+5
CoaddPsf.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008, 2009, 2010 LSST Corporation.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <http://www.lsstcorp.org/LegalNotices/>.
23  */
24 
25 /*
26  * Represent a PSF as for a Coadd based on the James Jee stacking
27  * algorithm which was extracted from Stackfit.
28  */
29 #include <cmath>
30 #include <sstream>
31 #include <iostream>
32 #include <numeric>
33 #include "boost/iterator/iterator_adaptor.hpp"
34 #include "boost/iterator/transform_iterator.hpp"
35 #include "ndarray/eigen.h"
36 #include "lsst/base.h"
37 #include "lsst/pex/exceptions.h"
45 
46 namespace lsst {
47 namespace meas {
48 namespace algorithms {
49 
50 namespace {
51 
52 // Struct used to simplify calculations in computeAveragePosition; lets us use
53 // std::accumulate instead of explicit for loop.
54 struct AvgPosItem {
55  double wx; // weighted x position
56  double wy; // weighted y position
57  double w; // weight value
58 
59  explicit AvgPosItem(double wx_=0.0, double wy_=0.0, double w_=0.0) : wx(wx_), wy(wy_), w(w_) {}
60 
61  // return point, assuming this is a sum of many AvgPosItems
62  afw::geom::Point2D getPoint() const { return afw::geom::Point2D(wx/w, wy/w); }
63 
64  // comparison so we can sort by weights
65  bool operator<(AvgPosItem const & other) const {
66  return w < other.w;
67  }
68 
69  AvgPosItem & operator+=(AvgPosItem const & other) {
70  wx += other.wx;
71  wy += other.wy;
72  w += other.w;
73  return *this;
74  }
75 
76  AvgPosItem & operator-=(AvgPosItem const & other) {
77  wx -= other.wx;
78  wy -= other.wy;
79  w -= other.w;
80  return *this;
81  }
82 
83  friend AvgPosItem operator+(AvgPosItem a, AvgPosItem const & b) { return a += b; }
84 
85  friend AvgPosItem operator-(AvgPosItem a, AvgPosItem const & b) { return a -= b; }
86 };
87 
88 afw::geom::Point2D computeAveragePosition(
89  afw::table::ExposureCatalog const & catalog,
90  afw::geom::SkyWcs const & coaddWcs,
91  afw::table::Key<double> weightKey
92 ) {
93  afw::table::Key<int> goodPixKey;
94  try {
95  goodPixKey = catalog.getSchema()["goodpix"];
96  } catch (pex::exceptions::NotFoundError &) {}
98  items.reserve(catalog.size());
99  for (afw::table::ExposureCatalog::const_iterator i = catalog.begin(); i != catalog.end(); ++i) {
100  afw::geom::Point2D p = coaddWcs.skyToPixel(
101  i->getWcs()->pixelToSky(
102  i->getPsf()->getAveragePosition()
103  )
104  );
105  AvgPosItem item(p.getX(), p.getY(), i->get(weightKey));
106  if (goodPixKey.isValid()) {
107  item.w *= i->get(goodPixKey);
108  }
109  item.wx *= item.w;
110  item.wy *= item.w;
111  items.push_back(item);
112  }
113  // This is a bit pessimistic - we save and sort all the weights all the time,
114  // even though we'll only need them if the average position from all of them
115  // is invalid. But it makes for simpler code, and it's not that expensive
116  // computationally anyhow.
117  std::sort(items.begin(), items.end());
118  AvgPosItem result = std::accumulate(items.begin(), items.end(), AvgPosItem());
119  // If the position isn't valid (no input frames contain it), we remove frames
120  // from the average until it does.
121  for (
123  catalog.subsetContaining(result.getPoint(), coaddWcs, true).empty();
124  ++iter
125  ) {
126  if (iter == items.end()) {
127  // This should only happen if there are no inputs at all,
128  // or if constituent Psfs have a badly-behaved implementation
129  // of getAveragePosition().
130  throw LSST_EXCEPT(
131  pex::exceptions::RuntimeError,
132  "Could not find a valid average position for CoaddPsf"
133  );
134  }
135  result -= *iter;
136  }
137  return result.getPoint();
138 }
139 
140 } // anonymous
141 
143  afw::table::ExposureCatalog const & catalog,
144  afw::geom::SkyWcs const & coaddWcs,
145  std::string const & weightFieldName,
147  int cacheSize
148 ) :
149  _coaddWcs(coaddWcs),
150  _warpingKernelName(warpingKernelName),
151  _warpingControl(std::make_shared<afw::math::WarpingControl>(warpingKernelName, "", cacheSize))
152 {
153  afw::table::SchemaMapper mapper(catalog.getSchema());
155 
156  // copy the field "goodpix", if available, for computeAveragePosition to use
157  try {
158  afw::table::Key<int> goodPixKey = catalog.getSchema()["goodpix"]; // auto does not work
159  mapper.addMapping(goodPixKey, true);
160  } catch (pex::exceptions::NotFoundError &) {}
161 
162  // copy the field specified by weightFieldName to field "weight"
163  afw::table::Field<double> weightField = afw::table::Field<double>("weight", "Coadd weight");
164  afw::table::Key<double> weightKey = catalog.getSchema()[weightFieldName];
165  _weightKey = mapper.addMapping(weightKey, weightField);
166 
167  _catalog = afw::table::ExposureCatalog(mapper.getOutputSchema());
168  for (afw::table::ExposureCatalog::const_iterator i = catalog.begin(); i != catalog.end(); ++i) {
169  PTR(afw::table::ExposureRecord) record = _catalog.getTable()->makeRecord();
170  record->assign(*i, mapper);
171  _catalog.push_back(record);
172  }
173  _averagePosition = computeAveragePosition(_catalog, _coaddWcs, _weightKey);
174 }
175 
177  return std::make_shared<CoaddPsf>(*this);
178 }
179 
180 PTR(afw::detection::Psf) CoaddPsf::resized(int width, int height) const {
181  // Not implemented for WarpedPsf
182  throw LSST_EXCEPT(pex::exceptions::LogicError, "Not Implemented");
183  }
184 
185 // Read all the images from the Image Vector and return the BBox in xy0 offset coordinates
186 
188 
190  // Calculate the box which will contain them all
191  for (unsigned int i = 0; i < imgVector.size(); i ++) {
192  PTR(afw::image::Image<double>) componentImg = imgVector[i];
193  afw::geom::Box2I cBBox = componentImg->getBBox();
194  bbox.include(cBBox); // JFB: this works even on empty bboxes
195  }
196  return bbox;
197 }
198 
199 
200 // Read all the images from the Image Vector and add them to image
201 
204  std::vector<PTR(afw::image::Image<double>)> const & imgVector,
205  std::vector<double> const & weightVector
206 ) {
207  assert(imgVector.size() == weightVector.size());
208  for (unsigned int i = 0; i < imgVector.size(); i ++) {
209  PTR(afw::image::Image<double>) componentImg = imgVector[i];
210  double weight = weightVector[i];
211  double sum = componentImg->getArray().asEigen().sum();
212 
213  // Now get the portion of the component image which is appropriate to add
214  // If the default image size is used, the component is guaranteed to fit,
215  // but not if a size has been specified.
216  afw::geom::Box2I cBBox = componentImg->getBBox();
217  afw::geom::Box2I overlap(cBBox);
218  overlap.clip(image->getBBox());
219  // JFB: A subimage view of the image we want to add to, containing only the overlap region.
220  afw::image::Image<double> targetSubImage(*image, overlap);
221  // JFB: A subimage view of the image we want to add from, containing only the overlap region.
222  afw::image::Image<double> cSubImage(*componentImg, overlap);
223  targetSubImage.scaledPlus(weight/sum, cSubImage);
224  }
225 }
226 
227 
229  afw::geom::Point2D const & ccdXY,
230  afw::image::Color const & color
231 ) const {
232  afw::table::ExposureCatalog subcat = _catalog.subsetContaining(ccdXY, _coaddWcs, true);
233  if (subcat.empty()) {
234  throw LSST_EXCEPT(
236  (boost::format("Cannot compute BBox at point %s; no input images at that point.")
237  % ccdXY).str());
238  }
239 
240  afw::geom::Box2I ret;
241  for (auto const & exposureRecord : subcat) {
242  // compute transform from exposure pixels to coadd pixels
243  auto exposureToCoadd = afw::geom::makeWcsPairTransform(*exposureRecord.getWcs(), _coaddWcs);
244  WarpedPsf warpedPsf = WarpedPsf(exposureRecord.getPsf(), exposureToCoadd, _warpingControl);
245  afw::geom::Box2I componentBBox = warpedPsf.computeBBox(ccdXY, color);
246  ret.include(componentBBox);
247  }
248 
249  return ret;
250 }
251 
253  afw::geom::Point2D const & ccdXY,
254  afw::image::Color const & color
255 ) const {
256  // Get the subset of expoures which contain our coordinate within their validPolygons.
257  afw::table::ExposureCatalog subcat = _catalog.subsetContaining(ccdXY, _coaddWcs, true);
258  if (subcat.empty()) {
259  throw LSST_EXCEPT(
261  (boost::format("Cannot compute CoaddPsf at point %s; no input images at that point.")
262  % ccdXY).str()
263  );
264  }
265  double weightSum = 0.0;
266 
267  // Read all the Psf images into a vector. The code is set up so that this can be done in chunks,
268  // with the image modified to accomodate
269  // However, we currently read all of the images.
271  std::vector<double> weightVector;
272 
273  for (auto const & exposureRecord : subcat) {
274  // compute transform from exposure pixels to coadd pixels
275  auto exposureToCoadd = afw::geom::makeWcsPairTransform(*exposureRecord.getWcs(), _coaddWcs);
276  PTR(afw::image::Image<double>) componentImg;
277  try {
278  WarpedPsf warpedPsf = WarpedPsf(exposureRecord.getPsf(), exposureToCoadd, _warpingControl);
279  componentImg = warpedPsf.computeKernelImage(ccdXY, color);
280  } catch (pex::exceptions::RangeError & exc) {
281  LSST_EXCEPT_ADD(exc, (boost::format("Computing WarpedPsf kernel image for id=%d") %
282  exposureRecord.getId()).str());
283  throw exc;
284  }
285  imgVector.push_back(componentImg);
286  weightSum += exposureRecord.get(_weightKey);
287  weightVector.push_back(exposureRecord.get(_weightKey));
288  }
289 
290  afw::geom::Box2I bbox = getOverallBBox(imgVector);
291 
292  // create a zero image of the right size to sum into
293  PTR(afw::detection::Psf::Image) image = std::make_shared<afw::detection::Psf::Image>(bbox);
294  *image = 0.0;
295  addToImage(image, imgVector, weightVector);
296  *image /= weightSum;
297  return image;
298 }
299 
301  return _catalog.size();
302 }
303 
305  if (index < 0 || index > getComponentCount()) {
306  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
307  }
308  return _catalog[index].getPsf();
309 }
310 
312  if (index < 0 || index > getComponentCount()) {
313  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
314  }
315  return *_catalog[index].getWcs();
316 }
317 
319  if (index < 0 || index > getComponentCount()) {
320  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
321  }
322  return _catalog[index].getValidPolygon();
323 }
324 
325 double CoaddPsf::getWeight(int index) {
326  if (index < 0 || index > getComponentCount()) {
327  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
328  }
329  return _catalog[index].get(_weightKey);
330 }
331 
333  if (index < 0 || index > getComponentCount()) {
334  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
335  }
336  return _catalog[index].getId();
337 }
338 
340  if (index < 0 || index > getComponentCount()) {
341  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
342  }
343  return _catalog[index].getBBox();
344 }
345 
346 // ---------- Persistence -----------------------------------------------------------------------------------
347 
348 // For persistence of CoaddPsf, we have two catalogs: the first has just one record, and contains
349 // the archive ID of the coadd WCS, the size of the warping cache, the name of the warping kernel,
350 // and the average position. The latter is simply the ExposureCatalog.
351 
352 namespace {
353 
354 namespace tbl = afw::table;
355 
356 // Singleton class that manages the first persistence catalog's schema and keys
357 class CoaddPsfPersistenceHelper {
358 public:
359  tbl::Schema schema;
360  tbl::Key<int> coaddWcs;
361  tbl::Key<int> cacheSize;
362  tbl::PointKey<double> averagePosition;
363  tbl::Key<std::string> warpingKernelName;
364 
365  static CoaddPsfPersistenceHelper const & get() {
366  static CoaddPsfPersistenceHelper const instance;
367  return instance;
368  }
369 
370 private:
371  CoaddPsfPersistenceHelper() :
372  schema(),
373  coaddWcs(schema.addField<int>("coaddwcs", "archive ID of the coadd's WCS")),
374  cacheSize(schema.addField<int>("cachesize", "size of the warping cache")),
375  averagePosition(tbl::PointKey<double>::addFields(
376  schema, "avgpos", "PSF accessors default position", "pixel"
377  )),
378  warpingKernelName(schema.addField<std::string>("warpingkernelname", "warping kernel name", 32))
379  {
380  schema.getCitizen().markPersistent();
381  }
382 };
383 
384 } // anonymous
385 
386 class CoaddPsf::Factory : public tbl::io::PersistableFactory {
387 public:
388 
389  virtual PTR(tbl::io::Persistable)
390  read(InputArchive const & archive, CatalogVector const & catalogs) const {
391  if (catalogs.size() == 1u) {
392  // Old CoaddPsfs were saved in only one catalog, because we didn't
393  // save the warping parameters and average position, and we could
394  // save the coadd Wcs in a special final record.
395  return readV0(archive, catalogs);
396  }
397  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
398  CoaddPsfPersistenceHelper const & keys1 = CoaddPsfPersistenceHelper::get();
399  LSST_ARCHIVE_ASSERT(catalogs.front().getSchema() == keys1.schema);
400  tbl::BaseRecord const & record1 = catalogs.front().front();
401  return PTR(CoaddPsf)(
402  new CoaddPsf(
403  tbl::ExposureCatalog::readFromArchive(archive, catalogs.back()),
404  *archive.get<afw::geom::SkyWcs>(record1.get(keys1.coaddWcs)),
405  record1.get(keys1.averagePosition),
406  record1.get(keys1.warpingKernelName),
407  record1.get(keys1.cacheSize)
408  )
409  );
410  }
411 
412 
413  // Backwards compatibility for files saved before meas_algorithms commit
414  // 53e61fae (7/10/2013). Prior to that change, the warping configuration
415  // and the average position were not saved at all, making it impossible to
416  // reconstruct the average position exactly, but it's better to
417  // approximate than to fail completely.
419  readV0(InputArchive const & archive, CatalogVector const & catalogs) const {
420  auto internalCat = tbl::ExposureCatalog::readFromArchive(archive, catalogs.front());
421  // Coadd WCS is stored in a special last record.
422  auto coaddWcs = internalCat.back().getWcs();
423  internalCat.pop_back();
424  // Attempt to reconstruct the average position. We can't do this
425  // exactly, since the catalog we saved isn't the same one that was
426  // used to compute the original average position.
427  tbl::Key<double> weightKey;
428  try {
429  weightKey = internalCat.getSchema()["weight"];
430  } catch (pex::exceptions::NotFoundError &) {}
431  auto averagePos = computeAveragePosition(internalCat, *coaddWcs, weightKey);
432  return std::shared_ptr<CoaddPsf>(new CoaddPsf(internalCat, *coaddWcs, averagePos));
433  }
434 
435  Factory(std::string const & name) : tbl::io::PersistableFactory(name) {}
436 
437 };
438 
439 namespace {
440 
441 std::string getCoaddPsfPersistenceName() { return "CoaddPsf"; }
442 
443 CoaddPsf::Factory registration(getCoaddPsfPersistenceName());
444 
445 } // anonymous
446 
447 std::string CoaddPsf::getPersistenceName() const { return getCoaddPsfPersistenceName(); }
448 
449 std::string CoaddPsf::getPythonModule() const { return "lsst.meas.algorithms"; }
450 
451 void CoaddPsf::write(OutputArchiveHandle & handle) const {
452  CoaddPsfPersistenceHelper const & keys1 = CoaddPsfPersistenceHelper::get();
453  tbl::BaseCatalog cat1 = handle.makeCatalog(keys1.schema);
454  PTR(tbl::BaseRecord) record1 = cat1.addNew();
455  auto coaddWcsPtr = std::make_shared<afw::geom::SkyWcs>(_coaddWcs);
456  record1->set(keys1.coaddWcs, handle.put(coaddWcsPtr));
457  record1->set(keys1.cacheSize, _warpingControl->getCacheSize());
458  record1->set(keys1.averagePosition, _averagePosition);
459  record1->set(keys1.warpingKernelName, _warpingKernelName);
460  handle.saveCatalog(cat1);
461  _catalog.writeToArchive(handle, false);
462 }
463 
465  afw::table::ExposureCatalog const & catalog,
466  afw::geom::SkyWcs const & coaddWcs,
469  int cacheSize
470 ) :
471  _catalog(catalog), _coaddWcs(coaddWcs), _weightKey(_catalog.getSchema()["weight"]),
472  _averagePosition(averagePosition), _warpingKernelName(warpingKernelName),
473  _warpingControl(new afw::math::WarpingControl(warpingKernelName, "", cacheSize))
474 {}
475 
476 }}} // namespace lsst::meas::algorithms
477 
478 
afw::geom::Box2I getOverallBBox(std::vector< boost::shared_ptr< afw::image::Image< double > >> const &imgVector)
Definition: CoaddPsf.cc:187
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
afw::geom::Box2D bbox
tbl::Key< double > weight
geom::Box2I getBBox(ImageOrigin origin=PARENT) const
afw::geom::Box2I getBBox(int index)
Get the bounding box (in component image Pixel coordinates) of the component image at index...
Definition: CoaddPsf.cc:339
double getWeight(int index)
Get the weight of the component image at index.
Definition: CoaddPsf.cc:325
#define LSST_ARCHIVE_ASSERT(EXPR)
void include(Point2I const &point)
std::shared_ptr< Table > getTable() const
virtual std::string getPythonModule() const
Definition: CoaddPsf.cc:449
STL namespace.
#define PTR(...)
T end(T... args)
constexpr Angle operator+(Angle a, Angle d) noexcept
virtual boost::shared_ptr< afw::detection::Psf > resized(int width, int height) const
Return a clone with specified kernel dimensions.
Definition: CoaddPsf.cc:180
virtual std::string getPersistenceName() const
Definition: CoaddPsf.cc:447
CoaddPsf(afw::table::ExposureCatalog const &catalog, afw::geom::SkyWcs const &coaddWcs, std::string const &weightFieldName="weight", std::string const &warpingKernelName="lanczos3", int cacheSize=10000)
Main constructors for CoaddPsf.
Definition: CoaddPsf.cc:142
virtual void write(OutputArchiveHandle &handle) const
Definition: CoaddPsf.cc:451
#define CONST_PTR(...)
tbl::Key< int > cacheSize
Definition: CoaddPsf.cc:361
int getComponentCount() const
Return the number of component Psfs in this CoaddPsf.
Definition: CoaddPsf.cc:300
Factory(std::string const &name)
Definition: CoaddPsf.cc:435
STL class.
virtual afw::geom::Box2I doComputeBBox(afw::geom::Point2D const &position, afw::image::Color const &color) const
Definition: CoaddPsf.cc:228
T push_back(T... args)
void push_back(Record const &r)
double w
Definition: CoaddPsf.cc:57
tbl::Schema schema
Definition: CoaddPsf.cc:359
Point< double, 2 > Point2D
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition: CoaddPsf.h:58
double wx
Definition: CoaddPsf.cc:55
void scaledPlus(double const c, Image< PixelT > const &rhs)
boost::shared_ptr< afw::detection::Psf::Image > doComputeKernelImage(afw::geom::Point2D const &ccdXY, afw::image::Color const &color) const
Definition: CoaddPsf.cc:252
ExposureCatalogT subsetContaining(Coord const &coord, bool includeValidPolygon=false) const
Extent< double, N > & operator+=(Extent< double, N > &lhs, Extent< int, N > const &rhs)
T size(T... args)
#define LSST_EXCEPT(type,...)
STL class.
tbl::Key< std::string > warpingKernelName
Definition: CoaddPsf.cc:363
Base::const_iterator const_iterator
void addMinimalSchema(Schema const &minimal, bool doMap=true)
T begin(T... args)
afw::geom::SkyWcs getWcs(int index)
Get the Wcs of the component image at index.
Definition: CoaddPsf.cc:311
constexpr Angle operator-(Angle a, Angle d) noexcept
virtual boost::shared_ptr< afw::detection::Psf > clone() const
Polymorphic deep copy. Usually unnecessary, as Psfs are immutable.
Definition: CoaddPsf.cc:176
void addToImage(boost::shared_ptr< afw::image::Image< double > > image, std::vector< boost::shared_ptr< afw::image::Image< double > >> const &imgVector, std::vector< double > const &weightVector)
Definition: CoaddPsf.cc:202
tbl::Key< int > coaddWcs
Definition: CoaddPsf.cc:360
std::shared_ptr< RecordT > const get(size_type i) const
io::OutputArchiveHandle OutputArchiveHandle
afw::table::Key< double > b
table::Key< int > a
T sort(T... args)
tbl::PointKey< double > averagePosition
Definition: CoaddPsf.cc:362
void clip(Box2I const &other)
double wy
Definition: CoaddPsf.cc:56
ExposureCatalogT< ExposureRecord > ExposureCatalog
std::shared_ptr< Image > computeKernelImage(geom::Point2D position=makeNullPoint(), image::Color color=image::Color(), ImageOwnerEnum owner=COPY) const
Extent< double, N > & operator-=(Extent< double, N > &lhs, Extent< int, N > const &rhs)
T accumulate(T... args)
std::shared_ptr< tbl::io::Persistable > readV0(InputArchive const &archive, CatalogVector const &catalogs) const
Definition: CoaddPsf.cc:419
#define LSST_EXCEPT_ADD(e, m)
A Psf class that maps an arbitrary Psf through a coordinate transformation.
Definition: WarpedPsf.h:49
boost::shared_ptr< afw::detection::Psf const > getPsf(int index)
Get the Psf of the component image at index.
Definition: CoaddPsf.cc:304
boost::shared_ptr< afw::geom::polygon::Polygon const > getValidPolygon(int index)
Get the validPolygon (in component image Pixel coordinates) of the component image at index...
Definition: CoaddPsf.cc:318
T reserve(T... args)
std::shared_ptr< RecordT > addNew()