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