6 #include "Eigen/Sparse"
8 #include "lsst/log/Log.h"
9 #include "lsst/pex/exceptions.h"
19 static double sqr(
double x) {
return x * x; }
22 LOG_LOGGER _log = LOG_GET(
"jointcal.AstrometryFit");
29 std::shared_ptr<AstrometryModel> astrometryModel,
double posError)
31 _astrometryModel(astrometryModel),
32 _refractionCoefficient(0),
35 _nParRefrac(_associations->getNFilters()),
45 _referenceColor += i->color;
46 _sigCol += sqr(i->color);
50 _referenceColor /= double(count);
51 if (_sigCol > 0) _sigCol = sqrt(_sigCol / count - sqr(_referenceColor));
53 LOGLS_INFO(_log,
"Reference Color: " << _referenceColor <<
" sig " << _sigCol);
61 Point AstrometryFit::transformFittedStar(FittedStar
const &fittedStar, Gtransfo
const *sky2TP,
62 Point
const &refractionVector,
double refractionCoeff,
64 Point fittedStarInTP = sky2TP->apply(fittedStar);
65 if (fittedStar.mightMove) {
66 fittedStarInTP.x += fittedStar.pmx * mjd;
67 fittedStarInTP.y += fittedStar.pmy * mjd;
72 double color = fittedStar.color - _referenceColor;
73 fittedStarInTP.x += refractionVector.x * color * refractionCoeff;
74 fittedStarInTP.y += refractionVector.y * color * refractionCoeff;
75 return fittedStarInTP;
81 static void tweakAstromMeasurementErrors(FatPoint &P, MeasuredStar
const &Ms,
double error) {
82 static bool called =
false;
83 static double increment = 0;
85 increment = sqr(error);
94 void AstrometryFit::leastSquareDerivativesMeasurement(CcdImage
const &ccdImage, TripletList &tripletList,
95 Eigen::VectorXd &fullGrad,
96 MeasuredStarList
const *msList)
const {
106 if (msList) assert(&(msList->front()->getCcdImage()) == &ccdImage);
109 const Mapping *mapping = _astrometryModel->getMapping(ccdImage);
111 unsigned npar_mapping = (_fittingDistortions) ? mapping->getNpar() : 0;
112 unsigned npar_pos = (_fittingPos) ? 2 : 0;
113 unsigned npar_refrac = (_fittingRefrac) ? 1 : 0;
114 unsigned npar_pm = (_fittingPM) ?
NPAR_PM : 0;
115 unsigned npar_tot = npar_mapping + npar_pos + npar_refrac + npar_pm;
118 if (npar_tot == 0)
return;
119 std::vector<unsigned> indices(npar_tot, -1);
120 if (_fittingDistortions) mapping->setMappingIndices(indices);
123 double mjd = ccdImage.getMjd() - _JDRef;
125 Point refractionVector = ccdImage.getRefractionVector();
127 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(ccdImage);
133 Eigen::Matrix2d transW(2, 2);
134 Eigen::Matrix2d alpha(2, 2);
135 Eigen::VectorXd grad(npar_tot);
137 unsigned kTriplets = tripletList.getNextFreeIndex();
138 const MeasuredStarList &catalog = (msList) ? *msList : ccdImage.getCatalogForFit();
140 for (
auto &i : catalog) {
141 const MeasuredStar &ms = *i;
142 if (!ms.isValid())
continue;
145 tweakAstromMeasurementErrors(inPos, ms, _posError);
149 if (_fittingDistortions)
150 mapping->computeTransformAndDerivatives(inPos, outPos, H);
152 mapping->transformPosAndErrors(inPos, outPos);
154 unsigned ipar = npar_mapping;
155 double det = outPos.vx * outPos.vy - sqr(outPos.vxy);
156 if (det <= 0 || outPos.vx <= 0 || outPos.vy <= 0) {
157 LOGLS_WARN(_log,
"Inconsistent measurement errors: drop measurement at "
158 << Point(ms) <<
" in image " << ccdImage.getName());
161 transW(0, 0) = outPos.vy / det;
162 transW(1, 1) = outPos.vx / det;
163 transW(0, 1) = transW(1, 0) = -outPos.vxy / det;
166 alpha(0, 0) = sqrt(transW(0, 0));
168 alpha(1, 0) = transW(0, 1) / alpha(0, 0);
171 alpha(1, 1) = 1. / sqrt(det * transW(0, 0));
174 auto fs = ms.getFittedStar();
176 Point fittedStarInTP =
177 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
182 sky2TP->computeDerivative(*fs, dypdy, 1e-3);
185 H(npar_mapping, 0) = -dypdy.A11();
186 H(npar_mapping + 1, 0) = -dypdy.A12();
187 H(npar_mapping, 1) = -dypdy.A21();
188 H(npar_mapping + 1, 1) = -dypdy.A22();
189 indices[npar_mapping] = fs->getIndexInMatrix();
190 indices.at(npar_mapping + 1) = fs->getIndexInMatrix() + 1;
195 if (_fittingPM && fs->mightMove) {
197 H(ipar + 1, 1) = -mjd;
198 indices[ipar] = fs->getIndexInMatrix() + 2;
199 indices[ipar + 1] = fs->getIndexInMatrix() + 3;
202 if (_fittingRefrac) {
205 double color = fs->color - _referenceColor;
207 H(ipar, 0) = -refractionVector.x * color;
208 H(ipar, 1) = -refractionVector.y * color;
209 indices[ipar] = _refracPosInMatrix;
214 Eigen::Vector2d res(fittedStarInTP.x - outPos.x, fittedStarInTP.y - outPos.y);
222 for (
unsigned ipar = 0; ipar < npar_tot; ++ipar) {
223 for (
unsigned ic = 0; ic < 2; ++ic) {
224 double val = halpha(ipar, ic);
225 if (val == 0)
continue;
226 tripletList.addTriplet(indices[ipar], kTriplets + ic, val);
228 fullGrad(indices[ipar]) += grad(ipar);
232 tripletList.setNextFreeIndex(kTriplets);
235 void AstrometryFit::leastSquareDerivativesReference(FittedStarList
const &fittedStarList,
236 TripletList &tripletList,
237 Eigen::VectorXd &fullGrad)
const {
246 if (!_fittingPos)
return;
250 Eigen::Matrix2d W(2, 2);
251 Eigen::Matrix2d alpha(2, 2);
252 Eigen::Matrix2d H(2, 2), halpha(2, 2), HW(2, 2);
254 Eigen::Vector2d res, grad;
256 unsigned kTriplets = tripletList.getNextFreeIndex();
263 TanRaDec2Pix proj(GtransfoLin(), Point(0., 0.));
264 for (
auto const &i : fittedStarList) {
265 const FittedStar &fs = *i;
266 const RefStar *rs = fs.getRefStar();
267 if (rs ==
nullptr)
continue;
268 proj.setTangentPoint(fs);
271 proj.transformPosAndErrors(*rs, rsProj);
273 proj.computeDerivative(fs, der, 1e-4);
275 H(0, 0) = -der.A11();
276 H(1, 0) = -der.A12();
277 H(0, 1) = -der.A21();
278 H(1, 1) = -der.A22();
280 double det = rsProj.vx * rsProj.vy - sqr(rsProj.vxy);
281 if (rsProj.vx <= 0 || rsProj.vy <= 0 || det <= 0) {
282 LOGLS_WARN(_log,
"RefStar error matrix not positive definite for: " << *rs);
285 W(0, 0) = rsProj.vy / det;
286 W(0, 1) = W(1, 0) = -rsProj.vxy / det;
287 W(1, 1) = rsProj.vx / det;
290 alpha(0, 0) = sqrt(W(0, 0));
292 alpha(1, 0) = W(0, 1) / alpha(0, 0);
293 alpha(1, 1) = 1. / sqrt(det * W(0, 0));
295 indices[0] = fs.getIndexInMatrix();
296 indices[1] = fs.getIndexInMatrix() + 1;
297 unsigned npar_tot = 2;
313 for (
unsigned ipar = 0; ipar < npar_tot; ++ipar) {
314 for (
unsigned ic = 0; ic < 2; ++ic) {
315 double val = halpha(ipar, ic);
316 if (val == 0)
continue;
317 tripletList.addTriplet(indices[ipar], kTriplets + ic, val);
319 fullGrad(indices[ipar]) += grad(ipar);
323 tripletList.setNextFreeIndex(kTriplets);
326 void AstrometryFit::accumulateStatImage(CcdImage
const &ccdImage, Chi2Accumulator &accum)
const {
333 const Mapping *mapping = _astrometryModel->getMapping(ccdImage);
335 double mjd = ccdImage.getMjd() - _JDRef;
337 Point refractionVector = ccdImage.getRefractionVector();
339 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(ccdImage);
341 Eigen::Matrix2Xd transW(2, 2);
343 auto &catalog = ccdImage.getCatalogForFit();
344 for (
auto const &ms : catalog) {
345 if (!ms->isValid())
continue;
347 FatPoint inPos = *ms;
348 tweakAstromMeasurementErrors(inPos, *ms, _posError);
352 mapping->transformPosAndErrors(inPos, outPos);
353 double det = outPos.vx * outPos.vy - sqr(outPos.vxy);
354 if (det <= 0 || outPos.vx <= 0 || outPos.vy <= 0) {
355 LOGLS_WARN(_log,
" Inconsistent measurement errors :drop measurement at "
356 << Point(*ms) <<
" in image " << ccdImage.getName());
359 transW(0, 0) = outPos.vy / det;
360 transW(1, 1) = outPos.vx / det;
361 transW(0, 1) = transW(1, 0) = -outPos.vxy / det;
363 auto fs = ms->getFittedStar();
364 Point fittedStarInTP =
365 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
367 Eigen::Vector2d res(fittedStarInTP.x - outPos.x, fittedStarInTP.y - outPos.y);
368 double chi2Val = res.transpose() * transW * res;
370 accum.addEntry(chi2Val, 2, ms);
374 void AstrometryFit::accumulateStatImageList(
CcdImageList const &ccdImageList, Chi2Accumulator &accum)
const {
375 for (
auto const &ccdImage : ccdImageList) {
376 accumulateStatImage(*ccdImage, accum);
380 void AstrometryFit::accumulateStatRefStars(Chi2Accumulator &accum)
const {
388 FittedStarList &fittedStarList =
_associations->fittedStarList;
389 TanRaDec2Pix proj(GtransfoLin(), Point(0., 0.));
390 for (
auto const &fs : fittedStarList) {
391 const RefStar *rs = fs->getRefStar();
392 if (rs ==
nullptr)
continue;
393 proj.setTangentPoint(*fs);
396 proj.transformPosAndErrors(*rs, rsProj);
398 double rx = rsProj.x;
399 double ry = rsProj.y;
400 double det = rsProj.vx * rsProj.vy - sqr(rsProj.vxy);
401 double wxx = rsProj.vy / det;
402 double wyy = rsProj.vx / det;
403 double wxy = -rsProj.vxy / det;
404 accum.addEntry(wxx * sqr(rx) + 2 * wxy * rx * ry + wyy * sqr(ry), 2, fs);
411 void AstrometryFit::getIndicesOfMeasuredStar(MeasuredStar
const &measuredStar,
412 std::vector<unsigned> &indices)
const {
413 if (_fittingDistortions) {
414 const Mapping *mapping = _astrometryModel->getMapping(measuredStar.getCcdImage());
415 mapping->setMappingIndices(indices);
417 auto fs = measuredStar.getFittedStar();
418 unsigned fsIndex = fs->getIndexInMatrix();
420 indices.push_back(fsIndex);
421 indices.push_back(fsIndex + 1);
425 for (
unsigned k = 0; k <
NPAR_PM; ++k) indices.push_back(fsIndex + 2 + k);
433 LOGLS_INFO(_log,
"assignIndices: Now fitting " << whatToFit);
434 _fittingDistortions = (
_whatToFit.find(
"Distortions") != std::string::npos);
435 _fittingPos = (
_whatToFit.find(
"Positions") != std::string::npos);
436 _fittingRefrac = (
_whatToFit.find(
"Refrac") != std::string::npos);
437 if (_sigCol == 0 && _fittingRefrac) {
439 "Cannot fit refraction coefficients without a color lever arm. Ignoring refraction.");
440 _fittingRefrac =
false;
442 _fittingPM = (
_whatToFit.find(
"PM") != std::string::npos);
445 _nParDistortions = 0;
446 if (_fittingDistortions) _nParDistortions = _astrometryModel->assignIndices(0,
_whatToFit);
447 unsigned ipar = _nParDistortions;
451 for (
auto &fittedStar : fittedStarList) {
456 fittedStar->setIndexInMatrix(ipar);
458 if ((_fittingPM)&fittedStar->mightMove) ipar +=
NPAR_PM;
461 _nParPositions = ipar - _nParDistortions;
462 if (_fittingRefrac) {
463 _refracPosInMatrix = ipar;
471 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
472 "AstrometryFit::offsetParams : the provided vector length is not compatible with "
473 "the current whatToFit setting");
474 if (_fittingDistortions) _astrometryModel->offsetParams(delta);
478 for (
auto const &i : fittedStarList) {
484 fs.
x += delta(index);
485 fs.
y += delta(index + 1);
487 fs.
pmx += delta(index + 2);
488 fs.
pmy += delta(index + 3);
492 if (_fittingRefrac) {
493 _refractionCoefficient += delta(_refracPosInMatrix);
499 static void write_sparse_matrix_in_fits(
SpMat const &mat, std::string
const &fitsName) {
500 if (mat.rows() * mat.cols() > 2e8) {
502 "write_sparse_matrix_in_fits: yout matrix is too large. " << fitsName <<
" not generated");
505 Mat m(mat.rows(), mat.cols());
506 for (
int k = 0; k < mat.outerSize(); ++k)
507 for (SpMat::InnerIterator it(mat, k); it; ++it) {
508 m(it.row(), it.col()) = it.value();
510 m.writeFits(fitsName);
513 static void write_vect_in_fits(Eigen::VectorXd
const &vectorXd, std::string
const &fitsName) {
514 Vect v(vectorXd.size());
515 for (
int k = 0; k < vectorXd.size(); ++k) v(k) = V(k);
516 Mat(v).writeFits(fitsName);
523 const char *what2fit[] = {
"Positions",
526 "Positions Distortions",
528 "Distortions Refrac",
529 "Positions Distortions Refrac"};
531 const char *what2fit[] = {
"Positions",
"Distortions",
"Positions Distortions"};
533 for (
unsigned k = 0; k <
sizeof(what2fit) /
sizeof(what2fit[0]); ++k) {
540 jacobian.setFromTriplets(tripletList.begin(), tripletList.end());
541 SpMat hessian = jacobian * jacobian.transpose();
544 sprintf(name,
"H%d.fits", k);
545 write_sparse_matrix_in_fits(hessian, name);
546 sprintf(name,
"g%d.fits", k);
547 write_vect_in_fits(grad, name);
549 LOGLS_DEBUG(_log,
"npar : " <<
_nParTot <<
' ' << _nParDistortions);
556 size_t dot = tupleName.rfind(
'.');
557 size_t slash = tupleName.rfind(
'/');
558 if (dot == std::string::npos || (slash != std::string::npos && dot < slash)) dot = tupleName.size();
559 std::string meas_tuple(tupleName);
560 meas_tuple.insert(dot,
"-meas");
562 std::string ref_tuple(tupleName);
563 ref_tuple.insert(dot,
"-ref");
568 std::ofstream tuple(tupleName.c_str());
569 tuple <<
"#xccd: coordinate in CCD" << std::endl
570 <<
"#yccd: " << std::endl
571 <<
"#rx: residual in degrees in TP" << std::endl
572 <<
"#ry:" << std::endl
573 <<
"#xtp: transformed coordinate in TP " << std::endl
574 <<
"#ytp:" << std::endl
575 <<
"#mag: rough mag" << std::endl
576 <<
"#jd: Julian date of the measurement" << std::endl
577 <<
"#rvx: transformed measurement uncertainty " << std::endl
578 <<
"#rvy:" << std::endl
579 <<
"#rvxy:" << std::endl
580 <<
"#color : " << std::endl
581 <<
"#fsindex: some unique index of the object" << std::endl
582 <<
"#ra: pos of fitted star" << std::endl
583 <<
"#dec: pos of fitted star" << std::endl
584 <<
"#chi2: contribution to Chi2 (2D dofs)" << std::endl
585 <<
"#nm: number of measurements of this FittedStar" << std::endl
586 <<
"#chip: chip number" << std::endl
587 <<
"#visit: visit id" << std::endl
588 <<
"#end" << std::endl;
590 for (
auto const &i : L) {
593 const Mapping *mapping = _astrometryModel->getMapping(im);
595 double mjd = im.
getMjd() - _JDRef;
596 for (
auto const &is : cat) {
601 tweakAstromMeasurementErrors(inPos, ms, _posError);
603 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(im);
606 Point fittedStarInTP =
607 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
608 Point res = tpPos - fittedStarInTP;
609 double det = tpPos.
vx * tpPos.
vy - sqr(tpPos.
vxy);
610 double wxx = tpPos.
vy / det;
611 double wyy = tpPos.
vx / det;
612 double wxy = -tpPos.
vxy / det;
614 double chi2 = wxx * res.
x * res.
x + wyy * res.
y * res.
y + 2 * wxy * res.
x * res.
y;
615 tuple << std::setprecision(9);
616 tuple << ms.
x <<
' ' << ms.
y <<
' ' << res.
x <<
' ' << res.
y <<
' ' << tpPos.
x <<
' ' << tpPos.
y
617 <<
' ' << fs->getMag() <<
' ' << mjd <<
' ' << tpPos.
vx <<
' ' << tpPos.
vy <<
' '
618 << tpPos.
vxy <<
' ' << fs->color <<
' ' << fs->getIndexInMatrix() <<
' ' << fs->x <<
' '
619 << fs->y <<
' ' << chi2 <<
' ' << fs->getMeasurementCount() <<
' ' << im.
getCcdId() <<
' '
626 std::ofstream tuple(tupleName.c_str());
627 tuple <<
"#ra: coordinates of FittedStar" << std::endl
628 <<
"#dec: " << std::endl
629 <<
"#rx: residual in degrees in TP" << std::endl
630 <<
"#ry:" << std::endl
631 <<
"#mag: mag" << std::endl
632 <<
"#rvx: transformed measurement uncertainty " << std::endl
633 <<
"#rvy:" << std::endl
634 <<
"#rvxy:" << std::endl
635 <<
"#color : " << std::endl
636 <<
"#fsindex: some unique index of the object" << std::endl
637 <<
"#chi2: contribution to Chi2 (2D dofs)" << std::endl
638 <<
"#nm: number of measurements of this FittedStar" << std::endl
639 <<
"#end" << std::endl;
643 for (
auto const &i : fittedStarList) {
646 if (rs ==
nullptr)
continue;
651 double rx = rsProj.
x;
652 double ry = rsProj.
y;
653 double det = rsProj.
vx * rsProj.
vy - sqr(rsProj.
vxy);
654 double wxx = rsProj.
vy / det;
655 double wyy = rsProj.
vx / det;
656 double wxy = -rsProj.
vxy / det;
657 double chi2 = wxx * sqr(rx) + 2 * wxy * rx * ry + wyy * sqr(ry);
658 tuple << std::setprecision(9);
659 tuple << fs.
x <<
' ' << fs.
y <<
' ' << rx <<
' ' << ry <<
' ' << fs.
getMag() <<
' ' << rsProj.
vx
Point getRefractionVector() const
Objects used as position anchors, typically USNO stars.
implements the linear transformations (6 real coefficients).
double getMjd() const
Julian Date.
virtual class needed in the abstraction of the distortion model
const RefStar * getRefStar() const
Get the astrometric reference star associated with this star.
std::shared_ptr< const FittedStar > getFittedStar() const
MeasuredStarList const & getCatalogForFit() const
Gets the catalog to be used for fitting, which may have been cleaned-up.
void assignIndices(std::string const &whatToFit) override
Set parameters to fit and assign indices in the big matrix.
AstrometryFit(std::shared_ptr< Associations > associations, std::shared_ptr< AstrometryModel > astrometryModel, double posError)
this is the only constructor
double getMag() const
derived using available zero points in input images. In the absence ofZP, ZP= 0.
void leastSquareDerivatives(TripletList &tripletList, Eigen::VectorXd &grad) const
Evaluates the chI^2 derivatives (Jacobian and gradient) for the current whatToFit setting...
A Point with uncertainties.
A list of MeasuredStar. They are usually filled in Associations::AddImage.
void makeRefResTuple(std::string const &tupleName) const
Produces a tuple containing residuals of reference terms.
int getIndexInMatrix() const
void makeMeasResTuple(std::string const &tupleName) const
Produces a tuple containing residuals of measurement terms.
Eigen::SparseMatrix< double > SpMat
A list of FittedStar s. Such a list is typically constructed by Associations.
objects measured on actual images.
void offsetParams(Eigen::VectorXd const &delta) override
Offset the parameters by the requested quantities.
std::shared_ptr< Associations > _associations
Eigen::Matrix< double, Eigen::Dynamic, 2 > MatrixX2d
void transformPosAndErrors(const FatPoint &in, FatPoint &out) const
transform with analytical derivatives
void setTangentPoint(const Point &tangentPoint)
Resets the projection (or tangent) point.
unsigned getNextFreeIndex() const
void saveResultTuples(std::string const &tupleName) const override
Save the full chi2 term per star that was used in the minimization, for debugging.
This one is the Tangent Plane (called gnomonic) projection (from celestial sphere to tangent plane) ...
a virtual (interface) class for geometric transformations.
int getMeasurementCount() const
std::list< std::shared_ptr< CcdImage > > CcdImageList
Handler of an actual image from a single CCD.
void checkStuff()
DEBUGGING routine.
bool isValid() const
Fits may use that to discard outliers.
The objects which have been measured several times.
virtual void transformPosAndErrors(FatPoint const &where, FatPoint &outPoint) const =0
The same as above but without the parameter derivatives (used to evaluate chi^2)
VisitIdType getVisit() const
returns visit ID
int getCcdId() const
returns ccd ID