LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
ImagePca.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 <algorithm>
31 #include "boost/make_shared.hpp"
32 #include "lsst/utils/ieee.h"
33 
34 #include "Eigen/Core"
35 #include "Eigen/SVD"
36 #include "Eigen/Eigenvalues"
37 
40 
41 namespace afwMath = lsst::afw::math;
42 
43 namespace lsst {
44 namespace afw {
45 namespace image {
46 
48 template <typename ImageT>
49 ImagePca<ImageT>::ImagePca(bool constantWeight
50  ) :
51  _imageList(),
52  _fluxList(),
53  _dimensions(0,0),
54  _constantWeight(constantWeight),
55  _eigenValues(std::vector<double>()),
56  _eigenImages(ImageList()) {
57 }
58 
64 template <typename ImageT>
65 void ImagePca<ImageT>::addImage(typename ImageT::Ptr img,
66  double flux
67  ) {
68  if (_imageList.empty()) {
69  _dimensions = img->getDimensions();
70  } else {
71  if (getDimensions() != img->getDimensions()) {
72  throw LSST_EXCEPT(
73  lsst::pex::exceptions::LengthError,
74  (boost::format("Dimension mismatch: saw %dx%d; expected %dx%d") %
75  img->getWidth() % img->getHeight() %
76  _dimensions.getX() % _dimensions.getY()
77  ).str()
78  );
79  }
80  }
81 
82  if (flux == 0.0) {
83  throw LSST_EXCEPT(lsst::pex::exceptions::OutOfRangeError, "Flux may not be zero");
84  }
85 
86  _imageList.push_back(img);
87  _fluxList.push_back(flux);
88 }
89 
91 template <typename ImageT>
93  return _imageList;
94 }
95 
96 /************************************************************************************************************/
100 template <typename ImageT>
101 typename ImageT::Ptr ImagePca<ImageT>::getMean() const {
102  if (_imageList.empty()) {
103  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError, "You haven't provided any images");
104  }
105 
106  typename ImageT::Ptr mean(new ImageT(getDimensions()));
107  *mean = static_cast<typename ImageT::Pixel>(0);
108 
109  for (typename ImageList::const_iterator ptr = _imageList.begin(), end = _imageList.end();
110  ptr != end; ++ptr) {
111  *mean += **ptr;
112  }
113  *mean /= _imageList.size();
114 
115  return mean;
116 }
117 
118 /************************************************************************************************************/
119 /*
120  * Analyze the images in an ImagePca, calculating the PCA decomposition (== Karhunen-Lo\`eve basis)
121  *
122  * The notation is that in chapter 7 of Gyula Szokoly's thesis at JHU
123  */
124 namespace {
125  template<typename T>
126  struct SortEvalueDecreasing : public std::binary_function<std::pair<T, int> const&,
127  std::pair<T, int> const&, bool> {
128  bool operator()(std::pair<T, int> const& a, std::pair<T, int> const& b) {
129  return a.first > b.first; // N.b. sort on greater
130  }
131  };
132 }
133 
134 template <typename ImageT>
136 {
137  int const nImage = _imageList.size();
138 
139  if (nImage == 0) {
140  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError, "No images provided for PCA analysis");
141  }
142  /*
143  * Eigen doesn't like 1x1 matrices, but we don't really need it to handle a single matrix...
144  */
145  if (nImage == 1) {
146  _eigenImages.clear();
147  _eigenImages.push_back(typename ImageT::Ptr(new ImageT(*_imageList[0], true)));
148 
149  _eigenValues.clear();
150  _eigenValues.push_back(1.0);
151 
152  return;
153  }
154  /*
155  * Find the eigenvectors/values of the scalar product matrix, R' (Eq. 7.4)
156  */
157  Eigen::MatrixXd R(nImage, nImage); // residuals' inner products
158 
159  double flux_bar = 0; // mean of flux for all regions
160  for (int i = 0; i != nImage; ++i) {
161  typename GetImage<ImageT>::type const& im_i = *GetImage<ImageT>::getImage(_imageList[i]);
162  double const flux_i = getFlux(i);
163  flux_bar += flux_i;
164 
165  for (int j = i; j != nImage; ++j) {
166  typename GetImage<ImageT>::type const& im_j = *GetImage<ImageT>::getImage(_imageList[j]);
167  double const flux_j = getFlux(j);
168 
169  double dot = innerProduct(im_i, im_j);
170  if (_constantWeight) {
171  dot /= flux_i*flux_j;
172  }
173  R(i, j) = R(j, i) = dot/nImage;
174  }
175  }
176  flux_bar /= nImage;
177  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eVecValues(R);
178  Eigen::MatrixXd const& Q = eVecValues.eigenvectors();
179  Eigen::VectorXd const& lambda = eVecValues.eigenvalues();
180  //
181  // We need to sort the eigenValues, and remember the permutation we applied to the eigenImages
182  // We'll use the vector lambdaAndIndex to achieve this
183  //
184  std::vector<std::pair<double, int> > lambdaAndIndex; // pairs (eValue, index)
185  lambdaAndIndex.reserve(nImage);
186 
187  for (int i = 0; i != nImage; ++i) {
188  lambdaAndIndex.push_back(std::make_pair(lambda(i), i));
189  }
190  std::sort(lambdaAndIndex.begin(), lambdaAndIndex.end(), SortEvalueDecreasing<double>());
191  //
192  // Save the (sorted) eigen values
193  //
194  _eigenValues.clear();
195  _eigenValues.reserve(nImage);
196  for (int i = 0; i != nImage; ++i) {
197  _eigenValues.push_back(lambdaAndIndex[i].first);
198  }
199  //
200  // Contruct the first ncomp eigenimages in basis
201  //
202  int ncomp = 100; // number of components to keep
203 
204  _eigenImages.clear();
205  _eigenImages.reserve(ncomp < nImage ? ncomp : nImage);
206 
207  for(int i = 0; i < ncomp; ++i) {
208  if(i >= nImage) {
209  continue;
210  }
211 
212  int const ii = lambdaAndIndex[i].second; // the index after sorting (backwards) by eigenvalue
213 
214  typename ImageT::Ptr eImage(new ImageT(_dimensions));
215  *eImage = static_cast<typename ImageT::Pixel>(0);
216 
217  for (int j = 0; j != nImage; ++j) {
218  int const jj = lambdaAndIndex[j].second; // the index after sorting (backwards) by eigenvalue
219  double const weight = Q(jj, ii)*(_constantWeight ? flux_bar/getFlux(jj) : 1);
220  eImage->scaledPlus(weight, *_imageList[jj]);
221  }
222  _eigenImages.push_back(eImage);
223  }
224 }
225 
226 /************************************************************************************************************/
227 /*
228  *
229  */
230 namespace {
231 /*
232  * Fit a LinearCombinationKernel to an Image, allowing the coefficients of the components to vary
233  *
234  * return std::pair(best-fit kernel, std::pair(amp, chi^2))
235  */
236 template<typename MaskedImageT>
237 typename MaskedImageT::Image::Ptr fitEigenImagesToImage(
238  typename ImagePca<MaskedImageT>::ImageList const& eigenImages, // Eigen images
239  int nEigen, // Number of eigen images to use
240  MaskedImageT const& image // The image to be fit
241  )
242 {
243  typedef typename MaskedImageT::Image ImageT;
244 
245  if (nEigen == 0) {
246  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
247  "You must have at least one eigen image");
248  } else if (nEigen > static_cast<int>(eigenImages.size())) {
249  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
250  (boost::format("You only have %d eigen images (you asked for %d)")
251  % eigenImages.size() % nEigen).str());
252  }
253  /*
254  * Solve the linear problem image = sum x_i K_i + epsilon; we solve this for x_i by constructing the
255  * normal equations, A x = b
256  */
257  Eigen::MatrixXd A(nEigen, nEigen);
258  Eigen::VectorXd b(nEigen);
259 
260  for (int i = 0; i != nEigen; ++i) {
261  b(i) = innerProduct(*eigenImages[i]->getImage(), *image.getImage());
262 
263  for (int j = i; j != nEigen; ++j) {
264  A(i, j) = A(j, i) = innerProduct(*eigenImages[i]->getImage(), *eigenImages[j]->getImage());
265  }
266  }
267  Eigen::VectorXd x(nEigen);
268 
269  if (nEigen == 1) {
270  x(0) = b(0)/A(0, 0);
271  } else {
272  x = A.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
273  }
274  //
275  // Accumulate the best-fit-image in bestFitImage
276  //
277  typename ImageT::Ptr bestFitImage = boost::make_shared<ImageT>(eigenImages[0]->getDimensions());
278 
279  for (int i = 0; i != nEigen; ++i) {
280  bestFitImage->scaledPlus(x[i], *eigenImages[i]->getImage());
281  }
282 
283  return bestFitImage;
284 }
285 
286 /************************************************************************************************************/
287 
288 template <typename ImageT>
289 double do_updateBadPixels(detail::basic_tag const&,
290  typename ImagePca<ImageT>::ImageList const&,
291  std::vector<double> const&,
292  typename ImagePca<ImageT>::ImageList const&,
293  unsigned long, int const)
294 {
295  return 0.0;
296 }
297 
298 template <typename ImageT>
299 double do_updateBadPixels(
300  detail::MaskedImage_tag const&,
301  typename ImagePca<ImageT>::ImageList const& imageList,
302  std::vector<double> const& fluxes, // fluxes of images
303  typename ImagePca<ImageT>::ImageList const& eigenImages, // Eigen images
304  unsigned long mask,
305  int const ncomp
306  )
307 {
308  int const nImage = imageList.size();
309  if (nImage == 0) {
310  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
311  "Please provide at least one Image for me to update");
312  }
313  geom::Extent2I dimensions = imageList[0]->getDimensions();
314  int const height = dimensions.getY();
315 
316  double maxChange = 0.0; // maximum change to the input images
317 
318  if (ncomp == 0) { // use mean of good pixels
319  typename ImageT::Image mean(dimensions); // desired mean image
320  image::Image<float> weight(mean.getDimensions()); // weight of each pixel
321 
322  for (int i = 0; i != nImage; ++i) {
323  double const flux_i = fluxes[i];
324 
325  for (int y = 0; y != height; ++y) {
326  typename ImageT::const_x_iterator iptr = imageList[i]->row_begin(y);
327  image::Image<float>::x_iterator wptr = weight.row_begin(y);
328  for (typename ImageT::Image::x_iterator mptr = mean.row_begin(y), end = mean.row_end(y);
329  mptr != end; ++mptr, ++iptr, ++wptr) {
330  if (!(iptr.mask() & mask) && iptr.variance() > 0.0) {
331  typename ImageT::Image::Pixel value = iptr.image()/flux_i;
332  float const var = iptr.variance()/(flux_i*flux_i);
333  float const ivar = 1.0/var;
334 
335  if (lsst::utils::isfinite(value*ivar)) {
336  *mptr += value*ivar;
337  *wptr += ivar;
338  }
339  }
340  }
341  }
342  }
343  //
344  // Calculate mean
345  //
346  for (int y = 0; y != height; ++y) {
347  image::Image<float>::x_iterator wptr = weight.row_begin(y);
348  for (typename ImageT::Image::x_iterator mptr = mean.row_begin(y), end = mean.row_end(y);
349  mptr != end; ++mptr, ++wptr) {
350  if (*wptr == 0.0) { // oops, no good values
351  ;
352  } else {
353  *mptr /= *wptr;
354  }
355  }
356  }
357  //
358  // Replace bad values by mean
359  //
360  for (int i = 0; i != nImage; ++i) {
361  double const flux_i = fluxes[i];
362 
363  for (int y = 0; y != height; ++y) {
364  typename ImageT::x_iterator iptr = imageList[i]->row_begin(y);
365  for (typename ImageT::Image::x_iterator mptr = mean.row_begin(y), end = mean.row_end(y);
366  mptr != end; ++mptr, ++iptr) {
367  if ((iptr.mask() & mask)) {
368  double const delta = ::fabs(flux_i*(*mptr) - iptr.image());
369  if (delta > maxChange) {
370  maxChange = delta;
371  }
372  iptr.image() = flux_i*(*mptr);
373  }
374  }
375  }
376  }
377  } else {
378  if (ncomp > static_cast<int>(eigenImages.size())) {
379  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
380  (boost::format("You only have %d eigen images (you asked for %d)")
381  % eigenImages.size() % ncomp).str());
382  }
383 
384  for (int i = 0; i != nImage; ++i) {
385  typename ImageT::Image::Ptr fitted = fitEigenImagesToImage(eigenImages, ncomp, *imageList[i]);
386 
387  for (int y = 0; y != height; ++y) {
388  typename ImageT::x_iterator iptr = imageList[i]->row_begin(y);
389  for (typename ImageT::Image::const_x_iterator fptr = fitted->row_begin(y),
390  end = fitted->row_end(y); fptr != end; ++fptr, ++iptr) {
391  if (iptr.mask() & mask) {
392  double const delta = ::fabs(*fptr - iptr.image());
393  if (delta > maxChange) {
394  maxChange = delta;
395  }
396 
397  iptr.image() = *fptr;
398  }
399  }
400  }
401  }
402  }
403 
404  return maxChange;
405 }
406 }
416 template <typename ImageT>
418  unsigned long mask,
419  int const ncomp
420  )
421 {
422  return do_updateBadPixels<ImageT>(typename ImageT::image_category(),
423  _imageList, _fluxList, _eigenImages, mask, ncomp);
424 }
425 
426 /*******************************************************************************************************/
427 namespace {
428  template<typename T, typename U>
429  struct IsSame {
430  IsSame(T const&, U const&) {}
431  bool operator()() { return false; }
432  };
433 
434  template<typename T>
435  struct IsSame<T, T> {
436  IsSame(T const& im1, T const& im2) : _same(im1.row_begin(0) == im2.row_begin(0)) {}
437  bool operator()() { return _same; }
438  private:
439  bool _same;
440  };
441 
442  // Test if two Images are identical; they need not be of the same type
443  template<typename Image1T, typename Image2T>
444  bool imagesAreIdentical(Image1T const& im1, Image2T const& im2) {
445  return IsSame<Image1T, Image2T>(im1, im2)();
446  }
447 }
453 template <typename Image1T, typename Image2T>
454 double innerProduct(Image1T const& lhs,
455  Image2T const& rhs,
456  int border
457  ) {
458  if (lhs.getWidth() <= 2*border || lhs.getHeight() <= 2*border) {
459  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
460  (boost::format("All image pixels are in the border of width %d: %dx%d") %
461  border % lhs.getWidth() % lhs.getHeight()).str());
462  }
463 
464  double sum = 0.0;
465  //
466  // Handle I.I specially for efficiency, and to avoid advancing the iterator twice
467  //
468  if (imagesAreIdentical(lhs, rhs)) {
469  for (int y = border; y != lhs.getHeight() - border; ++y) {
470  for (typename Image1T::const_x_iterator lptr = lhs.row_begin(y) + border,
471  lend = lhs.row_end(y) - border; lptr != lend; ++lptr) {
472  typename Image1T::Pixel val = *lptr;
473  if (lsst::utils::isfinite(val)) {
474  sum += val*val;
475  }
476  }
477  }
478  } else {
479  if (lhs.getDimensions() != rhs.getDimensions()) {
480  throw LSST_EXCEPT(lsst::pex::exceptions::LengthError,
481  (boost::format("Dimension mismatch: %dx%d v. %dx%d") %
482  lhs.getWidth() % lhs.getHeight() % rhs.getWidth() % rhs.getHeight()).str());
483  }
484 
485  for (int y = border; y != lhs.getHeight() - border; ++y) {
486  typename Image2T::const_x_iterator rptr = rhs.row_begin(y) + border;
487  for (typename Image1T::const_x_iterator lptr = lhs.row_begin(y) + border,
488  lend = lhs.row_end(y) - border; lptr != lend; ++lptr, ++rptr) {
489  double const tmp = (*lptr)*(*rptr);
490  if (lsst::utils::isfinite(tmp)) {
491  sum += tmp;
492  }
493  }
494  }
495  }
496 
497  return sum;
498 }
499 
500 /************************************************************************************************************/
501 //
502 // Explicit instantiations
503 //
505 #define INSTANTIATE(T) \
506  template class ImagePca<Image<T> >; \
507  template double innerProduct(Image<T> const&, Image<T> const&, int); \
508  template class ImagePca<MaskedImage<T> >;
509 
510 #define INSTANTIATE2(T, U) \
511  template double innerProduct(Image<T> const&, Image<U> const&, int); \
512  template double innerProduct(Image<U> const&, Image<T> const&, int);
513 
514 INSTANTIATE(boost::uint16_t)
515 INSTANTIATE(boost::uint64_t)
516 INSTANTIATE(int)
517 INSTANTIATE(float)
518 INSTANTIATE(double)
519 
520 INSTANTIATE2(float, double) // the two types must be different
522 
523 }}}
int y
tbl::Key< double > weight
Support for PCA analysis of 2-D images.
double innerProduct(Image1T const &lhs, Image2T const &rhs, int const border=0)
Definition: ImagePca.cc:454
#define INSTANTIATE(MATCH)
ImageList getImageList() const
Return the list of images being analyzed.
Definition: ImagePca.cc:92
void addImage(typename ImageT::Ptr img, double flux=0.0)
Definition: ImagePca.cc:65
geom::Extent2I const getDimensions() const
Return the dimension of the images being analyzed.
Definition: ImagePca.h:61
boost::enable_if< typename ExpressionTraits< Scalar >::IsScalar, Scalar >::type sum(Scalar const &scalar)
Definition: operators.h:1250
bool val
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
ImageT::Ptr getMean() const
Definition: ImagePca.cc:101
afw::table::PointKey< int > dimensions
Definition: GaussianPsf.cc:42
std::vector< typename ImageT::Ptr > ImageList
Definition: ImagePca.h:52
double x
int isfinite(T t)
Definition: ieee.h:100
void ImageT ImageT int float saturatedPixelValue int const height
Definition: saturated.cc:44
virtual void analyze()
Definition: ImagePca.cc:135
ImagePca(bool constantWeight=true)
ctor
Definition: ImagePca.cc:49
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
Compute Image Statistics.
afw::table::Key< double > b
bool _same
Definition: ImagePca.cc:439
virtual double updateBadPixels(unsigned long mask, int const ncomp)
Definition: ImagePca.cc:417