lsst.jointcal  16.0-26-g3163dd8
AstrometryTransform.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 /*
3  * This file is part of jointcal.
4  *
5  * Developed for the LSST Data Management System.
6  * This product includes software developed by the LSST Project
7  * (https://www.lsst.org).
8  * See the COPYRIGHT file at the top-level directory of this distribution
9  * for details of code ownership.
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program. If not, see <https://www.gnu.org/licenses/>.
23  */
24 
25 #include <iostream>
26 #include <iomanip>
27 #include <iterator> /* for ostream_iterator */
28 #include <limits>
29 #include <cmath>
30 #include <fstream>
31 #include "assert.h"
32 #include <sstream>
33 
34 #include "Eigen/Core"
35 
36 #include "lsst/log/Log.h"
37 #include "lsst/afw/geom/Point.h"
39 #include "lsst/jointcal/Frame.h"
41 #include "lsst/pex/exceptions.h"
42 #include "Eigen/Cholesky"
43 
45 
46 using namespace std;
47 
48 namespace {
49 LOG_LOGGER _log = LOG_GET("jointcal.AstrometryTransform");
50 }
51 
52 namespace lsst {
53 namespace jointcal {
54 
56  const AstrometryTransformPolynomial *shift =
57  dynamic_cast<const AstrometryTransformPolynomial *>(transform);
58  if (shift == nullptr) return false;
59 
60  static const double eps = 1e-5;
61 
62  double dx = shift->coeff(0, 0, 0);
63  double dy = shift->coeff(0, 0, 1);
64 
65  static Point dumb(4000, 4000);
66  if (fabs(dx - int(floor(dx + 0.5))) < eps && fabs(dy - int(floor(dy + 0.5))) < eps &&
67  fabs(dumb.x + dx - shift->apply(dumb).x) < eps && fabs(dumb.y + dy - shift->apply(dumb).y) < eps)
68  return true;
69 
70  return false;
71 }
72 
73 /********* AstrometryTransform ***********************/
74 
75 Frame AstrometryTransform::apply(Frame const &inputframe, bool inscribed) const {
76  // 2 opposite corners
77  double xtmin1, xtmax1, ytmin1, ytmax1;
78  apply(inputframe.xMin, inputframe.yMin, xtmin1, ytmin1);
79  apply(inputframe.xMax, inputframe.yMax, xtmax1, ytmax1);
80  Frame fr1(std::min(xtmin1, xtmax1), std::min(ytmin1, ytmax1), std::max(xtmin1, xtmax1),
81  std::max(ytmin1, ytmax1));
82  // 2 other corners
83  double xtmin2, xtmax2, ytmin2, ytmax2;
84  apply(inputframe.xMin, inputframe.yMax, xtmin2, ytmax2);
85  apply(inputframe.xMax, inputframe.yMin, xtmax2, ytmin2);
86  Frame fr2(std::min(xtmin2, xtmax2), std::min(ytmin2, ytmax2), std::max(xtmin2, xtmax2),
87  std::max(ytmin2, ytmax2));
88 
89  if (inscribed) return fr1 * fr2;
90  return fr1 + fr2;
91 }
92 
93 std::unique_ptr<AstrometryTransform> AstrometryTransform::composeAndReduce(
94  AstrometryTransform const &) const { // by default no way to compose
96 }
97 
98 double AstrometryTransform::getJacobian(const double x, const double y) const {
99  double x2, y2;
100  double eps = x * 0.01;
101  if (eps == 0) eps = 0.01;
102  apply(x, y, x2, y2);
103  double dxdx, dydx;
104  apply(x + eps, y, dxdx, dydx);
105  dxdx -= x2;
106  dydx -= y2;
107  double dxdy, dydy;
108  apply(x, y + eps, dxdy, dydy);
109  dxdy -= x2;
110  dydy -= y2;
111  return ((dxdx * dydy - dxdy * dydx) / (eps * eps));
112 }
113 
117 void AstrometryTransform::computeDerivative(Point const &where, AstrometryTransformLinear &derivative,
118  const double step) const {
119  double x = where.x;
120  double y = where.y;
121  double xp0, yp0;
122  apply(x, y, xp0, yp0);
123 
124  double xp, yp;
125  apply(x + step, y, xp, yp);
126  derivative.a11() = (xp - xp0) / step;
127  derivative.a21() = (yp - yp0) / step;
128  apply(x, y + step, xp, yp);
129  derivative.a12() = (xp - xp0) / step;
130  derivative.a22() = (yp - yp0) / step;
131  derivative.dx() = 0;
132  derivative.dy() = 0;
133 }
134 
135 AstrometryTransformLinear AstrometryTransform::linearApproximation(Point const &where,
136  const double step) const {
137  Point outwhere = apply(where);
139  computeDerivative(where, der, step);
140  return AstrometryTransformLinearShift(outwhere.x, outwhere.y) * der *
141  AstrometryTransformLinearShift(-where.x, -where.y);
142 }
143 
144 void AstrometryTransform::transformPosAndErrors(FatPoint const &in, FatPoint &out) const {
145  FatPoint res; // in case in and out are the same address...
146  res = apply(in);
148  // could save a call here, since Derivative needs the transform of where that we already have
149  // 0.01 may not be a very good idea in all cases. May be we should provide a way of altering that.
150  computeDerivative(in, der, 0.01);
151  double a11 = der.A11();
152  double a22 = der.A22();
153  double a21 = der.A21();
154  double a12 = der.A12();
155  res.vx = a11 * (a11 * in.vx + 2 * a12 * in.vxy) + a12 * a12 * in.vy;
156  res.vy = a21 * a21 * in.vx + a22 * a22 * in.vy + 2. * a21 * a22 * in.vxy;
157  res.vxy = a21 * a11 * in.vx + a22 * a12 * in.vy + (a21 * a12 + a11 * a22) * in.vxy;
158  out = res;
159 }
160 
161 void AstrometryTransform::transformErrors(Point const &where, const double *vIn, double *vOut) const {
163  computeDerivative(where, der, 0.01);
164  double a11 = der.A11();
165  double a22 = der.A22();
166  double a21 = der.A21();
167  double a12 = der.A12();
168 
169  /* (a11 a12) (vxx vxy)
170  M = ( ) and V = ( )
171  (a21 a22) (xvy vyy)
172 
173  Vxx = Vin[0], vyy = Vin[1], Vxy = Vin[2];
174  we want to compute M*V*tp(M)
175  A lin alg light package would be perfect...
176  */
177  int xx = 0;
178  int yy = 1;
179  int xy = 2;
180  // M*V :
181 
182  double b11 = a11 * vIn[xx] + a12 * vIn[xy];
183  double b22 = a21 * vIn[xy] + a22 * vIn[yy];
184  double b12 = a11 * vIn[xy] + a12 * vIn[yy];
185  double b21 = a21 * vIn[xx] + a22 * vIn[xy];
186 
187  // (M*V) * tp(M)
188 
189  vOut[xx] = b11 * a11 + b12 * a12;
190  vOut[xy] = b11 * a21 + b12 * a22;
191  vOut[yy] = b21 * a21 + b22 * a22;
192 }
193 
194 std::unique_ptr<AstrometryTransform> AstrometryTransform::roughInverse(const Frame &region) const {
195  // "in" and "out" refer to the inverse direction.
196  Point centerOut = region.getCenter();
197  Point centerIn = apply(centerOut);
199  computeDerivative(centerOut, der, std::sqrt(region.getArea()) / 5.);
200  der = der.inverted();
201  der = AstrometryTransformLinearShift(centerOut.x, centerOut.y) * der *
202  AstrometryTransformLinearShift(-centerIn.x, -centerIn.y);
204 }
205 
206 /* implement one in AstrometryTransform, so that all derived
207  classes do not need to provide one... */
208 
209 /* the routines that follow are used for ea generic parameter
210  transformation serialization, used e.g. for fits. Enables
211  to manipulate transformation parameters as vectors.
212 */
213 
214 // not dummy : what it does is virtual because paramRef is virtual.
215 void AstrometryTransform::getParams(double *params) const {
216  int npar = getNpar();
217  for (int i = 0; i < npar; ++i) params[i] = paramRef(i);
218 }
219 
220 void AstrometryTransform::offsetParams(Eigen::VectorXd const &delta) {
221  int npar = getNpar();
222  for (int i = 0; i < npar; ++i) paramRef(i) += delta[i];
223 }
224 
225 double AstrometryTransform::paramRef(const int) const {
227  std::string("AstrometryTransform::paramRef should never be called "));
228 }
229 
230 double &AstrometryTransform::paramRef(const int) {
232  "AstrometryTransform::paramRef should never be called ");
233 }
234 
235 void AstrometryTransform::paramDerivatives(Point const &, double *, double *) const {
237  "AstrometryTransform::paramDerivatives() should never be called ");
238 }
239 
241  transform.dump(stream);
242  return stream;
243 }
244 
245 void AstrometryTransform::write(const std::string &fileName) const {
246  ofstream s(fileName.c_str());
247  write(s);
248  bool ok = !s.fail();
249  s.close();
250  if (!ok)
252  "AstrometryTransform::write, something went wrong for file " + fileName);
253 }
254 
255 void AstrometryTransform::write(ostream &stream) const {
256  throw LSST_EXCEPT(
258  "AstrometryTransform::write(ostream), should never be called. MEans that it is missing in some "
259  "derived class ");
260 }
261 
262 /******************* GTransfoInverse ****************/
263 /* inverse transformation, solved by iterations. Before using
264  it (probably via AstrometryTransform::inverseTransform), consider
265  seriously StarMatchList::inverseTransform */
267 private:
270  double precision2;
271 
272 public:
273  AstrometryTransformInverse(const AstrometryTransform *direct, const double precision,
274  const Frame &region);
275 
278  void apply(const double xIn, const double yIn, double &xOut, double &yOut) const;
279 
280  void dump(ostream &stream) const;
281 
282  double fit(StarMatchList const &starMatchList);
283 
285 
287 
289  std::unique_ptr<AstrometryTransform> roughInverse(const Frame &) const { return _direct->clone(); }
290 
293  return _direct->clone();
294  }
295 
297 
298 private:
299  void operator=(AstrometryTransformInverse const &);
300 };
301 
302 std::unique_ptr<AstrometryTransform> AstrometryTransform::inverseTransform(const double precision,
303  const Frame &region) const {
304  return std::unique_ptr<AstrometryTransform>(new AstrometryTransformInverse(this, precision, region));
305 }
306 
307 AstrometryTransformInverse::AstrometryTransformInverse(const AstrometryTransform *direct,
308  const double precision, const Frame &region) {
309  _direct = direct->clone();
310  _roughInverse = _direct->roughInverse(region);
311  precision2 = precision * precision;
312 }
313 
314 AstrometryTransformInverse::AstrometryTransformInverse(AstrometryTransformInverse const &model)
315  : AstrometryTransform() {
316  _direct = model._direct->clone();
317  _roughInverse = model._roughInverse->clone();
318  precision2 = model.precision2;
319 }
320 
322 
323 void AstrometryTransformInverse::operator=(AstrometryTransformInverse const &model) {
324  _direct = model._direct->clone();
325  _roughInverse = model._roughInverse->clone();
326  precision2 = model.precision2;
327 }
328 
329 void AstrometryTransformInverse::apply(const double xIn, const double yIn, double &xOut, double &yOut) const {
330  Point in(xIn, yIn);
331  Point outGuess = _roughInverse->apply(in);
332  AstrometryTransformLinear directDer, reverseDer;
333  int loop = 0;
334  int maxloop = 20;
335  double move2;
336  do {
337  loop++;
338  Point inGuess = _direct->apply(outGuess);
339  _direct->computeDerivative(outGuess, directDer);
340  reverseDer = directDer.inverted();
341  double xShift, yShift;
342  reverseDer.apply(xIn - inGuess.x, yIn - inGuess.y, xShift, yShift);
343  outGuess.x += xShift;
344  outGuess.y += yShift;
345  move2 = xShift * xShift + yShift * yShift;
346  } while ((move2 > precision2) && (loop < maxloop));
347  if (loop == maxloop) LOGLS_WARN(_log, "Problems applying AstrometryTransformInverse at " << in);
348  xOut = outGuess.x;
349  yOut = outGuess.y;
350 }
351 
353  stream << " AstrometryTransformInverse of :" << endl << *_direct << endl;
354 }
355 
358  "Cannot fit a AstrometryTransformInverse. Use StarMatchList::inverseTransform instead.");
359 }
360 
363 }
364 
365 /************* AstrometryTransformComposition **************/
366 
367 // This class was done to allow composition of AstrometryTransform's, without specifications of their types.
368 // does not need to be public. Invoked by compose(left,right)
369 
373 private:
374  std::unique_ptr<AstrometryTransform> _first, _second;
375 
376 public:
379 
381  void apply(const double xIn, const double yIn, double &xOut, double &yOut) const;
382  void dump(ostream &stream = cout) const;
383 
385  double fit(StarMatchList const &starMatchList);
386 
389 };
390 
392  AstrometryTransform const &first) {
393  _first = first.clone();
394  _second = second.clone();
395 }
396 
397 void AstrometryTransformComposition::apply(const double xIn, const double yIn, double &xOut,
398  double &yOut) const {
399  double xout, yout;
400  _first->apply(xIn, yIn, xout, yout);
401  _second->apply(xout, yout, xOut, yOut);
402 }
403 
405  _first->dump(stream);
406  _second->dump(stream);
407 }
408 
410  /* fits only one of them. could check that first can actually be fitted... */
411  return _first->fit(starMatchList);
412 }
413 
415  return std::make_unique<AstrometryTransformComposition>(*_second, *_first);
416 }
417 
419 
421  AstrometryTransformIdentity const &right) {
422  return left.clone();
423 }
424 
426  AstrometryTransform const &right) {
427  // Try to use the composeAndReduce method from left. If absent, AstrometryTransform::composeAndReduce
428  // returns NULL. composeAndReduce is non trivial for polynomials.
430  // composition == NULL means no reduction: just build a Composition that pipelines "left" and "right".
431  if (composition == nullptr)
432  return std::make_unique<AstrometryTransformComposition>(left, right);
433  else
434  return composition;
435 }
436 
437 // just a speed up, to avoid useless numerical derivation.
439  const double) const {
440  derivative = AstrometryTransformLinear();
441 }
442 
444  const double) const {
446  return result; // rely on default AstrometryTransformlin constructor;
447 }
448 
450  return std::make_shared<ast::UnitMap>(2); // a AstrometryTransformIdentity is identically ast::UnitMap(2)
451 }
452 
454  stream << "AstrometryTransformIdentity 1" << endl;
455 }
456 
458  int format;
459  stream >> format;
460  if (format != 1)
462  " AstrometryTransformIdentity::read : format is not 1 ");
463 }
464 
465 /*************** AstrometryTransformPolynomial **************************************/
466 
468 
470  _nterms = (order + 1) * (order + 2) / 2;
471 
472  // allocate and fill coefficients
473  _coeffs.resize(2 * _nterms, 0.);
474  // the default is supposed to be the identity, (for order>=1).
475  if (_order >= 1) {
476  coeff(1, 0, 0) = 1;
477  coeff(0, 1, 1) = 1;
478  }
479 }
480 
481 //#ifdef TO_BE_FIXED
483  const Frame &frame, unsigned order,
484  unsigned nPoint) {
485  StarMatchList sm;
486 
487  double step = std::sqrt(fabs(frame.getArea()) / double(nPoint));
488  for (double x = frame.xMin + step / 2; x <= frame.xMax; x += step)
489  for (double y = frame.yMin + step / 2; y <= frame.yMax; y += step) {
490  auto pix = std::make_shared<BaseStar>(x, y, 0, 0);
491  double xtr, ytr;
492  transform->apply(x, y, xtr, ytr);
493  auto tp = std::make_shared<BaseStar>(xtr, ytr, 0, 0);
494  /* These are fake stars so no need to transform fake errors.
495  all errors (and weights) will be equal : */
496  sm.push_back(StarMatch(*pix, *tp, pix, tp));
497  }
499  ret.fit(sm);
500  *this = ret;
501 }
502 //#endif
503 
506  unsigned const order, unsigned const nSteps) {
507  jointcal::StarMatchList starMatchList;
508  double xStart = domain.xMin;
509  double yStart = domain.yMin;
510  double xStep = domain.getWidth() / (nSteps + 1);
511  double yStep = domain.getHeight() / (nSteps + 1);
512  for (unsigned i = 0; i < nSteps; ++i) {
513  for (unsigned j = 0; j < nSteps; ++j) {
514  // TODO: once DM-4044 is done, we can remove the redundancy in `Point`/`Point2D` here
515  jointcal::Point in(xStart + i * xStep, yStart + j * yStep);
516  afw::geom::Point2D inAfw(in.x, in.y);
517  afw::geom::Point2D outAfw = transform->applyForward(inAfw);
518  jointcal::Point out(outAfw.getX(), outAfw.getY());
519  starMatchList.emplace_back(in, out, nullptr, nullptr);
520  }
521  }
522  AstrometryTransformPolynomial poly(order);
523  poly.fit(starMatchList);
524  *this = poly;
525 }
526 
527 void AstrometryTransformPolynomial::computeMonomials(double xIn, double yIn, double *monomial) const {
528  /* The ordering of monomials is implemented here.
529  You may not change it without updating the "mapping" routines
530  coeff(unsigned, unsigned, unsigned).
531  I (P.A.) did not find a clever way to loop over monomials.
532  Improvements welcome.
533  This routine is used also by the fit to fill monomials.
534  We could certainly be more elegant.
535  */
536 
537  double xx = 1;
538  for (unsigned ix = 0; ix <= _order; ++ix) {
539  double yy = 1;
540  unsigned k = ix * (ix + 1) / 2;
541  for (unsigned iy = 0; iy <= _order - ix; ++iy) {
542  monomial[k] = xx * yy;
543  yy *= yIn;
544  k += ix + iy + 2;
545  }
546  xx *= xIn;
547  }
548 }
549 
550 void AstrometryTransformPolynomial::setOrder(const unsigned order) {
551  _order = order;
552  unsigned old_nterms = _nterms;
553  _nterms = (_order + 1) * (_order + 2) / 2;
554 
555  // temporarily save coefficients
556  vector<double> old_coeffs = _coeffs;
557  // reallocate enough size
558  _coeffs.resize(2 * _nterms);
559  // reassign to zero (this is necessary because ycoeffs
560  // are after xcoeffs and so their meaning changes
561  for (unsigned k = 0; k < _nterms; ++k) _coeffs[k] = 0;
562  // put back what we had before
563  unsigned kmax = min(old_nterms, _nterms);
564  for (unsigned k = 0; k < kmax; ++k) {
565  _coeffs[k] = old_coeffs[k]; // x terms
566  _coeffs[k + _nterms] = old_coeffs[k + old_nterms]; // y terms
567  }
568 }
569 
570 /* this is reasonably fast, when optimized */
571 void AstrometryTransformPolynomial::apply(const double xIn, const double yIn, double &xOut,
572  double &yOut) const {
573  /*
574  This routine computes the monomials only once for both
575  polynomials. This is why AstrometryTransformPolynomial does not use an auxilary
576  class (such as PolyXY) to handle each polynomial.
577 
578  The code works even if &xIn == &xOut (or &yIn == &yOut)
579  It uses Variable Length Allocation (VLA) rather than a vector<double>
580  because allocating the later costs about 50 ns. All VLA uses are tagged.
581  */
582  double monomials[_nterms]; // this is VLA, which is (perhaps) not casher C++
583  computeMonomials(xIn, yIn, monomials);
584 
585  xOut = 0;
586  yOut = 0;
587  const double *c = &_coeffs[0];
588  const double *pm = &monomials[0];
589  // the ordering of the coefficients and the monomials are identical.
590  for (int k = _nterms; k--;) xOut += (*(pm++)) * (*(c++));
591  pm = &monomials[0];
592  for (int k = _nterms; k--;) yOut += (*(pm++)) * (*(c++));
593 }
594 
596  AstrometryTransformLinear &derivative,
597  const double step)
598  const { /* routine checked against numerical derivatives from AstrometryTransform::Derivative */
599  if (_order == 1) {
600  derivative = AstrometryTransformLinear(*this);
601  derivative.dx() = derivative.dy() = 0;
602  return;
603  }
604 
605  double dermx[2 * _nterms]; // VLA
606  double *dermy = dermx + _nterms;
607  double xin = where.x;
608  double yin = where.y;
609 
610  double xx = 1;
611  double xxm1 = 1; // xx^(ix-1)
612  for (unsigned ix = 0; ix <= _order; ++ix) {
613  unsigned k = (ix) * (ix + 1) / 2;
614  // iy = 0
615  dermx[k] = ix * xxm1;
616  dermy[k] = 0;
617  k += ix + 2;
618  double yym1 = 1; // yy^(iy-1)
619  for (unsigned iy = 1; iy <= _order - ix; ++iy) {
620  dermx[k] = ix * xxm1 * yym1 * yin;
621  dermy[k] = iy * xx * yym1;
622  yym1 *= yin;
623  k += ix + iy + 2;
624  }
625  xx *= xin;
626  if (ix >= 1) xxm1 *= xin;
627  }
628 
629  derivative.dx() = 0;
630  derivative.dy() = 0;
631 
632  const double *mx = &dermx[0];
633  const double *my = &dermy[0];
634  const double *c = &_coeffs[0];
635  // dx'
636  double a11 = 0, a12 = 0;
637  for (int k = _nterms; k--;) {
638  a11 += (*(mx++)) * (*c);
639  a12 += (*(my++)) * (*(c++));
640  }
641  derivative.a11() = a11;
642  derivative.a12() = a12;
643  // dy'
644  double a21 = 0, a22 = 0;
645  mx = &dermx[0];
646  my = &dermy[0];
647  for (int k = _nterms; k--;) {
648  a21 += (*(mx++)) * (*c);
649  a22 += (*(my++)) * (*(c++));
650  }
651  derivative.a21() = a21;
652  derivative.a22() = a22;
653 }
654 
656  /*
657  The results from this routine were compared to what comes out
658  from apply and transformErrors. The Derivative routine was
659  checked against numerical derivatives from
660  AstrometryTransform::Derivative. (P.A dec 2009).
661 
662  This routine could be made much simpler by calling apply and
663  Derivative (i.e. you just suppress it, and the fallback is the
664  generic version in AstrometryTransform). BTW, I checked that both routines
665  provide the same result. This version is however faster
666  (monomials get recycled).
667  */
668  double monomials[_nterms]; // VLA
669 
670  FatPoint res; // to store the result, because nothing forbids &in == &out.
671 
672  double dermx[2 * _nterms]; // monomials for derivative w.r.t. x (VLA)
673  double *dermy = dermx + _nterms; // same for y
674  double xin = in.x;
675  double yin = in.y;
676 
677  double xx = 1;
678  double xxm1 = 1; // xx^(ix-1)
679  for (unsigned ix = 0; ix <= _order; ++ix) {
680  unsigned k = (ix) * (ix + 1) / 2;
681  // iy = 0
682  dermx[k] = ix * xxm1;
683  dermy[k] = 0;
684  monomials[k] = xx;
685  k += ix + 2;
686  double yy = yin;
687  double yym1 = 1; // yy^(iy-1)
688  for (unsigned iy = 1; iy <= _order - ix; ++iy) {
689  monomials[k] = xx * yy;
690  dermx[k] = ix * xxm1 * yy;
691  dermy[k] = iy * xx * yym1;
692  yym1 *= yin;
693  yy *= yin;
694  k += ix + iy + 2;
695  }
696  xx *= xin;
697  if (ix >= 1) xxm1 *= xin;
698  }
699 
700  // output position
701  double xout = 0, yout = 0;
702  const double *c = &_coeffs[0];
703  const double *pm = &monomials[0];
704  for (int k = _nterms; k--;) xout += (*(pm++)) * (*(c++));
705  pm = &monomials[0];
706  for (int k = _nterms; k--;) yout += (*(pm++)) * (*(c++));
707  res.x = xout;
708  res.y = yout;
709 
710  // derivatives
711  c = &_coeffs[0];
712  const double *mx = &dermx[0];
713  const double *my = &dermy[0];
714  double a11 = 0, a12 = 0;
715  for (int k = _nterms; k--;) {
716  a11 += (*(mx++)) * (*c);
717  a12 += (*(my++)) * (*(c++));
718  }
719 
720  double a21 = 0, a22 = 0;
721  mx = &dermx[0];
722  my = &dermy[0];
723  for (int k = _nterms; k--;) {
724  a21 += (*(mx++)) * (*c);
725  a22 += (*(my++)) * (*(c++));
726  }
727 
728  // output co-variance
729  res.vx = a11 * (a11 * in.vx + 2 * a12 * in.vxy) + a12 * a12 * in.vy;
730  res.vy = a21 * a21 * in.vx + a22 * a22 * in.vy + 2. * a21 * a22 * in.vxy;
731  res.vxy = a21 * a11 * in.vx + a22 * a12 * in.vy + (a21 * a12 + a11 * a22) * in.vxy;
732  out = res;
733 }
734 
735 /* The coefficient ordering is defined both here *AND* in the
736  AstrometryTransformPolynomial::apply, AstrometryTransformPolynomial::Derivative, ... routines
737  Change all or none ! */
738 
739 double AstrometryTransformPolynomial::coeff(const unsigned degX, const unsigned degY,
740  const unsigned whichCoord) const {
741  assert((degX + degY <= _order) && whichCoord < 2);
742  /* this assertion above is enough to ensure that the index used just
743  below is within bounds since the reserved length is
744  2*_nterms=(order+1)*(order+2) */
745  return _coeffs[(degX + degY) * (degX + degY + 1) / 2 + degY + whichCoord * _nterms];
746 }
747 
748 double &AstrometryTransformPolynomial::coeff(const unsigned degX, const unsigned degY,
749  const unsigned whichCoord) {
750  assert((degX + degY <= _order) && whichCoord < 2);
751  return _coeffs[(degX + degY) * (degX + degY + 1) / 2 + degY + whichCoord * _nterms];
752 }
753 
754 double AstrometryTransformPolynomial::coeffOrZero(const unsigned degX, const unsigned degY,
755  const unsigned whichCoord) const {
756  // assert((degX+degY<=order) && whichCoord<2);
757  assert(whichCoord < 2);
758  if (degX + degY <= _order)
759  return _coeffs[(degX + degY) * (degX + degY + 1) / 2 + degY + whichCoord * _nterms];
760  return 0;
761 }
762 
763 /* parameter serialization for "virtual" fits */
764 double AstrometryTransformPolynomial::paramRef(const int i) const {
765  assert(unsigned(i) < 2 * _nterms);
766  return _coeffs[i];
767 }
768 
770  assert(unsigned(i) < 2 * _nterms);
771  return _coeffs[i];
772 }
773 
774 void AstrometryTransformPolynomial::paramDerivatives(Point const &where, double *dx, double *dy)
775  const { /* first half : dxout/dpar, second half : dyout/dpar */
776  computeMonomials(where.x, where.y, dx);
777  for (unsigned k = 0; k < _nterms; ++k) {
778  dy[_nterms + k] = dx[k];
779  dx[_nterms + k] = dy[k] = 0;
780  }
781 }
782 
783 /* utility for the dump(ostream&) routine */
784 static string monomialString(const unsigned powX, const unsigned powY) {
785  stringstream ss;
786  if (powX + powY) ss << "*";
787  if (powX > 0) ss << "x";
788  if (powX > 1) ss << "^" << powX;
789  if (powY > 0) ss << "y";
790  if (powY > 1) ss << "^" << powY;
791  return ss.str();
792 }
793 
795  auto oldPrecision = stream.precision();
796  stream.precision(12);
797  for (unsigned ic = 0; ic < 2; ++ic) {
798  if (ic == 0)
799  stream << "newx = ";
800  else
801  stream << "newy = ";
802  for (unsigned p = 0; p <= _order; ++p)
803  for (unsigned py = 0; py <= p; ++py) {
804  if (p + py != 0) stream << " + ";
805  stream << coeff(p - py, py, ic) << monomialString(p - py, py);
806  }
807  stream << endl;
808  }
809  if (_order > 0) stream << " Linear determinant = " << determinant() << endl;
810  stream.precision(oldPrecision);
811 }
812 
814  if (_order >= 1) return coeff(1, 0, 0) * coeff(0, 1, 1) - coeff(0, 1, 0) * coeff(1, 0, 1);
815  return 0;
816 }
817 
820  Point center = frame.getCenter();
821  return AstrometryTransformLinearScale(2. / frame.getWidth(), 2. / frame.getHeight()) *
822  AstrometryTransformLinearShift(-center.x, -center.y);
823 }
824 
825 /*utility for the AstrometryTransformPolynomial::fit() routine */
826 static AstrometryTransformLinear shiftAndNormalize(StarMatchList const &starMatchList) {
827  double xav = 0;
828  double x2 = 0;
829  double yav = 0;
830  double y2 = 0;
831  double count = 0;
832  for (auto it = starMatchList.begin(); it != starMatchList.end(); ++it) {
833  const StarMatch &a_match = *it;
834  Point const &point1 = a_match.point1;
835  xav += point1.x;
836  yav += point1.y;
837  x2 += std::pow(point1.x, 2);
838  y2 += std::pow(point1.y, 2);
839  count++;
840  }
841  if (count == 0) return AstrometryTransformLinear();
842  xav /= count;
843  yav /= count;
844  // 3.5 stands for sqrt(12).
845  double xspan = 3.5 * std::sqrt(x2 / count - std::pow(xav, 2));
846  double yspan = 3.5 * std::sqrt(y2 / count - std::pow(yav, 2));
847  return AstrometryTransformLinearScale(2. / xspan, 2. / yspan) *
848  AstrometryTransformLinearShift(-xav, -yav);
849 }
850 
851 static double sq(double x) { return x * x; }
852 
853 double AstrometryTransformPolynomial::computeFit(StarMatchList const &starMatchList,
854  AstrometryTransform const &shiftToCenter,
855  const bool useErrors) {
856  Eigen::MatrixXd A(2 * _nterms, 2 * _nterms);
857  A.setZero();
858  Eigen::VectorXd B(2 * _nterms);
859  B.setZero();
860  double sumr2 = 0;
861  double monomials[_nterms];
862  for (auto it = starMatchList.begin(); it != starMatchList.end(); ++it) {
863  const StarMatch &a_match = *it;
864  Point tmp = shiftToCenter.apply(a_match.point1);
865  FatPoint point1(tmp, a_match.point1.vx, a_match.point1.vy, a_match.point1.vxy);
866  FatPoint const &point2 = a_match.point2;
867  double wxx, wyy, wxy;
868  FatPoint tr1;
869  computeMonomials(point1.x, point1.y, monomials);
870  if (useErrors) {
871  transformPosAndErrors(point1, tr1); // we might consider recycling the monomials
872  double vxx = (tr1.vx + point2.vx);
873  double vyy = (tr1.vy + point2.vy);
874  double vxy = (tr1.vxy + point2.vxy);
875  double det = vxx * vyy - vxy * vxy;
876  wxx = vyy / det;
877  wyy = vxx / det;
878  wxy = -vxy / det;
879  } else {
880  wxx = wyy = 1;
881  wxy = 0;
882  apply(point1.x, point1.y, tr1.x, tr1.y);
883  }
884  double resx = point2.x - tr1.x;
885  double resy = point2.y - tr1.y;
886  sumr2 += wxx * sq(resx) + wyy * sq(resy) + 2 * wxy * resx * resy;
887 
888  double bxcoeff = wxx * resx + wxy * resy;
889  double bycoeff = wyy * resy + wxy * resx;
890  for (unsigned j = 0; j < _nterms; ++j) {
891  for (unsigned i = j; i < _nterms; ++i) {
892  A(i, j) += wxx * monomials[i] * monomials[j];
893  A(i + _nterms, j + _nterms) += wyy * monomials[i] * monomials[j];
894  A(j, i + _nterms) = A(i, j + _nterms) += wxy * monomials[i] * monomials[j];
895  }
896  B(j) += bxcoeff * monomials[j];
897  B(j + _nterms) += bycoeff * monomials[j];
898  }
899  } // end loop on points
900  Eigen::LDLT<Eigen::MatrixXd, Eigen::Lower> factor(A);
901  // should probably throw
902  if (factor.info() != Eigen::Success) {
903  LOGL_ERROR(_log, "AstrometryTransformPolynomial::fit could not factorize");
904  return -1;
905  }
906 
907  Eigen::VectorXd sol = factor.solve(B);
908  for (unsigned k = 0; k < 2 * _nterms; ++k) _coeffs[k] += sol(k);
909  if (starMatchList.size() == _nterms) return 0;
910  return (sumr2 - B.dot(sol));
911 }
912 
913 double AstrometryTransformPolynomial::fit(StarMatchList const &starMatchList) {
914  if (starMatchList.size() < _nterms) {
915  LOGLS_FATAL(_log, "AstrometryTransformPolynomial::fit trying to fit a polynomial transform of order "
916  << _order << " with only " << starMatchList.size() << " matches.");
917  return -1;
918  }
919 
920  AstrometryTransformPolynomial conditionner = shiftAndNormalize(starMatchList);
921 
922  computeFit(starMatchList, conditionner, false); // get a rough solution
923  computeFit(starMatchList, conditionner, true); // weight with it
924  double chi2 = computeFit(starMatchList, conditionner, true); // once more
925 
926  (*this) = (*this) * conditionner;
927  if (starMatchList.size() == _nterms) return 0;
928  return chi2;
929 }
930 
932  AstrometryTransformPolynomial const &right) const {
933  if (getOrder() == 1 && right.getOrder() == 1)
934  return std::make_unique<AstrometryTransformLinear>((*this) * (right)); // does the composition
935  else
936  return std::make_unique<AstrometryTransformPolynomial>((*this) * (right)); // does the composition
937 }
938 
939 /* PolyXY the class used to perform polynomial algebra (and in
940  particular composition) at the coefficient level. This class
941  handles a single polynomial, while a AstrometryTransformPolynomial is a couple of
942  polynomials. This class does not have any routine to evaluate
943  polynomials. Efficiency is not a concern since these routines are
944  seldom used. There is no need to expose this tool class to
945  AstrometryTransform users.
946 */
947 
948 class PolyXY {
949  unsigned order;
950  unsigned nterms;
951  vector<long double> coeffs;
952 
953 public:
954  PolyXY(const int order) : order(order), nterms((order + 1) * (order + 2) / 2) {
955  coeffs.reserve(nterms);
956  coeffs.insert(coeffs.begin(), nterms, 0L); // fill & initialize to 0.
957  }
958 
959  unsigned getOrder() const { return order; }
960 
961  PolyXY(AstrometryTransformPolynomial const &transform, const unsigned whichCoord)
962  : order(transform.getOrder()), nterms((order + 1) * (order + 2) / 2), coeffs(nterms, 0L) {
963  for (unsigned px = 0; px <= order; ++px)
964  for (unsigned py = 0; py <= order - px; ++py) coeff(px, py) = transform.coeff(px, py, whichCoord);
965  }
966 
967  long double coeff(const unsigned powX, const unsigned powY) const {
968  assert(powX + powY <= order);
969  return coeffs.at((powX + powY) * (powX + powY + 1) / 2 + powY);
970  }
971 
972  long double &coeff(const unsigned powX, const unsigned powY) {
973  assert(powX + powY <= order);
974  return coeffs.at((powX + powY) * (powX + powY + 1) / 2 + powY);
975  }
976 };
977 
978 /* ===================== PolyXY Algebra routines ================== */
979 
980 static void operator+=(PolyXY &left, const PolyXY &right) {
981  unsigned rdeg = right.getOrder();
982  assert(left.getOrder() >= rdeg);
983  for (unsigned i = 0; i <= rdeg; ++i)
984  for (unsigned j = 0; j <= rdeg - i; ++j) left.coeff(i, j) += right.coeff(i, j);
985 }
986 
987 /* multiplication by a scalar */
988 static PolyXY operator*(const long double &a, const PolyXY &polyXY) {
989  PolyXY result(polyXY);
990  // no direct access to coefficients: do it the soft way
991  unsigned order = polyXY.getOrder();
992  for (unsigned i = 0; i <= order; ++i)
993  for (unsigned j = 0; j <= order - i; ++j) result.coeff(i, j) *= a;
994  return result;
995 }
996 
998 static PolyXY product(const PolyXY &p1, const PolyXY &p2) {
999  unsigned deg1 = p1.getOrder();
1000  unsigned deg2 = p2.getOrder();
1001  PolyXY result(deg1 + deg2);
1002  for (unsigned i1 = 0; i1 <= deg1; ++i1)
1003  for (unsigned j1 = 0; j1 <= deg1 - i1; ++j1)
1004  for (unsigned i2 = 0; i2 <= deg2; ++i2)
1005  for (unsigned j2 = 0; j2 <= deg2 - i2; ++j2)
1006  result.coeff(i1 + i2, j1 + j2) += p1.coeff(i1, j1) * p2.coeff(i2, j2);
1007  return result;
1008 }
1009 
1010 /* powers[k](x,y) = polyXY(x,y)**k, 0 <= k <= maxP */
1011 static void computePowers(const PolyXY &polyXY, const unsigned maxP, vector<PolyXY> &powers) {
1012  powers.reserve(maxP + 1);
1013  powers.push_back(PolyXY(0));
1014  powers[0].coeff(0, 0) = 1L;
1015  for (unsigned k = 1; k <= maxP; ++k) powers.push_back(product(powers[k - 1], polyXY));
1016 }
1017 
1019 static PolyXY composition(const PolyXY &polyXY, const PolyXY &polyX, const PolyXY &polyY) {
1020  unsigned pdeg = polyXY.getOrder();
1021  PolyXY result(pdeg * max(polyX.getOrder(), polyY.getOrder()));
1022  vector<PolyXY> pXPowers;
1023  vector<PolyXY> pYPowers;
1024  computePowers(polyX, pdeg, pXPowers);
1025  computePowers(polyY, pdeg, pYPowers);
1026  for (unsigned px = 0; px <= pdeg; ++px)
1027  for (unsigned py = 0; py <= pdeg - px; ++py)
1028  result += polyXY.coeff(px, py) * product(pXPowers.at(px), pYPowers.at(py));
1029  return result;
1030 }
1031 
1032 /* ===================== end of PolyXY Algebra routines ============= */
1033 
1034 /* reducing polynomial composition is the reason for PolyXY stuff : */
1035 
1037  AstrometryTransformPolynomial const &right) const {
1038  // split each transform into 2d polynomials
1039  PolyXY plx(*this, 0);
1040  PolyXY ply(*this, 1);
1041  PolyXY prx(right, 0);
1042  PolyXY pry(right, 1);
1043 
1044  // compute the compositions
1045  PolyXY rx(composition(plx, prx, pry));
1046  PolyXY ry(composition(ply, prx, pry));
1047 
1048  // copy the results the hard way.
1049  AstrometryTransformPolynomial result(_order * right._order);
1050  for (unsigned px = 0; px <= result._order; ++px)
1051  for (unsigned py = 0; py <= result._order - px; ++py) {
1052  result.coeff(px, py, 0) = rx.coeff(px, py);
1053  result.coeff(px, py, 1) = ry.coeff(px, py);
1054  }
1055  return result;
1056 }
1057 
1059  AstrometryTransformPolynomial const &right) const {
1060  if (_order >= right._order) {
1061  AstrometryTransformPolynomial res(*this);
1062  for (unsigned i = 0; i <= right._order; ++i)
1063  for (unsigned j = 0; j <= right._order - i; ++j) {
1064  res.coeff(i, j, 0) += right.coeff(i, j, 0);
1065  res.coeff(i, j, 1) += right.coeff(i, j, 1);
1066  }
1067  return res;
1068  } else
1069  return (right + (*this));
1070 }
1071 
1073  AstrometryTransformPolynomial const &right) const {
1074  AstrometryTransformPolynomial res(std::max(_order, right._order));
1075  for (unsigned i = 0; i <= res._order; ++i)
1076  for (unsigned j = 0; j <= res._order - i; ++j) {
1077  res.coeff(i, j, 0) = coeffOrZero(i, j, 0) - right.coeffOrZero(i, j, 0);
1078  res.coeff(i, j, 1) = coeffOrZero(i, j, 1) - right.coeffOrZero(i, j, 1);
1079  }
1080  return res;
1081 }
1082 
1084  auto inverse = inversePolyTransform(*this, domain, 1e-7, _order + 2, 100);
1085  return std::make_shared<ast::PolyMap>(toAstPolyMapCoefficients(), inverse->toAstPolyMapCoefficients());
1086 }
1087 
1089  s << " AstrometryTransformPolynomial 1" << endl;
1090  s << "order " << _order << endl;
1091  int oldprec = s.precision();
1092  s << setprecision(12);
1093  for (unsigned k = 0; k < 2 * _nterms; ++k) s << _coeffs[k] << ' ';
1094  s << endl;
1095  s << setprecision(oldprec);
1096 }
1097 
1099  int format;
1100  s >> format;
1101  if (format != 1)
1103  " AstrometryTransformPolynomial::read : format is not 1 ");
1104 
1105  string order;
1106  s >> order >> _order;
1107  if (order != "order")
1109  " AstrometryTransformPolynomial::read : expecting \"order\" and found " + order);
1110  setOrder(_order);
1111  for (unsigned k = 0; k < 2 * _nterms; ++k) s >> _coeffs[k];
1112 }
1113 
1114 ndarray::Array<double, 2, 2> AstrometryTransformPolynomial::toAstPolyMapCoefficients() const {
1115  int nCoeffs = _coeffs.size();
1116  ndarray::Array<double, 2, 2> result = ndarray::allocate(ndarray::makeVector(nCoeffs, 4));
1117 
1118  ndarray::Size k = 0;
1119  for (unsigned iCoord = 0; iCoord < 2; ++iCoord) {
1120  for (unsigned p = 0; p <= _order; ++p) {
1121  for (unsigned py = 0; py <= p; ++py, ++k) {
1122  result[k][0] = coeff(p - py, py, iCoord);
1123  result[k][1] = iCoord + 1;
1124  result[k][2] = p - py;
1125  result[k][3] = py;
1126  }
1127  }
1128  }
1129 
1130  return result;
1131 }
1132 
1134  Frame const &domain,
1135  double const precision,
1136  int const maxOrder,
1137  unsigned const nSteps) {
1138  StarMatchList sm;
1139  double xStart = domain.xMin;
1140  double yStart = domain.yMin;
1141  double xStep = domain.getWidth() / (nSteps - 1);
1142  double yStep = domain.getHeight() / (nSteps - 1);
1143  for (unsigned i = 0; i < nSteps; ++i) {
1144  for (unsigned j = 0; j < nSteps; ++j) {
1145  Point in(xStart + i * xStep, yStart + j * yStep);
1146  Point out(forward.apply(in));
1147  sm.push_back(StarMatch(out, in, nullptr, nullptr));
1148  }
1149  }
1150  unsigned npairs = sm.size();
1151  int order;
1154  double chi2 = 0;
1155  double oldChi2 = std::numeric_limits<double>::infinity();
1156  for (order = 1; order <= maxOrder; ++order) {
1157  poly.reset(new AstrometryTransformPolynomial(order));
1158  auto success = poly->fit(sm);
1159  if (success == -1) {
1160  std::stringstream errMsg;
1161  errMsg << "Cannot fit a polynomial of order " << order << " with " << nSteps << "^2 points";
1162  throw pexExcept::RuntimeError(errMsg.str());
1163  }
1164  // compute the chi2 ignoring errors:
1165  chi2 = 0;
1166  for (auto const &i : sm) chi2 += i.point2.computeDist2(poly->apply((i.point1)));
1167  LOGLS_TRACE(_log, "inversePoly order " << order << ": " << chi2 << " / " << npairs << " = "
1168  << chi2 / npairs << " < " << precision * precision);
1169 
1170  if (chi2 / npairs < precision * precision) break;
1171 
1172  // If this triggers, we know we did not reach the required precision.
1173  if (chi2 > oldChi2) {
1174  LOGLS_WARN(_log, "inversePolyTransform: chi2 increases ("
1175  << chi2 << " > " << oldChi2 << "); ending fit with order: " << order);
1176  LOGLS_WARN(_log, "inversePolyTransform: requested precision not reached: "
1177  << chi2 << " / " << npairs << " = " << chi2 / npairs << " < "
1178  << precision * precision);
1179  poly = std::move(oldPoly);
1180  order--;
1181  break;
1182  } else {
1183  oldChi2 = chi2;
1184  // Clone it so we don't lose it in the next iteration.
1186  std::shared_ptr<AstrometryTransform>(poly->clone()));
1187  }
1188  }
1189  if (order > maxOrder)
1190  LOGLS_WARN(_log, "inversePolyTransform: Reached max order without reaching requested precision: "
1191  << chi2 << " / " << npairs << " = " << chi2 / npairs << " < "
1192  << precision * precision);
1193  return poly;
1194 }
1195 
1196 /**************** AstrometryTransformLinear ***************************************/
1197 /* AstrometryTransformLinear is a specialized constructor of AstrometryTransformPolynomial
1198  May be it could just disappear ??
1199 */
1200 
1201 AstrometryTransformLinear::AstrometryTransformLinear(const double Dx, const double Dy, const double A11,
1202  const double A12, const double A21, const double A22)
1204  dx() = Dx;
1205  a11() = A11;
1206  a12() = A12;
1207  dy() = Dy;
1208  a21() = A21;
1209  a22() = A22;
1210 }
1211 
1214  if (transform.getOrder() != 1)
1216  "Trying to build a AstrometryTransformLinear from a higher order transform. Aborting. ");
1217  (AstrometryTransformPolynomial &)(*this) = transform;
1218 }
1219 
1221  // There is a general routine in AstrometryTransformPolynomial that would do the job:
1222  // return AstrometryTransformLinear(AstrometryTransformPolynomial::operator*(right));
1223  // however, we are using this composition of linear stuff heavily in
1224  // AstrometryTransform::linearApproximation, itself used in inverseTransform::apply.
1225  // So, for sake of efficiency, and since it is easy, we take a shortcut:
1227  apply(right.Dx(), right.Dy(), result.dx(), result.dy());
1228  result.a11() = A11() * right.A11() + A12() * right.A21();
1229  result.a12() = A11() * right.A12() + A12() * right.A22();
1230  result.a21() = A21() * right.A11() + A22() * right.A21();
1231  result.a22() = A21() * right.A12() + A22() * right.A22();
1232  return result;
1233 }
1234 
1236  const double) const {
1237  derivative = *this;
1238  derivative.coeff(0, 0, 0) = 0;
1239  derivative.coeff(0, 0, 1) = 0;
1240 }
1241 
1243  return *this;
1244 }
1245 
1247  //
1248  // (T1,M1) * (T2,M2) = 1 i.e (0,1) implies
1249  // T1 = -M1 * T2
1250  // M1 = M2^(-1)
1251  //
1252 
1253  double a11 = A11();
1254  double a12 = A12();
1255  double a21 = A21();
1256  double a22 = A22();
1257  double d = (a11 * a22 - a12 * a21);
1258  if (d == 0) {
1259  LOGL_FATAL(_log,
1260  "AstrometryTransformLinear::invert singular transformation: transform contents will be "
1261  "dumped to stderr.");
1262  dump(cerr);
1263  }
1264 
1265  AstrometryTransformLinear result(0, 0, a22 / d, -a12 / d, -a21 / d, a11 / d);
1266  double rdx, rdy;
1267  result.apply(Dx(), Dy(), rdx, rdy);
1268  result.dx() = -rdx;
1269  result.dy() = -rdy;
1270  return result;
1271 }
1272 
1274  const Frame &) const {
1276 }
1277 
1279  throw pexExcept::NotFoundError("GTransfoLinRot::fit not implemented! aborting");
1280 }
1281 
1283  int npairs = starMatchList.size();
1284  if (npairs < 3) {
1285  LOGLS_FATAL(_log, "AstrometryTransformLinearShift::fit trying to fit a linear transform with only "
1286  << npairs << " matches.");
1287  return -1;
1288  }
1289 
1290  double sumr2 = 0; /* used to compute chi2 without relooping */
1291  /* loop on pairs and fill */
1292  Eigen::VectorXd B(2);
1293  B.setZero();
1294  Eigen::MatrixXd A(2, 2);
1295  A.setZero();
1296 
1297  for (auto const &it : starMatchList) {
1298  FatPoint const &point1 = it.point1;
1299  FatPoint const &point2 = it.point2;
1300  double deltax = point2.x - point1.x;
1301  double deltay = point2.y - point1.y;
1302  double vxx = point1.vx + point2.vx;
1303  double vyy = point1.vy + point2.vy;
1304  double vxy = point1.vxy + point2.vxy;
1305  double det = vxx * vyy - vxy * vxy;
1306  double wxx = vyy / det;
1307  double wyy = vxx / det;
1308  double wxy = -vxy / det;
1309  B(0) += deltax * wxx + wxy * deltay;
1310  B(1) += deltay * wyy + wxy * deltax;
1311  A(0, 0) += wxx;
1312  A(1, 1) += wyy;
1313  A(0, 1) += wxy;
1314  sumr2 += deltax * deltax * wxx + deltay * deltay * wyy + 2. * wxy * deltax * deltay;
1315  }
1316  double det = A(0, 0) * A(1, 1) - A(0, 1) * A(1, 0);
1317  if (det <= 0) return -1;
1318  double tmp = A(0, 0);
1319  A(0, 0) = A(1, 1) / det;
1320  A(1, 1) = tmp / det;
1321  A(0, 1) = A(1, 0) = -A(0, 1) / det;
1322  Eigen::VectorXd sol = A * B;
1323  (*this) = AstrometryTransformLinearShift(sol(0), sol(1));
1324  return (sumr2 - sol.dot(B)); // chi2 compact form
1325 }
1326 
1328  const double scaleFactor) {
1329  double c = scaleFactor * std::cos(angleRad);
1330  double s = scaleFactor * std::sin(angleRad);
1331  a11() = a22() = c;
1332  a21() = s;
1333  a12() = -s;
1334 
1335  // we want that the center does not move : transform+M*C = C ==> transform = C - M*C
1336  Point a_point(0., 0.);
1337  if (center) a_point = *center;
1338 
1339  dx() = dy() = 0;
1340  AstrometryTransformPolynomial::apply(a_point.x, a_point.y, dx(), dy()); // compute M*C
1341  dx() = a_point.x - Dx();
1342  dy() = a_point.y - dy();
1343 }
1344 
1345 static double deg2rad(double degree) { return degree * M_PI / 180.; }
1346 
1347 static double rad2deg(double rad) { return rad * 180. / M_PI; }
1348 
1349 /************* WCS transform ******************/
1350 /************** LinPixelToTan *******************/
1351 
1352 /* Implementation note : it seemed wise to incorporate
1353  the radians to degreess convertion into the linPixelToTan
1354  part (right in the constructor), and to do the
1355  opposite operation in the LinPart routine.
1356  When I was coding the fit, I realized that it was a
1357  bad idea. then I realized that the fitting routine
1358  itself was probably useless. Finaly, for a persistor,
1359  it seems bizarre that the stored data is different
1360  from what convention (angles in degrees for WCS's)
1361  would expect.
1362  So, no more "automatic" degrees to radians and
1363  radians to degrees conversion. They are explicitely
1364  done in apply (for TanPixelToRaDec and TanRaDecToPixel).
1365  This is a minor concern though....
1366 */
1367 BaseTanWcs::BaseTanWcs(AstrometryTransformLinear const &pixToTan, Point const &tangentPoint,
1368  const AstrometryTransformPolynomial *corrections) {
1369  // the angles returned by linPixelToTan should be in degrees.
1370  linPixelToTan = pixToTan;
1371  ra0 = deg2rad(tangentPoint.x);
1372  dec0 = deg2rad(tangentPoint.y);
1373  cos0 = std::cos(dec0);
1374  sin0 = std::sin(dec0);
1375  corr = nullptr;
1376  if (corrections) corr.reset(new AstrometryTransformPolynomial(*corrections));
1377 }
1378 
1379 /* with some sort of smart pointer ro handle "corr", we could remove the
1380  copy constructor, the operator = and the destructor */
1381 
1382 // ": AstrometryTransform" suppresses a warning
1384  corr = nullptr;
1385  *this = original;
1386 }
1387 
1388 void BaseTanWcs::operator=(const BaseTanWcs &original) {
1389  linPixelToTan = original.linPixelToTan;
1390  ra0 = original.ra0;
1391  dec0 = original.dec0;
1392  cos0 = std::cos(dec0);
1393  sin0 = std::sin(dec0);
1394  corr = nullptr;
1395  if (original.corr) corr.reset(new AstrometryTransformPolynomial(*original.corr));
1396 }
1397 
1398 void BaseTanWcs::apply(const double xIn, const double yIn, double &xOut, double &yOut) const {
1399  double l, m; // radians in the tangent plane
1400  pixToTangentPlane(xIn, yIn, l, m); // l, m in degrees.
1401  l = deg2rad(l);
1402  m = deg2rad(m); // now in radians
1403  // Code inspired from worldpos.c in wcssubs (ancestor of the wcslib)
1404  /* At variance with wcslib, it collapses the projection to a plane
1405  and expression of sidereal cooordinates into a single set of
1406  operations. */
1407  double dect = cos0 - m * sin0;
1408  if (dect == 0) {
1409  LOGL_WARN(_log, "No sidereal coordinates at pole!");
1410  xOut = 0;
1411  yOut = 0;
1412  return;
1413  }
1414  double rat = ra0 + atan2(l, dect);
1415  dect = atan(std::cos(rat - ra0) * (m * cos0 + sin0) / dect);
1416  if (rat - ra0 > M_PI) rat -= (2. * M_PI);
1417  if (rat - ra0 < -M_PI) rat += (2. * M_PI);
1418  if (rat < 0.0) rat += (2. * M_PI);
1419  // convert to degree
1420  xOut = rad2deg(rat);
1421  yOut = rad2deg(dect);
1422 }
1423 
1424 Point BaseTanWcs::getTangentPoint() const { return Point(rad2deg(ra0), rad2deg(dec0)); }
1425 
1427 
1429  corr = std::move(corrections);
1430 }
1431 
1433  /* CRPIX's are defined by:
1434  ( CD1_1 CD1_2 ) (x - crpix1)
1435  transformed = ( ) * ( )
1436  ( CD2_1 CD2_2 ) (y - crpix2)
1437 
1438  so that CrPix is the point which transforms to (0,0)
1439  */
1441  return Point(inverse.Dx(), inverse.Dy());
1442 }
1443 
1445 
1447  : _skyWcs(skyWcs) {}
1448 
1449 void AstrometryTransformSkyWcs::apply(const double xIn, const double yIn, double &xOut, double &yOut) const {
1450  auto const outCoord = _skyWcs->pixelToSky(afw::geom::Point2D(xIn, yIn));
1451  xOut = outCoord[0].asDegrees();
1452  yOut = outCoord[1].asDegrees();
1453 }
1454 
1456  stream << "AstrometryTransformSkyWcs(" << *_skyWcs << ")";
1457 }
1458 
1459 double AstrometryTransformSkyWcs::fit(const StarMatchList &starMatchList) {
1460  throw LSST_EXCEPT(pex::exceptions::LogicError, "Not implemented");
1461 }
1462 
1465 }
1466 
1467 /*************************** TanPixelToRaDec ***************/
1468 
1470  const AstrometryTransformPolynomial *corrections)
1471  : BaseTanWcs(pixToTan, tangentPoint, corrections) {}
1472 
1473 // ": AstrometryTransform" suppresses a warning
1475 
1477  AstrometryTransformLinear const &right) const {
1478  if (right.getOrder() == 1) {
1479  return std::make_unique<TanPixelToRaDec>((*this) * (right));
1480  } else {
1481  return std::unique_ptr<AstrometryTransform>(nullptr);
1482  }
1483 }
1484 
1486  TanPixelToRaDec result(*this);
1487  result.linPixelToTan = result.linPixelToTan * right;
1488  return result;
1489 }
1490 
1492  if (corr != nullptr) {
1493  LOGL_WARN(_log, "You are inverting a TanPixelToRaDec with corrections.");
1494  LOGL_WARN(_log, "The inverse you get ignores the corrections!");
1495  }
1497 }
1498 
1502 }
1503 
1505  const Frame &region) const {
1506  if (!corr)
1509  else
1510  return std::unique_ptr<AstrometryTransform>(new AstrometryTransformInverse(this, precision, region));
1511 }
1512 
1514  if (corr)
1515  return (*corr) * linPixelToTan;
1516  else
1517  return linPixelToTan;
1518 }
1519 
1520 void TanPixelToRaDec::pixToTangentPlane(double xPixel, double yPixel, double &xTangentPlane,
1521  double &yTangentPlane) const {
1522  // xTangentPlane, yTangentPlane in degrees.
1523  linPixelToTan.apply(xPixel, yPixel, xTangentPlane, yTangentPlane);
1524  if (corr) {
1525  double xtmp = xTangentPlane;
1526  double ytmp = yTangentPlane;
1527  corr->apply(xtmp, ytmp, xTangentPlane, yTangentPlane); // still in degrees.
1528  }
1529 }
1530 
1533  new TanPixelToRaDec(getLinPart(), getTangentPoint(), corr.get()));
1534 }
1535 
1536 void TanPixelToRaDec::dump(ostream &stream) const {
1537  stream << " TanPixelToRaDec, lin part :" << endl << linPixelToTan;
1538  Point tp = getTangentPoint();
1539  stream << " tangent point " << tp.x << ' ' << tp.y << endl;
1540  Point crpix = getCrPix();
1541  stream << " crpix " << crpix.x << ' ' << crpix.y << endl;
1542  if (corr) stream << "PV correction: " << endl << *corr;
1543 }
1544 
1546  /* OK we could implement this routine, but it is
1547  probably useless since to do the match, we have to
1548  project from sky to tangent plane. When a match is
1549  found, it is easier to carry out the fit in the
1550  tangent plane, rather than going back to the celestial
1551  sphere (and reproject to fit...). Anyway if this
1552  message shows up, we'll think about it.
1553  */
1555  "TanPixelToRaDec::fit is NOT implemented (although it is doable)) ");
1556  return -1;
1557 }
1558 
1559 /*************************** TanSipPixelToRaDec ***************/
1560 
1562  const AstrometryTransformPolynomial *corrections)
1563  : BaseTanWcs(pixToTan, tangentPoint, corrections) {}
1564 
1565 // ": AstrometryTransform" suppresses a warning
1567 
1568 /* Would require some checks before cooking up something more efficient
1569  than just a linear approximation */
1570 #if 0
1572 {
1573  if (&region) {}
1575 }
1576 #endif
1577 
1579  const Frame &region)
1580  const { /* We have not implemented (yet) the reverse corrections available in SIP */
1581  return std::unique_ptr<AstrometryTransform>(new AstrometryTransformInverse(this, precision, region));
1582 }
1583 
1585  if (corr)
1586  return AstrometryTransformPolynomial(linPixelToTan) * (*corr);
1587  else
1588  return linPixelToTan;
1589 }
1590 
1591 void TanSipPixelToRaDec::pixToTangentPlane(double xPixel, double yPixel, double &xTangentPlane,
1592  double &yTangentPlane) const {
1593  // xTangentPlane, yTangentPlane returned in degrees
1594  if (corr) {
1595  double xtmp, ytmp;
1596  corr->apply(xPixel, yPixel, xtmp, ytmp);
1597  linPixelToTan.apply(xtmp, ytmp, xTangentPlane, yTangentPlane);
1598  } else
1599  linPixelToTan.apply(xPixel, yPixel, xTangentPlane, yTangentPlane);
1600 }
1601 
1605 }
1606 
1607 void TanSipPixelToRaDec::dump(ostream &stream) const {
1608  stream << " TanSipPixelToRaDec, lin part :" << endl << linPixelToTan;
1609  Point tp = getTangentPoint();
1610  stream << " tangent point " << tp.x << ' ' << tp.y << endl;
1611  Point crpix = getCrPix();
1612  stream << " crpix " << crpix.x << ' ' << crpix.y << endl;
1613  if (corr) stream << "PV correction: " << endl << *corr;
1614 }
1615 
1617  /* OK we could implement this routine, but it is
1618  probably useless since to do the match, we have to
1619  project from sky to tangent plane. When a match is
1620  found, it is easier to carry out the fit in the
1621  tangent plane, rather than going back to the celestial
1622  sphere (and reproject to fit...). Anyway if this
1623  message shows up, we'll think about it.
1624  */
1626  "TanSipPixelToRaDec::fit is NOT implemented (although it is doable)) ");
1627  return -1;
1628 }
1629 
1630 /*************** reverse transform of TanPixelToRaDec: TanRaDecToPixel ********/
1631 
1633  : linTan2Pix(tan2Pix) {
1634  setTangentPoint(tangentPoint);
1635 }
1636 
1637 void TanRaDecToPixel::setTangentPoint(Point const &tangentPoint) {
1638  /* the radian to degrees conversion after projection
1639  is handled in apply */
1640  ra0 = deg2rad(tangentPoint.x);
1641  dec0 = deg2rad(tangentPoint.y);
1642  cos0 = std::cos(dec0);
1643  sin0 = std::sin(dec0);
1644 }
1645 
1647  ra0 = dec0 = 0;
1648  cos0 = 1;
1649  sin0 = 0;
1650 }
1651 
1652 Point TanRaDecToPixel::getTangentPoint() const { return Point(rad2deg(ra0), rad2deg(dec0)); }
1653 
1655 
1656 // Use analytic derivatives, computed at the same time as the transform itself
1658  /* this routine is very similar to apply, but also propagates errors.
1659  The deg2rad and rad2deg are ignored for errors because they act as
1660  2 global scalings that cancel each other.
1661  Derivatives were computed using maple:
1662 
1663  l1 := sin(a - a0)*cos(d);
1664  m1 := sin(d)*sin(d0)+cos(d)*cos(d0)*cos(a-a0);
1665  l2 := sin(d)*cos(d0)-cos(d)*sin(d0)*cos(a-a0);
1666  simplify(diff(l1/m1,a));
1667  simplify(diff(l1/m1,d));
1668  simplify(diff(l2/m1,a));
1669  simplify(diff(l2/m1,d));
1670 
1671  Checked against AstrometryTransform::transformPosAndErrors (dec 09)
1672  */
1673  double ra = deg2rad(in.x);
1674  double dec = deg2rad(in.y);
1675  if (ra - ra0 > M_PI) ra -= (2. * M_PI);
1676  if (ra - ra0 < -M_PI) ra += (2. * M_PI);
1677  // Code inspired from worldpos.c in wcssubs (ancestor of the wcslib)
1678  // The same code is copied in ::apply()
1679 
1680  double coss = std::cos(dec);
1681  double sins = std::sin(dec);
1682  double sinda = std::sin(ra - ra0);
1683  double cosda = std::cos(ra - ra0);
1684  double l = sinda * coss;
1685  double m = sins * sin0 + coss * cos0 * cosda;
1686  l = l / m;
1687  m = (sins * cos0 - coss * sin0 * cosda) / m;
1688 
1689  // derivatives
1690  double deno =
1691  sq(sin0) - sq(coss) + sq(coss * cos0) * (1 + sq(cosda)) + 2 * sins * sin0 * coss * cos0 * cosda;
1692  double a11 = coss * (cosda * sins * sin0 + coss * cos0) / deno;
1693  double a12 = -sinda * sin0 / deno;
1694  double a21 = coss * sinda * sins / deno;
1695  double a22 = cosda / deno;
1696 
1697  FatPoint tmp;
1698  tmp.vx = a11 * (a11 * in.vx + 2 * a12 * in.vxy) + a12 * a12 * in.vy;
1699  tmp.vy = a21 * a21 * in.vx + a22 * a22 * in.vy + 2. * a21 * a22 * in.vxy;
1700  tmp.vxy = a21 * a11 * in.vx + a22 * a12 * in.vy + (a21 * a12 + a11 * a22) * in.vxy;
1701 
1702  // l and m are now coordinates in the tangent plane, in radians.
1703  tmp.x = rad2deg(l);
1704  tmp.y = rad2deg(m);
1705 
1706  linTan2Pix.transformPosAndErrors(tmp, out);
1707 }
1708 
1709 void TanRaDecToPixel::apply(const double xIn, const double yIn, double &xOut, double &yOut) const {
1710  double ra = deg2rad(xIn);
1711  double dec = deg2rad(yIn);
1712  if (ra - ra0 > M_PI) ra -= (2. * M_PI);
1713  if (ra - ra0 < -M_PI) ra += (2. * M_PI);
1714  // Code inspired from worldpos.c in wcssubs (ancestor of the wcslib)
1715  // The same code is copied in ::transformPosAndErrors()
1716  double coss = std::cos(dec);
1717  double sins = std::sin(dec);
1718  double l = std::sin(ra - ra0) * coss;
1719  double m = sins * sin0 + coss * cos0 * std::cos(ra - ra0);
1720  l = l / m;
1721  m = (sins * cos0 - coss * sin0 * std::cos(ra - ra0)) / m;
1722  // l and m are now coordinates in the tangent plane, in radians.
1723  l = rad2deg(l);
1724  m = rad2deg(m);
1725  linTan2Pix.apply(l, m, xOut, yOut);
1726 }
1727 
1730 }
1731 
1732 void TanRaDecToPixel::dump(ostream &stream) const {
1733  Point tp = getTangentPoint();
1734  stream << " tan2pix " << linTan2Pix << " tangent point " << tp.x << ' ' << tp.y << endl;
1735 }
1736 
1740 }
1741 
1745 }
1746 
1749 }
1750 
1753  "TanRaDecToPixel::fit is NOT implemented (although it is doable)) ");
1754  return -1;
1755 }
1756 
1757 /************* a "run-time" transform, that does not require to modify this file */
1758 
1760  : _userFun(userFun), _userData(userData) {}
1761 
1762 void UserTransform::apply(const double xIn, const double yIn, double &xOut, double &yOut) const {
1763  _userFun(xIn, yIn, xOut, yOut, _userData);
1764 }
1765 
1766 void UserTransform::dump(ostream &stream) const {
1767  stream << "UserTransform with user function @ " << _userFun << "and userData@ " << _userData << endl;
1768 }
1769 
1772  "UserTransform::fit is NOT implemented (and will never be)) ");
1773  return -1;
1774 }
1775 
1778 }
1779 
1780 /*************************************************************/
1781 
1783  ifstream s(fileName.c_str());
1784  if (!s)
1786  " astrometryTransformRead : cannot open " + fileName);
1787  try {
1789  s.close();
1790  return res;
1793  std::string(e.what()) + " in file " + fileName);
1794  }
1795 }
1796 
1798  std::string type;
1799  s >> type;
1800  if (s.fail())
1802  "astrometryTransformRead : could not find a AstrometryTransformtype");
1803  if (type == "AstrometryTransformIdentity") {
1805  res->read(s);
1806  return std::move(res);
1807  } else if (type == "AstrometryTransformPolynomial") {
1809  res->read(s);
1810  return std::move(res);
1811  } else
1813  " astrometryTransformRead : No reader for AstrometryTransform type " + type);
1814 }
1815 } // namespace jointcal
1816 } // namespace lsst
#define LOGLS_WARN(logger, message)
std::unique_ptr< AstrometryTransform > inverseTransform(const double precision, const Frame &region) const
Inverse transform: returns a TanRaDecToPixel if there are no corrections, or the iterative solver if ...
AstrometryTransformPolynomial operator+(AstrometryTransformPolynomial const &right) const
Addition.
std::unique_ptr< AstrometryTransform > inverseTransform(const double precision, const Frame &region) const
returns an inverse transform. Numerical if not overloaded.
std::unique_ptr< AstrometryTransform > inverseTransform(const double precision, const Frame &region) const
Inverse transform: returns a TanRaDecToPixel if there are no corrections, or the iterative solver if ...
void() AstrometryTransformFun(const double, const double, double &, double &, const void *)
signature of the user-provided routine that actually does the coordinate transform for UserTransform...
the transformation that handles pix to sideral transfos (Gnomonic, possibly with polynomial distortio...
T atan(T... args)
double paramRef(const int i) const override
void computeDerivative(Point const &where, AstrometryTransformLinear &derivative, const double step=0.01) const override
Computes the local Derivative of a transform, w.r.t.
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const
implements an iterative (Gauss-Newton) solver.
double dec
void paramDerivatives(Point const &where, double *dx, double *dy) const override
Derivative w.r.t parameters.
void computeDerivative(Point const &where, AstrometryTransformLinear &derivative, const double step=0.01) const override
specialised analytic routine
void setOrder(const unsigned order)
Sets the polynomial order (the highest sum of exponents of the largest monomial). ...
PolyXY(AstrometryTransformPolynomial const &transform, const unsigned whichCoord)
A hanger for star associations.
Definition: StarMatch.h:54
implements the linear transformations (6 real coefficients).
A point in a plane.
Definition: Point.h:36
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const
return second(first(xIn,yIn))
double getArea() const
Definition: Frame.cc:118
AstrometryTransformLinear linPixelToTan
double coeff(const unsigned powX, const unsigned powY, const unsigned whichCoord) const
access to coefficients (read only)
virtual char const * what(void) const noexcept
long double & coeff(const unsigned powX, const unsigned powY)
std::unique_ptr< AstrometryTransform > composeAndReduce(AstrometryTransformLinear const &right) const
Return a reduced composition of newTransform = this(right()), or nullptr if it cannot be reduced...
std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
bool isIntegerShift(const AstrometryTransform *transform)
Shorthand test to tell if a transform is a simple integer shift.
std::ostream & operator<<(std::ostream &stream, AstrometryTransform const &transform)
Delegates to transform.dump()
double fit(StarMatchList const &starMatchList)
fits a transform to a std::list of Point pairs (p1,p2, the Point fields in StarMatch).
table::PointKey< double > crpix
T fail(T... args)
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const override
AstrometryTransformPolynomial getPixelToTangentPlane() const
the transformation from pixels to tangent plane (degrees)
FatPoint point2
2 points
Definition: StarMatch.h:60
double x
double fit(StarMatchList const &starMatchList)
Not implemented yet, because we do it otherwise.
virtual void pixToTangentPlane(double xPixel, double yPixel, double &xTangentPlane, double &yTangentPlane) const =0
Transform from pixels to tangent plane (degrees)
T endl(T... args)
T left(T... args)
double fit(StarMatchList const &starMatchList)
fits a transform to a std::list of Point pairs (p1,p2, the Point fields in StarMatch).
std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const
Transform pixels to ICRS RA, Dec in degrees.
STL namespace.
T precision(T... args)
double xMin
coordinate of boundary.
Definition: Frame.h:41
TanPixelToRaDec operator*(AstrometryTransformLinear const &right) const
composition with AstrometryTransformLinear
Extent< double, N > & operator+=(Extent< double, N > &lhs, Extent< int, N > const &rhs) noexcept
T end(T... args)
#define LOGL_ERROR(logger, message...)
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const
T floor(T... args)
double coeffOrZero(const unsigned powX, const unsigned powY, const unsigned whichCoord) const
read access, zero if beyond order
void dump(std::ostream &stream=std::cout) const override
print out of coefficients in a readable form.
AstrometryTransformInverse(const AstrometryTransform *direct, const double precision, const Frame &region)
STL class.
A Point with uncertainties.
Definition: FatPoint.h:34
#define M_PI
Definition: ListMatch.cc:31
void write(std::ostream &s) const override
void setCorrections(std::unique_ptr< AstrometryTransformPolynomial > corrections)
Assign the correction polynomial (what it means is left to derived classes)
std::unique_ptr< AstrometryTransform > roughInverse(const Frame &region) const
Overload the "generic routine" (available for all AstrometryTransform types.
std::unique_ptr< AstrometryTransformPolynomial > corr
T resize(T... args)
int const step
T atan2(T... args)
STL class.
UserTransform(AstrometryTransformFun &fun, const void *userData)
the transform routine and extra data that it may need.
double x
coordinate
Definition: Point.h:41
T min(T... args)
pairs of points
T sin(T... args)
void computeDerivative(Point const &where, AstrometryTransformLinear &derivative, const double step=0.01) const
specialised analytic routine
T at(T... args)
AstrometryTransformLinear getLinPart() const
The Linear part (corresponding to CD&#39;s and CRPIX&#39;s)
double getHeight() const
size along y axis
Definition: Frame.h:57
TanPixelToRaDec inverted() const
exact typed inverse:
T push_back(T... args)
void setTangentPoint(Point const &tangentPoint)
Resets the projection (or tangent) point.
STL class.
std::unique_ptr< AstrometryTransform > roughInverse(const Frame &region) const
Overload the "generic routine" (available for all AstrometryTransform types.
AstrometryTransformLinear getLinPart() const
The Linear part (corresponding to CD&#39;s and CRPIX&#39;s)
AstrometryTransformPolynomial operator*(AstrometryTransformPolynomial const &right) const
Composition (internal stuff in quadruple precision)
first
std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
#define LOGL_FATAL(logger, message...)
virtual void pixToTangentPlane(double xPixel, double yPixel, double &xTangentPlane, double &yTangentPlane) const
transforms from pixel space to tangent plane (degrees)
rectangle with sides parallel to axes.
Definition: Frame.h:38
AstrometryTransformLinear normalizeCoordinatesTransform(const Frame &frame)
Returns the transformation that maps the input frame along both axes to [-1,1].
void dump(std::ostream &stream) const
dumps the transform coefficients to stream.
Class for a simple mapping implementing a generic AstrometryTransform.
AstrometryTransformComposition(AstrometryTransform const &second, AstrometryTransform const &first)
will pipe transfos
#define LOGLS_TRACE(logger, message)
AstrometryTransformPolynomial getPixelToTangentPlane() const
the transformation from pixels to tangent plane (degrees)
AstrometryTransformPolynomial(const unsigned order=1)
Default transform : identity for all orders (>=1 ).
std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
Point getCrPix() const
Get the pixel origin of the WCS (CRPIX in FITS WCS terminology, but zero-based)
void dump(std::ostream &stream) const
dumps the transform coefficients to stream.
virtual std::unique_ptr< AstrometryTransform > composeAndReduce(AstrometryTransform const &right) const
Return a reduced composition of newTransform = this(right()), or nullptr if it cannot be reduced...
std::unique_ptr< AstrometryTransform > clone() const override
returns a copy (allocated by new) of the transformation.
AstrometryTransformLinear()
the default constructor constructs the do-nothing transformation.
int max
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const override
std::unique_ptr< AstrometryTransform > compose(AstrometryTransform const &left, AstrometryTransform const &right)
Returns a pointer to a composition of transforms, representing left(right()).
T str(T... args)
This one is the Tangent Plane (called gnomonic) projection (from celestial sphere to tangent plane) ...
just here to provide a specialized constructor, and fit.
T reset(T... args)
T cos(T... args)
T dynamic_pointer_cast(T... args)
void apply(const double xIn, const double yIn, double &xOut, double &yOut) const
#define LOGL_WARN(logger, message...)
table::Key< int > type
T infinity(T... args)
T fabs(T... args)
T max(T... args)
T move(T... args)
std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
double fit(StarMatchList const &starMatchList)
Not implemented yet, because we do it otherwise.
void dump(ostream &stream) const
dumps the transform coefficients to stream.
std::unique_ptr< SchemaItem< U > > result
double getWidth() const
size along x axis
Definition: Frame.h:54
T count(T... args)
std::unique_ptr< AstrometryTransform > inverseTransform(const double precision, const Frame &region) const
Inverse transform: returns a TanPixelToRaDec.
void dump(std::ostream &stream=std::cout) const
dumps the transform coefficients to stream.
BaseTanWcs(AstrometryTransformLinear const &pixToTan, Point const &tangentPoint, const AstrometryTransformPolynomial *corrections=nullptr)
void transformPosAndErrors(const FatPoint &in, FatPoint &out) const
transform with analytical derivatives
A do-nothing transformation. It anyway has dummy routines to mimick a AstrometryTransform.
std::unique_ptr< AstrometryTransform > astrometryTransformRead(const std::string &fileName)
The virtual constructor from a file.
T insert(T... args)
std::shared_ptr< AstrometryTransformPolynomial > inversePolyTransform(AstrometryTransform const &forward, Frame const &domain, double const precision, int const maxOrder=9, unsigned const nSteps=50)
Approximate the inverse by a polynomial, to some precision.
Point getCenter() const
Center of the frame.
Definition: Frame.h:60
second
T size(T... args)
double fit(StarMatchList const &starMatchList)
guess what
#define LSST_EXCEPT(type,...)
STL class.
AstrometryTransformLinear inverted() const
returns the inverse: T1 = T2.inverted();
int min
void dump(std::ostream &stream) const
dumps the transform coefficients to stream.
double fit(StarMatchList const &starMatchList)
guess what
a virtual (interface) class for geometric transformations.
T begin(T... args)
std::unique_ptr< AstrometryTransform > inverseTransform(double, const Frame &) const
Inverse transform: returns the direct one!
T pow(T... args)
double fit(StarMatchList const &starMatchList)
fits a transform to a std::list of Point pairs (p1,p2, the Point fields in StarMatch).
T c_str(T... args)
virtual void pixToTangentPlane(double xPixel, double yPixel, double &xTangentPlane, double &yTangentPlane) const
transforms from pixel space to tangent plane (degrees)
AstrometryTransformLinear operator*(AstrometryTransformLinear const &right) const
enables to combine linear tranformations: T1=T2*T3 is legal.
virtual std::unique_ptr< AstrometryTransform > clone() const
returns a copy (allocated by new) of the transformation.
virtual void transformPosAndErrors(const FatPoint &in, FatPoint &out) const override
a mix of apply and Derivative
std::shared_ptr< afw::geom::SkyWcs > getSkyWcs() const
Point getTangentPoint() const
Get the sky origin (CRVAL in FITS WCS terminology) in degrees.
AstrometryTransformPolynomial operator-(AstrometryTransformPolynomial const &right) const
Subtraction.
void write(std::ostream &s) const override
AstrometryTransformSkyWcs(std::shared_ptr< afw::geom::SkyWcs > skyWcs)
std::unique_ptr< AstrometryTransform > composeAndReduce(AstrometryTransformPolynomial const &right) const
Return a reduced composition of newTransform = this(right()), or nullptr if it cannot be reduced...
std::unique_ptr< AstrometryTransform > roughInverse(const Frame &) const
Overload the "generic routine".
table::Key< int > a
T transform(T... args)
T sqrt(T... args)
m
double fit(StarMatchList const &starMatchList) override
guess what
Private class to handle AstrometryTransform compositions (i.e.
#define LOGLS_FATAL(logger, message)
TanRaDecToPixel inverted() const
approximate inverse : it ignores corrections;
double fit(const StarMatchList &starMatchList) override
Not implemented; throws pex::exceptions::LogicError.
std::shared_ptr< ast::Mapping > toAstMap(jointcal::Frame const &domain) const override
Create an equivalent AST mapping for this transformation, including an analytic inverse if possible...
double fit(StarMatchList const &starMatchList)
fits a transform to a std::list of Point pairs (p1,p2, the Point fields in StarMatch).
std::shared_ptr< ast::Mapping > toAstMap(jointcal::Frame const &domain) const override
Create an equivalent AST mapping for this transformation, including an analytic inverse if possible...
long double coeff(const unsigned powX, const unsigned powY) const
#define LOG_GET(logger)
T setprecision(T... args)
AstrometryTransformLinear linearApproximation(Point const &where, const double step=0.01) const
linear (local) approximation.
STL class.
unsigned getOrder() const
Returns the polynomial order.
virtual AstrometryTransformLinear linearApproximation(Point const &where, const double step=0.01) const override
linear approximation.
int y
void dump(std::ostream &stream=std::cout) const override
dumps the transform coefficients to stream.
virtual std::unique_ptr< AstrometryTransform > clone() const =0
returns a copy (allocated by new) of the transformation.
virtual void dump(std::ostream &stream=std::cout) const =0
dumps the transform coefficients to stream.
clone
just here to provide specialized constructors. AstrometryTransformLinear fit routine.
STL class.
void operator=(const BaseTanWcs &original)
virtual void apply(const double xIn, const double yIn, double &xOut, double &yOut) const =0
T reserve(T... args)
void dump(ostream &stream=cout) const
dumps the transform coefficients to stream.
Point getTangentPoint() const
tangent point coordinates (degrees)
T emplace_back(T... args)