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