lsst.meas.extensions.psfex  13.0-4-g20ee73e+3
 All Classes Namespaces Files Functions Variables Friends Macros Groups
PsfexPsf.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 
30 #include <cmath>
31 #include <cassert>
32 #include <numeric>
33 #include <memory>
34 
35 #include "lsst/base.h"
36 #include "lsst/pex/exceptions.h"
37 #include "lsst/afw/image/ImageUtils.h"
38 #include "lsst/afw/math/Statistics.h"
39 extern "C" {
40 #include "vignet.h"
41 }
43 #include "lsst/meas/algorithms/KernelPsfFactory.h"
44 #include "lsst/afw/table/aggregates.h"
45 
46 namespace lsst { namespace meas { namespace extensions { namespace psfex {
47 
48 namespace afw = lsst::afw;
49 
50 PsfexPsf::PsfexPsf(
51  lsst::meas::extensions::psfex::Psf const& psf,
52  afw::geom::Point2D const & averagePosition
53  ) : ImagePsf(), _averagePosition(averagePosition),
54  _size(psf.impl->dim),
55  _comp(psf.impl->npix),
56  _context(psf.impl->poly->ndim)
57 
58 {
59  _poly = poly_copy(psf.impl->poly);
60 
61  _pixstep = psf.impl->pixstep;
62 
63  std::copy(psf.impl->size, psf.impl->size + psf.impl->dim, _size.begin());
64 
65  std::copy(psf.impl->comp, psf.impl->comp + psf.impl->npix, _comp.begin());
66 
67  for (int i = 0; i != psf.impl->poly->ndim; ++i) {
68  _context[i].first = psf.impl->contextoffset[i];
69  _context[i].second = psf.impl->contextscale[i];
70  }
71 }
72 
73  PsfexPsf::PsfexPsf() : ImagePsf(),
74  _averagePosition(afw::geom::Point2I(0, 0)),
75  _poly(0),
76  _pixstep(0.0),
77  _size(),
78  _comp(),
79  _context()
80 {
81  ;
82 }
83 
84 PsfexPsf::~PsfexPsf()
85 {
86  poly_end(_poly);
87 }
88 
89 PTR(afw::detection::Psf)
90 PsfexPsf::clone() const {
91  return std::make_shared<PsfexPsf>(*this);
92 }
93 
94 PTR(afw::math::LinearCombinationKernel const)
95 PsfexPsf::getKernel(afw::geom::Point2D position) const
96 {
97  double pos[MAXCONTEXT];
98  int const ndim = _context.size();
99  if (ndim != 2) { // we're only handling spatial variation for now
100  throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,
101  str(boost::format("Only spatial variation (ndim == 2) is supported; saw %d")
102  % ndim));
103 
104  }
105  // where we want to evaluate the basis function's weights
106  if (!std::isfinite(position[0])) {
107  position = _averagePosition;
108  }
109 
110  for (int i = 0; i < ndim; ++i) {
111  pos[i] = (position[i] - _context[i].first)/_context[i].second;
112  }
113 
114  poly_func(_poly, pos); // evaluate polynomial
115 
116  int const w = _size[0], h = _size[1];
117  std::vector<float> fullresIm(w*h); // accumulate full-resolution image into this buffer
118  /*
119  * Create a fixed Kernel out of each component, and then create a LinearCombinationKernel from them
120  */
121  const int nbasis = _size.size() > 2 ? _size[2] : 1; // number of basis functions
122  afw::math::KernelList kernels; kernels.reserve(nbasis); // the insides of the LinearCombinationKernel
123  std::vector<double> weights; weights.reserve(nbasis);
124 
125  float const vigstep = 1/_pixstep;
126  float const dx = 0.0, dy = 0.0;
127 
128  afw::geom::Box2I bbox = _doComputeBBox(position, afw::geom::Point2D(0, 0));
129  afw::detection::Psf::Image kim(bbox); // a basis function image, to be copied into a FixedKernel
130 
131  int sampleW = bbox.getWidth();
132  int sampleH = bbox.getHeight();
133 
134  std::vector<float> sampledBasis(sampleW*sampleH);
135 
136  for (int i = 0; i != nbasis; ++i) {
137  /*
138  * Resample the basis function onto the output resolution (and potentially subpixel offset)
139  */
140  vignet_resample(const_cast<float *>(&_comp[i*w*h]), w, h,
141  &sampledBasis[0], sampleW, sampleH,
142  -dx*vigstep, -dy*vigstep, vigstep, 1.0);
143  //
144  // And copy it into place
145  //
146  {
147  float *pl = &sampledBasis[0];
148  for (int y = 0; y != sampleH; ++y) {
149  for (int x = 0; x != sampleW; ++x) {
150  kim(x, y) = *pl++;
151  }
152  }
153  }
154 
155  kernels.push_back(std::make_shared<afw::math::FixedKernel>(kim));
156  weights.push_back(_poly->basis[i]);
157  }
158 
159  _kernel = std::make_shared<afw::math::LinearCombinationKernel>(kernels, weights);
160 
161  return _kernel;
162 }
163 
164 PTR(afw::detection::Psf::Image)
165 PsfexPsf::doComputeImage(afw::geom::Point2D const & position,
166  afw::image::Color const & color) const {
167  return _doComputeImage(position, color, position);
168 }
169 
170 PTR(afw::detection::Psf::Image)
171 PsfexPsf::doComputeKernelImage(afw::geom::Point2D const& position,
172  afw::image::Color const& color) const
173 {
174  return _doComputeImage(position, color, afw::geom::Point2D(0, 0));
175 }
176 
177 afw::geom::Box2I PsfexPsf::doComputeBBox(afw::geom::Point2D const & position,
178  afw::image::Color const & color) const {
179  return _doComputeBBox(position, afw::geom::Point2D(0, 0));
180 }
181 
182 afw::geom::Box2I PsfexPsf::_doComputeBBox(afw::geom::Point2D const & position,
183  afw::geom::Point2D const & center) const {
184  int const w = _size[0], h = _size[1];
185  int sampleW = static_cast<int>(w*_pixstep);
186  int sampleH = static_cast<int>(h*_pixstep);
187 
188  // Ensure that sizes are odd
189  if (sampleW % 2 == 0) sampleW += 1;
190  if (sampleH % 2 == 0) sampleH += 1;
191 
192  float dx = center[0] - static_cast<int>(center[0]);
193  float dy = center[1] - static_cast<int>(center[1]);
194 
195  if (dx > 0.5) dx -= 1.0;
196  if (dy > 0.5) dy -= 1.0;
197  // N.b. center[0] - dx == (int)center[x] until we reduced dx to (-0.5, 0.5].
198  // The + 0.5 is to handle floating point imprecision in this calculation
199  afw::geom::Box2I bbox(afw::geom::Point2I(static_cast<int>(center[0] - dx + 0.5) - sampleW/2,
200  static_cast<int>(center[1] - dy + 0.5) - sampleH/2),
201  afw::geom::Extent2I(sampleW, sampleH));
202  return bbox;
203 }
204 
205 PTR(afw::detection::Psf::Image)
206 PsfexPsf::_doComputeImage(afw::geom::Point2D const& position,
207  afw::image::Color const& color,
208  afw::geom::Point2D const& center
209  ) const
210 {
211  double pos[MAXCONTEXT];
212  int const ndim = _context.size();
213  if (ndim != 2) { // we're only handling spatial variation for now
214  throw LSST_EXCEPT(lsst::pex::exceptions::InvalidParameterError,
215  str(boost::format("Only spatial variation (ndim == 2) is supported; saw %d")
216  % ndim));
217 
218  }
219 
220  for (int i = 0; i < ndim; ++i) {
221  pos[i] = (position[i] - _context[i].first)/_context[i].second;
222  }
223 
224  poly_func(_poly, pos); // evaluate polynomial
225 
226  int const w = _size[0], h = _size[1];
227  std::vector<float> fullresIm(w*h); // accumulate full-resolution image into this buffer
228  const int nbasis = _size.size() > 2 ? _size[2] : 1; // number of basis functions
229 
230  /* Sum each component */
231  int const npix = w*h;
232  for (int i = 0; i != nbasis; ++i) {
233  float *pl = &fullresIm[0];
234  float const fac = _poly->basis[i];
235  float const *ppc = &_comp[i*w*h];
236 
237  for (int j = 0; j != npix; ++j) {
238  pl[j] += fac*ppc[j];
239  }
240  }
241  /*
242  * We now have the image reconstructed at internal resolution; resample it onto the output resolution
243  * and subpixel offset
244  */
245  float const vigstep = 1/_pixstep;
246  float dx = center[0] - static_cast<int>(center[0]);
247  float dy = center[1] - static_cast<int>(center[1]);
248  if (dx > 0.5) dx -= 1.0;
249  if (dy > 0.5) dy -= 1.0;
250  //
251  // And copy it into place
252  //
253  afw::geom::Box2I bbox = _doComputeBBox(position, center);
254  PTR(afw::detection::Psf::Image) im = std::make_shared<afw::detection::Psf::Image>(bbox);
255 
256  int sampleW = bbox.getWidth();
257  int sampleH = bbox.getHeight();
258 
259  std::vector<float> sampledIm(sampleW*sampleH);
260 
261  vignet_resample(&fullresIm[0], w, h,
262  &sampledIm[0], sampleW, sampleH,
263  -dx*vigstep, -dy*vigstep, vigstep, 1.0);
264  {
265  float *pl = &sampledIm[0];
266  float const sum = std::accumulate(pl, pl + sampleW*sampleH, static_cast<float>(0));
267  for (int y = 0; y != sampleH; ++y) {
268  for (int x = 0; x != sampleW; ++x) {
269  (*im)(x, y) = *pl++/sum;
270  }
271  }
272  }
273 
274  return im;
275 }
276 
277 /************************************************************************************************************/
278 /*
279  * All the rest of this file handles persistence to FITS files
280  */
281 namespace table = afw::table;
282 
283 namespace {
284 
285 class PsfexPsfSchema1 {
286 public:
287  PsfexPsfSchema1() :
288  schema(),
289  ndim(schema.addField<int>("ndim", "Number of elements in group")),
290  ngroup(schema.addField<int>("ngroup", "Number of elements in degree")),
291  ncoeff(schema.addField<int>("ncoeff", "Number of coefficients")),
292 
293  _size_size(schema.addField<int>("_size_size", "Size of _size array")),
294  _comp_size(schema.addField<int>("_comp_size", "Size of _comp array")),
295  _context_size(schema.addField<int>("_context_size", "Size of _context array")),
296  // Other scalars
297  averagePosition(afw::table::PointKey<double>::addFields(schema,"averagePosition","average position of stars used to make the PSF","pixel")),
298  _pixstep(schema.addField<float>("_pixstep", "oversampling", "pixel"))
299  {
300  ;
301  }
302 
303  table::Schema schema;
304 
305  // Sizes in _poly
306  table::Key<int> ndim;
307  table::Key<int> ngroup;
308  table::Key<int> ncoeff;
309  // Sizes of vectors
310  table::Key<int> _size_size;
311  table::Key<int> _comp_size;
312  table::Key<int> _context_size;
313  // Other scalars
314  table::PointKey<double> averagePosition;
315  table::Key<float> _pixstep;
316 };
317 
318 class PsfexPsfSchema2 {
319 public:
320  PsfexPsfSchema2(int const ndim, int const ngroup, int const ncoeff,
321  int size_size, int comp_size, int context_size) :
322 
323  schema(),
324  group(schema.addField<table::Array<int> >("group", "Groups (of coefficients?)", ndim)),
325  degree(schema.addField<table::Array<int> >("degree", "Degree in each group", ngroup)),
326  basis(schema.addField<table::Array<double> >("basis", "Values of the basis functions", ncoeff)),
327  coeff(schema.addField<table::Array<double> >("coeff", "Polynomial coefficients", ncoeff)),
328  _size(schema.addField<table::Array<int> >("_size", "PSF dimensions", size_size)),
329  _comp(schema.addField<table::Array<float> >("_comp", "Complete pixel data", comp_size)),
330  _context_first( schema.addField<table::Array<double> >("_context_first",
331  "Offset to apply to context data", context_size)),
332  _context_second(schema.addField<table::Array<double> >("_context_second",
333  "Scale to apply to context data", context_size))
334  {
335  ;
336  }
337 
338  table::Schema schema;
339  // _poly
340  table::Key<table::Array<int> > group; // len(group) == ndim
341  table::Key<table::Array<int> > degree; // len(degree) == ngroup
342  table::Key<table::Array<double> > basis; // len(basis) == ncoeff
343  table::Key<table::Array<double> > coeff; // len(coeff) == ncoeff
344  // vectors
345  table::Key<table::Array<int> > _size;
346  table::Key<table::Array<float> > _comp;
347  table::Key<table::Array<double> > _context_first;
348  table::Key<table::Array<double> > _context_second;
349 };
350 
351 std::string getPsfexPsfPersistenceName() { return "PsfexPsf"; }
352 
353 } // anonymous
354 
355 /************************************************************************************************************/
356 
357 namespace detail { // PsfexPsfFactory needs to be a friend of PsfexPsf
358 class PsfexPsfFactory : public table::io::PersistableFactory {
359 public:
360 
361 virtual PTR(table::io::Persistable)
362 read(InputArchive const & archive, CatalogVector const & catalogs) const {
363  LSST_ARCHIVE_ASSERT(catalogs.size() == 2u);
364 
365  PTR(PsfexPsf) result(new PsfexPsf());
366 
367  int ndim, ngroup, ncoeff;
368  int size_size, comp_size, context_size;
369  {
370  PsfexPsfSchema1 const & keys = PsfexPsfSchema1();
371  LSST_ARCHIVE_ASSERT(catalogs[0].size() == 1u);
372  LSST_ARCHIVE_ASSERT(catalogs[0].getSchema() == keys.schema);
373  table::BaseRecord const & record = catalogs[0].front();
374 
375  // fields in _poly
376  ndim = record.get(keys.ndim);
377  ngroup = record.get(keys.ngroup);
378  ncoeff = record.get(keys.ncoeff);
379  // Other scalars
380  result->_averagePosition = record.get(keys.averagePosition);
381  result->_pixstep = record.get(keys._pixstep);
382  // sizes of vectors
383  size_size = record.get(keys._size_size);
384  comp_size = record.get(keys._comp_size);
385  context_size = record.get(keys._context_size);
386  }
387  // Now we can read the data
388  {
389  PsfexPsfSchema2 const keys(ndim, ngroup, ncoeff,
390  size_size, comp_size, context_size);
391 
392  LSST_ARCHIVE_ASSERT(catalogs[1].size() == 1u);
393  LSST_ARCHIVE_ASSERT(catalogs[1].getSchema() == keys.schema);
394  table::BaseRecord const & record = catalogs[1].front();
395 
396  // _poly
397  std::vector<int> group;
398  {
399  int const *begin = record.getElement(keys.group);
400  group.assign(begin, begin + ndim);
401 
402  for (int i = 0; i != ndim; ++i) {
403  ++group[i]; // poly_init subtracts 1 from each element. Sigh.
404  }
405  }
406  std::vector<int> degree;
407  {
408  int const *begin = record.getElement(keys.degree);
409  degree.assign(begin, begin + ngroup);
410  }
411  result->_poly = poly_init(&group[0], group.size(), &degree[0], degree.size());
412  LSST_ARCHIVE_ASSERT(result->_poly->ncoeff == ncoeff);
413 
414  {
415  double const *begin = record.getElement(keys.basis);
416  std::copy(begin, begin + ncoeff, result->_poly->basis);
417  }
418  {
419  double const *begin = record.getElement(keys.coeff);
420  std::copy(begin, begin + ncoeff, result->_poly->coeff);
421  }
422  // vectors
423  {
424  int const *begin = record.getElement(keys._size);
425  result->_size.assign(begin, begin + size_size);
426  }
427  {
428  float const *begin = record.getElement(keys._comp);
429  result->_comp.assign(begin, begin + comp_size);
430  }
431  {
432  double const *begin1 = record.getElement(keys._context_first);
433  double const *begin2 = record.getElement(keys._context_second);
434  result->_context.resize(context_size);
435  for (int i = 0; i != context_size; ++i) {
436  result->_context[i].first = begin1[i];
437  result->_context[i].second = begin2[i];
438  }
439  }
440  }
441 
442  return result;
443 }
444 
445 explicit PsfexPsfFactory(std::string const & name) : table::io::PersistableFactory(name) {}
446 
447 };
448 }
449 
450 namespace {
451  detail::PsfexPsfFactory registration(getPsfexPsfPersistenceName());
452 }
453 
454 /************************************************************************************************************/
455 
456 std::string PsfexPsf::getPythonModule() const { return "lsst.meas.extensions.psfex"; }
457 
458 std::string PsfexPsf::getPersistenceName() const { return getPsfexPsfPersistenceName(); }
459 
460 void PsfexPsf::write(afw::table::io::OutputArchiveHandle & handle) const {
461  // First write the dimensions of the various arrays to an HDU, so we can construct the second
462  // HDU using them when we read the Psf back
463  {
464  PsfexPsfSchema1 const keys;
465  afw::table::BaseCatalog cat = handle.makeCatalog(keys.schema);
466  PTR(afw::table::BaseRecord) record = cat.addNew();
467 
468  // Sizes in _poly
469  record->set(keys.ndim, _poly->ndim);
470  record->set(keys.ngroup, _poly->ngroup);
471  record->set(keys.ncoeff, _poly->ncoeff);
472  // Other scalars
473  record->set(keys.averagePosition, _averagePosition);
474  record->set(keys._pixstep, _pixstep);
475  record->set(keys._size_size, _size.size());
476  record->set(keys._comp_size, _comp.size());
477  record->set(keys._context_size, _context.size());
478 
479  handle.saveCatalog(cat);
480  }
481  // Now we can write the data
482  {
483  PsfexPsfSchema2 const keys(_poly->ndim, _poly->ngroup, _poly->ncoeff,
484  _size.size(), _comp.size(), _context.size());
485  afw::table::BaseCatalog cat = handle.makeCatalog(keys.schema);
486  // _poly
487  PTR(afw::table::BaseRecord) record = cat.addNew();
488  {
489  int *begin = record->getElement(keys.group);
490  std::copy(_poly->group, _poly->group + _poly->ndim, begin);
491  }
492  {
493  int *begin = record->getElement(keys.degree);
494  std::copy(_poly->degree, _poly->degree + _poly->ngroup, begin);
495  }
496  {
497  double *begin = record->getElement(keys.basis);
498  std::copy(_poly->basis, _poly->basis + _poly->ncoeff, begin);
499  }
500  {
501  double *begin = record->getElement(keys.coeff);
502  std::copy(_poly->coeff, _poly->coeff + _poly->ncoeff, begin);
503  }
504  // vectors
505  {
506  int *begin = record->getElement(keys._size);
507  std::copy(_size.begin(), _size.end(), begin);
508  }
509  {
510  float *begin = record->getElement(keys._comp);
511  std::copy(_comp.begin(), _comp.end(), begin);
512  }
513  {
514  double *begin1 = record->getElement(keys._context_first);
515  double *begin2 = record->getElement(keys._context_second);
516  for (unsigned int i = 0; i != _context.size(); ++i) {
517  begin1[i] = _context[i].first;
518  begin2[i] = _context[i].second;
519  }
520  }
521 
522  handle.saveCatalog(cat);
523  }
524 }
525 
526 }}}}
table::Key< int > ncoeff
Definition: PsfexPsf.cc:308
table::Key< int > ndim
Definition: PsfexPsf.cc:306
table::Key< table::Array< int > > degree
Definition: PsfexPsf.cc:341
table::Key< int > _size_size
Definition: PsfexPsf.cc:310
Represent a PSF as a linear combination of PSFEX (== Karhunen-Loeve) basis functions.
Definition: PsfexPsf.h:38
table::Key< table::Array< double > > _context_second
Definition: PsfexPsf.cc:348
table::Key< table::Array< int > > _size
Definition: PsfexPsf.cc:345
table::Key< table::Array< double > > coeff
Definition: PsfexPsf.cc:343
table::Key< int > _comp_size
Definition: PsfexPsf.cc:311
table::Key< table::Array< double > > basis
Definition: PsfexPsf.cc:342
table::Key< table::Array< float > > _comp
Definition: PsfexPsf.cc:346
table::Key< table::Array< double > > _context_first
Definition: PsfexPsf.cc:347
table::Schema schema
Definition: PsfexPsf.cc:303
table::Key< int > _context_size
Definition: PsfexPsf.cc:312
table::Key< table::Array< int > > group
Definition: PsfexPsf.cc:340
table::Key< float > _pixstep
Definition: PsfexPsf.cc:315
table::PointKey< double > averagePosition
Definition: PsfexPsf.cc:314
table::Key< int > ngroup
Definition: PsfexPsf.cc:307