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