LSSTApplications  10.0-2-g4f67435,11.0.rc2+1,11.0.rc2+12,11.0.rc2+3,11.0.rc2+4,11.0.rc2+5,11.0.rc2+6,11.0.rc2+7,11.0.rc2+8
LSSTDataManagementBasePackage
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::image::Wcs 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 &) {}
97  std::vector<AvgPosItem> items;
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 (
122  std::vector<AvgPosItem>::iterator iter = items.begin();
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::image::Wcs const & coaddWcs,
145  std::string const & weightFieldName,
146  std::string const & warpingKernelName,
147  int cacheSize
148 ) :
149  _coaddWcs(coaddWcs.clone()),
150  _warpingKernelName(warpingKernelName),
151  _warpingControl(boost::make_shared<afw::math::WarpingControl>(warpingKernelName, "", cacheSize))
152 {
153  afw::table::SchemaMapper mapper(catalog.getSchema());
155  afw::table::Field<double> weightField = afw::table::Field<double>("weight", "Coadd weight");
156  afw::table::Key<double> weightKey = catalog.getSchema()[weightFieldName];
157  _weightKey = mapper.addMapping(weightKey, weightField);
158  _catalog = afw::table::ExposureCatalog(mapper.getOutputSchema());
159  for (afw::table::ExposureCatalog::const_iterator i = catalog.begin(); i != catalog.end(); ++i) {
160  PTR(afw::table::ExposureRecord) record = _catalog.getTable()->makeRecord();
161  record->assign(*i, mapper);
162  _catalog.push_back(record);
163  }
164  _averagePosition = computeAveragePosition(_catalog, *_coaddWcs, _weightKey);
165 }
166 
168  return boost::make_shared<CoaddPsf>(*this);
169 }
170 
171 
172 // Read all the images from the Image Vector and return the BBox in xy0 offset coordinates
173 
175 
176  afw::geom::Box2I bbox;
177  // Calculate the box which will contain them all
178  for (unsigned int i = 0; i < imgVector.size(); i ++) {
179  PTR(afw::image::Image<double>) componentImg = imgVector[i];
180  afw::geom::Box2I cBBox = componentImg->getBBox();
181  bbox.include(cBBox); // JFB: this works even on empty bboxes
182  }
183  return bbox;
184 }
185 
186 
187 // Read all the images from the Image Vector and add them to image
188 
191  std::vector<PTR(afw::image::Image<double>)> const & imgVector,
192  std::vector<double> const & weightVector
193 ) {
194  assert(imgVector.size() == weightVector.size());
195  for (unsigned int i = 0; i < imgVector.size(); i ++) {
196  PTR(afw::image::Image<double>) componentImg = imgVector[i];
197  double weight = weightVector[i];
198  double sum = componentImg->getArray().asEigen().sum();
199 
200  // Now get the portion of the component image which is appropriate to add
201  // If the default image size is used, the component is guaranteed to fit,
202  // but not if a size has been specified.
203  afw::geom::Box2I cBBox = componentImg->getBBox();
204  afw::geom::Box2I overlap(cBBox);
205  overlap.clip(image->getBBox());
206  // JFB: A subimage view of the image we want to add to, containing only the overlap region.
207  afw::image::Image<double> targetSubImage(*image, overlap);
208  // JFB: A subimage view of the image we want to add from, containing only the overlap region.
209  afw::image::Image<double> cSubImage(*componentImg, overlap);
210  targetSubImage.scaledPlus(weight/sum, cSubImage);
211  }
212 }
213 
214 
215 PTR(afw::detection::Psf::Image) CoaddPsf::doComputeKernelImage(
216  afw::geom::Point2D const & ccdXY,
217  afw::image::Color const & color
218 ) const {
219  // Get the subset of expoures which contain our coordinate within their validPolygons.
220  afw::table::ExposureCatalog subcat = _catalog.subsetContaining(ccdXY, *_coaddWcs, true);
221  if (subcat.empty()) {
222  throw LSST_EXCEPT(
223  pex::exceptions::InvalidParameterError,
224  (boost::format("Cannot compute CoaddPsf at point %s; no input images at that point.")
225  % ccdXY).str()
226  );
227  }
228  double weightSum = 0.0;
229 
230  // Read all the Psf images into a vector. The code is set up so that this can be done in chunks,
231  // with the image modified to accomodate
232  // However, we currently read all of the images.
233  std::vector<PTR(afw::image::Image<double>)> imgVector;
234  std::vector<double> weightVector;
235 
236  for (afw::table::ExposureCatalog::const_iterator i = subcat.begin(); i != subcat.end(); ++i) {
237  PTR(afw::geom::XYTransform) xytransform(
238  new afw::image::XYTransformFromWcsPair(_coaddWcs, i->getWcs())
239  );
240  WarpedPsf warpedPsf = WarpedPsf(i->getPsf(), xytransform, _warpingControl);
241  PTR(afw::image::Image<double>) componentImg = warpedPsf.computeKernelImage(ccdXY, color);
242  imgVector.push_back(componentImg);
243  weightSum += i->get(_weightKey);
244  weightVector.push_back(i->get(_weightKey));
245  }
246 
247  afw::geom::Box2I bbox = getOverallBBox(imgVector);
248 
249  // create a zero image of the right size to sum into
250  PTR(afw::detection::Psf::Image) image = boost::make_shared<afw::detection::Psf::Image>(bbox);
251  *image = 0.0;
252  addToImage(image, imgVector, weightVector);
253  *image /= weightSum;
254  return image;
255 }
256 
258  return _catalog.size();
259 }
260 
262  if (index < 0 || index > getComponentCount()) {
263  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
264  }
265  return _catalog[index].getPsf();
266 }
267 
268 CONST_PTR(afw::image::Wcs) CoaddPsf::getWcs(int index) {
269  if (index < 0 || index > getComponentCount()) {
270  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
271  }
272  return _catalog[index].getWcs();
273 }
274 
275 CONST_PTR(afw::geom::polygon::Polygon) CoaddPsf::getValidPolygon(int index) {
276  if (index < 0 || index > getComponentCount()) {
277  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
278  }
279  return _catalog[index].getValidPolygon();
280 }
281 
282 double CoaddPsf::getWeight(int index) {
283  if (index < 0 || index > getComponentCount()) {
284  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
285  }
286  return _catalog[index].get(_weightKey);
287 }
288 
290  if (index < 0 || index > getComponentCount()) {
291  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
292  }
293  return _catalog[index].getId();
294 }
295 
297  if (index < 0 || index > getComponentCount()) {
298  throw LSST_EXCEPT(pex::exceptions::RangeError, "index of CoaddPsf component out of range");
299  }
300  return _catalog[index].getBBox();
301 }
302 
303 // ---------- Persistence -----------------------------------------------------------------------------------
304 
305 // For persistence of CoaddPsf, we have two catalogs: the first has just one record, and contains
306 // the archive ID of the coadd WCS, the size of the warping cache, the name of the warping kernel,
307 // and the average position. The latter is simply the ExposureCatalog.
308 
309 namespace {
310 
311 namespace tbl = afw::table;
312 
313 // Singleton class that manages the first persistence catalog's schema and keys
314 class CoaddPsfPersistenceHelper {
315 public:
316  tbl::Schema schema;
317  tbl::Key<int> coaddWcs;
318  tbl::Key<int> cacheSize;
319  tbl::PointKey<double> averagePosition;
320  tbl::Key<std::string> warpingKernelName;
321 
322  static CoaddPsfPersistenceHelper const & get() {
323  static CoaddPsfPersistenceHelper const instance;
324  return instance;
325  }
326 
327 private:
328  CoaddPsfPersistenceHelper() :
329  schema(),
330  coaddWcs(schema.addField<int>("coaddwcs", "archive ID of the coadd's WCS")),
331  cacheSize(schema.addField<int>("cachesize", "size of the warping cache")),
332  averagePosition(tbl::PointKey<double>::addFields(
333  schema, "avgpos", "PSF accessors default position", "pixels"
334  )),
335  warpingKernelName(schema.addField<std::string>("warpingkernelname", "warping kernel name", 32))
336  {
337  schema.getCitizen().markPersistent();
338  }
339 };
340 
341 } // anonymous
342 
343 class CoaddPsf::Factory : public tbl::io::PersistableFactory {
344 public:
345 
346  virtual PTR(tbl::io::Persistable)
347  read(InputArchive const & archive, CatalogVector const & catalogs) const {
348  CoaddPsfPersistenceHelper const & keys1 = CoaddPsfPersistenceHelper::get();
349  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
350  LSST_ARCHIVE_ASSERT(catalogs.front().getSchema() == keys1.schema);
351  tbl::BaseRecord const & record1 = catalogs.front().front();
352  return PTR(CoaddPsf)(
353  new CoaddPsf(
354  tbl::ExposureCatalog::readFromArchive(archive, catalogs.back()),
355  archive.get<afw::image::Wcs>(record1.get(keys1.coaddWcs)),
356  record1.get(keys1.averagePosition),
357  record1.get(keys1.warpingKernelName),
358  record1.get(keys1.cacheSize)
359  )
360  );
361  }
362 
363  Factory(std::string const & name) : tbl::io::PersistableFactory(name) {}
364 
365 };
366 
367 namespace {
368 
369 std::string getCoaddPsfPersistenceName() { return "CoaddPsf"; }
370 
371 CoaddPsf::Factory registration(getCoaddPsfPersistenceName());
372 
373 } // anonymous
374 
375 std::string CoaddPsf::getPersistenceName() const { return getCoaddPsfPersistenceName(); }
376 
377 std::string CoaddPsf::getPythonModule() const { return "lsst.meas.algorithms"; }
378 
379 void CoaddPsf::write(OutputArchiveHandle & handle) const {
380  CoaddPsfPersistenceHelper const & keys1 = CoaddPsfPersistenceHelper::get();
381  tbl::BaseCatalog cat1 = handle.makeCatalog(keys1.schema);
382  PTR(tbl::BaseRecord) record1 = cat1.addNew();
383  record1->set(keys1.coaddWcs, handle.put(_coaddWcs));
384  record1->set(keys1.cacheSize, _warpingControl->getCacheSize());
385  record1->set(keys1.averagePosition, _averagePosition);
386  record1->set(keys1.warpingKernelName, _warpingKernelName);
387  handle.saveCatalog(cat1);
388  _catalog.writeToArchive(handle, false);
389 }
390 
392  afw::table::ExposureCatalog const & catalog,
393  PTR(afw::image::Wcs const) coaddWcs,
395  std::string const & warpingKernelName,
396  int cacheSize
397 ) :
398  _catalog(catalog), _coaddWcs(coaddWcs), _weightKey(_catalog.getSchema()["weight"]),
399  _averagePosition(averagePosition), _warpingKernelName(warpingKernelName),
400  _warpingControl(new afw::math::WarpingControl(warpingKernelName, "", cacheSize))
401 {}
402 
403 }}} // namespace lsst::meas::algorithms
404 
405 
int iter
afw::geom::Box2I getOverallBBox(std::vector< boost::shared_ptr< afw::image::Image< double > >> const &imgVector)
Definition: CoaddPsf.cc:174
static Schema makeMinimalSchema()
Return a minimal schema for Exposure tables and records.
Definition: Exposure.h:164
void clip(Box2I const &other)
Shrink this to ensure that other.contains(*this).
void push_back(Record const &r)
Add a copy of the given record to the end of the catalog.
Definition: Catalog.h:458
void addMinimalSchema(Schema const &minimal, bool doMap=true)
Add the given minimal schema to the output schema.
#define PTR(...)
Definition: base.h:41
table::Key< std::string > name
Definition: ApCorrMap.cc:71
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
Eigen matrix objects that present a view into an ndarray::Array.
tbl::Key< double > weight
afw::geom::Box2I getBBox(int index)
Definition: CoaddPsf.cc:296
An object passed to Persistable::write to allow it to persist itself.
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition: Persistable.h:47
double getWeight(int index)
Definition: CoaddPsf.cc:282
int put(Persistable const *obj, bool permissive=false)
Save a nested Persistable to the same archive.
A custom container class for records, based on std::vector.
Definition: Catalog.h:94
A mapping between the keys of two Schemas, used to copy data between them.
Definition: SchemaMapper.h:19
memId getId() const
Return the Citizen&#39;s ID.
Definition: Citizen.cc:223
afw::geom::Point2D _averagePosition
Definition: CoaddPsf.h:186
#define CONST_PTR(...)
Definition: base.h:47
boost::shared_ptr< Image > computeKernelImage(geom::Point2D position=makeNullPoint(), image::Color color=image::Color(), ImageOwnerEnum owner=COPY) const
Return an Image of the PSF, in a form suitable for convolution.
XYTransformFromWcsPair: An XYTransform obtained by putting two Wcs objects &quot;back to back&quot;...
Definition: Wcs.h:432
boost::shared_ptr< Table > getTable() const
Return the table associated with the catalog.
Definition: Catalog.h:111
boost::shared_ptr< afw::image::Wcs const > _coaddWcs
Definition: CoaddPsf.h:184
Extent< double, N > & operator+=(Extent< double, N > &lhs, Extent< int, N > const &rhs)
Definition: Extent.h:429
afw::table::Key< double > _weightKey
Definition: CoaddPsf.h:185
CoaddPsf(afw::table::ExposureCatalog const &catalog, afw::image::Wcs const &coaddWcs, std::string const &weightFieldName="weight", std::string const &warpingKernelName="lanczos3", int cacheSize=10000)
Main constructors for CoaddPsf.
Definition: CoaddPsf.cc:142
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
Extent< double, N > & operator-=(Extent< double, N > &lhs, Extent< int, N > const &rhs)
Definition: Extent.h:439
std::complex< double > operator-(const std::complex< double > &z1, const Position &p2)
Definition: Bounds.h:70
boost::shared_ptr< RecordT > const get(size_type i) const
Return a pointer to the record at index i.
Definition: Catalog.h:439
Image utility functions.
boost::enable_if< typename ExpressionTraits< Scalar >::IsScalar, Scalar >::type sum(Scalar const &scalar)
Definition: operators.h:1250
boost::shared_ptr< RecordT > addNew()
Create a new record, add it to the end of the catalog, and return a pointer to it.
Definition: Catalog.h:469
An integer coordinate rectangle.
Definition: Box.h:53
tbl::Key< int > cacheSize
Definition: CoaddPsf.cc:318
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
Factory(std::string const &name)
Definition: CoaddPsf.cc:363
virtual std::string getPersistenceName() const
Return the unique name used to persist this object and look up its factory.
Definition: CoaddPsf.cc:375
const Angle operator+(Angle const a, Angle const d)
Definition: Angle.h:264
Base::const_iterator const_iterator
Definition: Exposure.h:259
afw::table::ExposureCatalog _catalog
Definition: CoaddPsf.h:183
double w
Definition: CoaddPsf.cc:57
Schema getSchema() const
Return the schema associated with the catalog&#39;s table.
Definition: Catalog.h:114
CoaddPsf is the Psf derived to be used for non-PSF-matched Coadd images.
Definition: CoaddPsf.h:45
Iterator class for CatalogT.
Definition: Catalog.h:34
int getComponentCount() const
Return the number of component Psfs in this CoaddPsf.
Definition: CoaddPsf.cc:257
A description of a field in a table.
Definition: Field.h:22
virtual boost::shared_ptr< tbl::io::Persistable > read(InputArchive const &archive, CatalogVector const &catalogs) const
Definition: CoaddPsf.cc:347
double wx
Definition: CoaddPsf.cc:55
boost::shared_ptr< afw::math::WarpingControl const > _warpingControl
Definition: CoaddPsf.h:188
tbl::Schema schema
void include(Point2I const &point)
Expand this to ensure that this-&gt;contains(point).
lsst::afw::image::Wcs Wcs
Definition: Wcs.cc:60
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
tbl::Key< std::string > warpingKernelName
Definition: CoaddPsf.cc:320
Base class for all records.
Definition: BaseRecord.h:27
geom::Box2I getBBox(ImageOrigin origin=PARENT) const
Definition: Image.h:377
Custom catalog class for ExposureRecord/Table.
Definition: Exposure.h:55
Virtual base class for 2D transforms.
Definition: XYTransform.h:48
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:189
Compute Image Statistics.
afw::table::Key< double > b
Record class used to store exposure metadata.
Definition: Exposure.h:67
Point< double, 2 > Point2D
Definition: Point.h:286
virtual void write(OutputArchiveHandle &handle) const
Write the object to one or more catalogs.
Definition: CoaddPsf.cc:379
tbl::PointKey< double > averagePosition
Definition: CoaddPsf.cc:319
double wy
Definition: CoaddPsf.cc:56
ExposureCatalogT< ExposureRecord > ExposureCatalog
Definition: Exposure.h:415
virtual std::string getPythonModule() const
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
Definition: CoaddPsf.cc:377
void writeToArchive(io::OutputArchiveHandle &handle, bool ignoreUnpersistable=true) const
Convenience output function for Persistables that contain an ExposureCatalog.
A polymorphic base class for representing an image&#39;s Point Spread Function.
Definition: Psf.h:68
boost::int64_t RecordId
Type used for unique IDs for records.
Definition: misc.h:21
A class to represent a 2-dimensional array of pixels.
Definition: PSF.h:43
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
A Psf class that maps an arbitrary Psf through a coordinate transformation.
Definition: WarpedPsf.h:49
tbl::Key< int > coaddWcs
Include files required for standard LSST Exception handling.
bool empty() const
Return true if the catalog has no records.
Definition: Catalog.h:398
size_type size() const
Return the number of elements in the catalog.
Definition: Catalog.h:401
ExposureCatalogT subsetContaining(Coord const &coord, bool includeValidPolygon=false) const
Return a shallow subset of the catalog with only those records that contain the given point...
void scaledPlus(double const c, Image< PixelT >const &rhs)
Add Image c*rhs to lhs.
Definition: Image.cc:677