lsst.jointcal  16.0-18-gdf247dd+4
PhotometryTransfo.cc
Go to the documentation of this file.
1 #include "ndarray.h"
2 #include "Eigen/Core"
3 
5 
6 #include "lsst/jointcal/Point.h"
8 
9 namespace lsst {
10 namespace jointcal {
11 
12 // ------------------ PhotometryTransfoChebyshev helpers ---------------------------------------------------
13 
14 namespace {
15 
16 // To evaluate a 1-d Chebyshev function without needing to have workspace, we use the
17 // Clenshaw algorith, which is like going through the recurrence relation in reverse.
18 // The CoeffGetter argument g is something that behaves like an array, providing access
19 // to the coefficients.
20 template <typename CoeffGetter>
21 double evaluateFunction1d(CoeffGetter g, double x, int size) {
22  double b_kp2 = 0.0, b_kp1 = 0.0;
23  for (int k = (size - 1); k > 0; --k) {
24  double b_k = g[k] + 2 * x * b_kp1 - b_kp2;
25  b_kp2 = b_kp1;
26  b_kp1 = b_k;
27  }
28  return g[0] + x * b_kp1 - b_kp2;
29 }
30 
31 // This class imitates a 1-d array, by running evaluateFunction1d on a nested dimension;
32 // this lets us reuse the logic in evaluateFunction1d for both dimensions. Essentially,
33 // we run evaluateFunction1d on a column of coefficients to evaluate T_i(x), then pass
34 // the result of that to evaluateFunction1d with the results as the "coefficients" associated
35 // with the T_j(y) functions.
36 struct RecursionArrayImitator {
37  double operator[](int i) const {
38  return evaluateFunction1d(coefficients[i], x, coefficients.getSize<1>());
39  }
40 
41  RecursionArrayImitator(ndarray::Array<double const, 2, 2> const &coefficients_, double x_)
42  : coefficients(coefficients_), x(x_) {}
43 
44  ndarray::Array<double const, 2, 2> coefficients;
45  double x;
46 };
47 
48 // Compute an affine transform that maps an arbitrary box to [-1,1]x[-1,1]
49 afw::geom::AffineTransform makeChebyshevRangeTransform(afw::geom::Box2D const &bbox) {
50  return afw::geom::AffineTransform(
51  afw::geom::LinearTransform::makeScaling(2.0 / bbox.getWidth(), 2.0 / bbox.getHeight()),
52  afw::geom::Extent2D(-(2.0 * bbox.getCenterX()) / bbox.getWidth(),
53  -(2.0 * bbox.getCenterY()) / bbox.getHeight()));
54 }
55 
56 // Initialize a "unit" Chebyshev
57 ndarray::Array<double, 2, 2> _initializeChebyshev(size_t order, bool identity) {
58  ndarray::Array<double, 2, 2> coeffs = ndarray::allocate(ndarray::makeVector(order + 1, order + 1));
59  coeffs.deep() = 0.0;
60  if (identity) {
61  coeffs[0][0] = 1;
62  }
63  return coeffs;
64 }
65 } // namespace
66 
67 PhotometryTransfoChebyshev::PhotometryTransfoChebyshev(size_t order, afw::geom::Box2D const &bbox,
68  bool identity)
69  : _bbox(bbox),
70  _toChebyshevRange(makeChebyshevRangeTransform(bbox)),
71  _coefficients(_initializeChebyshev(order, identity)),
72  _order(order),
73  _nParameters((order + 1) * (order + 2) / 2) {}
74 
76  afw::geom::Box2D const &bbox)
77  : _bbox(bbox),
78  _toChebyshevRange(makeChebyshevRangeTransform(bbox)),
79  _coefficients(coefficients),
80  _order(coefficients.size() - 1),
81  _nParameters((_order + 1) * (_order + 2) / 2) {}
82 
83 void PhotometryTransfoChebyshev::offsetParams(Eigen::VectorXd const &delta) {
84  // NOTE: the indexing in this method and computeParameterDerivatives must be kept consistent!
85  Eigen::VectorXd::Index k = 0;
86  for (ndarray::Size j = 0; j <= _order; ++j) {
87  ndarray::Size const iMax = _order - j; // to save re-computing `i+j <= order` every inner step.
88  for (ndarray::Size i = 0; i <= iMax; ++i, ++k) {
89  _coefficients[j][i] -= delta[k];
90  }
91  }
92 }
93 
94 namespace {
95 // The integral of T_n(x) over [-1,1]:
96 // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Differentiation_and_integration
97 double integrateTn(int n) {
98  if (n % 2 == 1)
99  return 0;
100  else
101  return 2.0 / (1.0 - static_cast<double>(n * n));
102 }
103 } // namespace
104 
105 double PhotometryTransfoChebyshev::integrate() const {
106  double result = 0;
107  double determinant = _bbox.getArea() / 4.0;
108  for (ndarray::Size j = 0; j < _coefficients.getSize<0>(); j++) {
109  for (ndarray::Size i = 0; i < _coefficients.getSize<1>(); i++) {
110  result += _coefficients[j][i] * integrateTn(i) * integrateTn(j);
111  }
112  }
113  return result * determinant;
114 }
115 
116 double PhotometryTransfoChebyshev::mean() const { return integrate() / _bbox.getArea(); }
117 
119  Eigen::VectorXd parameters(_nParameters);
120  // NOTE: the indexing in this method and offsetParams must be kept consistent!
121  Eigen::VectorXd::Index k = 0;
122  for (ndarray::Size j = 0; j <= _order; ++j) {
123  ndarray::Size const iMax = _order - j; // to save re-computing `i+j <= order` every inner step.
124  for (ndarray::Size i = 0; i <= iMax; ++i, ++k) {
125  parameters[k] = _coefficients[j][i];
126  }
127  }
128 
129  return parameters;
130 }
131 
132 double PhotometryTransfoChebyshev::computeChebyshev(double x, double y) const {
133  afw::geom::Point2D p = _toChebyshevRange(afw::geom::Point2D(x, y));
134  return evaluateFunction1d(RecursionArrayImitator(_coefficients, p.getX()), p.getY(),
135  _coefficients.getSize<0>());
136 }
137 
139  Eigen::Ref<Eigen::VectorXd> derivatives) const {
140  afw::geom::Point2D p = _toChebyshevRange(afw::geom::Point2D(x, y));
141  // Algorithm: compute all the individual components recursively (since we'll need them anyway),
142  // then combine them into the final answer vectors.
143  Eigen::VectorXd Tnx(_order + 1);
144  Eigen::VectorXd Tmy(_order + 1);
145  Tnx[0] = 1;
146  Tmy[0] = 1;
147  if (_order >= 1) {
148  Tnx[1] = p.getX();
149  Tmy[1] = p.getY();
150  }
151  for (ndarray::Size i = 2; i <= _order; ++i) {
152  Tnx[i] = 2 * p.getX() * Tnx[i - 1] - Tnx[i - 2];
153  Tmy[i] = 2 * p.getY() * Tmy[i - 1] - Tmy[i - 2];
154  }
155 
156  // NOTE: the indexing in this method and offsetParams must be kept consistent!
157  Eigen::VectorXd::Index k = 0;
158  for (ndarray::Size j = 0; j <= _order; ++j) {
159  ndarray::Size const iMax = _order - j; // to save re-computing `i+j <= order` every inner step.
160  for (ndarray::Size i = 0; i <= iMax; ++i, ++k) {
161  derivatives[k] = Tmy[j] * Tnx[i];
162  }
163  }
164 }
165 
166 } // namespace jointcal
167 } // namespace lsst
Eigen::VectorXd getParameters() const override
Get a copy of the parameters of this model, in the same order as offsetParams.
double integrateTn(int n)
Class for a simple mapping implementing a generic Gtransfo.
std::unique_ptr< SchemaItem< U > > result
PhotometryTransfoChebyshev(size_t order, afw::geom::Box2D const &bbox, bool identity)
Create a Chebyshev transfo with terms up to order in (x*y).
void offsetParams(Eigen::VectorXd const &delta) override
Offset the parameters by some (negative) amount during fitting.
double x
ndarray::Array< double const, 2, 2 > coefficients
void computeChebyshevDerivatives(double x, double y, Eigen::Ref< Eigen::VectorXd > derivatives) const
Set the derivatives of this polynomial at x,y.
double computeChebyshev(double x, double y) const
Return the value of this polynomial at x,y.