6 #include "Eigen/Sparse" 8 #include "lsst/log/Log.h" 9 #include "lsst/pex/exceptions.h" 20 LOG_LOGGER _log = LOG_GET(
"jointcal.AstrometryFit");
27 std::shared_ptr<AstrometryModel> astrometryModel,
double posError)
29 _astrometryModel(astrometryModel),
30 _refractionCoefficient(0),
33 _nParRefrac(_associations->getNFilters()),
43 _referenceColor += i->color;
44 _sigCol += std::pow(i->color, 2);
48 _referenceColor /= double(count);
49 if (_sigCol > 0) _sigCol = sqrt(_sigCol / count - std::pow(_referenceColor, 2));
51 LOGLS_INFO(_log,
"Reference Color: " << _referenceColor <<
" sig " << _sigCol);
59 Point AstrometryFit::transformFittedStar(FittedStar
const &fittedStar, Gtransfo
const *sky2TP,
60 Point
const &refractionVector,
double refractionCoeff,
62 Point fittedStarInTP = sky2TP->apply(fittedStar);
63 if (fittedStar.mightMove) {
64 fittedStarInTP.x += fittedStar.pmx * mjd;
65 fittedStarInTP.y += fittedStar.pmy * mjd;
70 double color = fittedStar.color - _referenceColor;
71 fittedStarInTP.x += refractionVector.x * color * refractionCoeff;
72 fittedStarInTP.y += refractionVector.y * color * refractionCoeff;
73 return fittedStarInTP;
79 static void tweakAstromMeasurementErrors(FatPoint &P, MeasuredStar
const &Ms,
double error) {
80 static bool called =
false;
81 static double increment = 0;
83 increment = std::pow(error, 2);
92 void AstrometryFit::leastSquareDerivativesMeasurement(CcdImage
const &
ccdImage, TripletList &tripletList,
93 Eigen::VectorXd &fullGrad,
94 MeasuredStarList
const *msList)
const {
104 if (msList) assert(&(msList->front()->getCcdImage()) == &ccdImage);
107 const Mapping *mapping = _astrometryModel->getMapping(ccdImage);
109 unsigned npar_mapping = (_fittingDistortions) ? mapping->getNpar() : 0;
110 unsigned npar_pos = (_fittingPos) ? 2 : 0;
111 unsigned npar_refrac = (_fittingRefrac) ? 1 : 0;
112 unsigned npar_pm = (_fittingPM) ?
NPAR_PM : 0;
113 unsigned npar_tot = npar_mapping + npar_pos + npar_refrac + npar_pm;
116 if (npar_tot == 0)
return;
117 std::vector<unsigned> indices(npar_tot, -1);
118 if (_fittingDistortions) mapping->getMappingIndices(indices);
121 double mjd = ccdImage.getMjd() - _JDRef;
123 Point refractionVector = ccdImage.getRefractionVector();
125 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(ccdImage);
131 Eigen::Matrix2d transW(2, 2);
132 Eigen::Matrix2d alpha(2, 2);
133 Eigen::VectorXd grad(npar_tot);
135 unsigned kTriplets = tripletList.getNextFreeIndex();
136 const MeasuredStarList &catalog = (msList) ? *msList : ccdImage.getCatalogForFit();
138 for (
auto &i : catalog) {
139 const MeasuredStar &ms = *i;
140 if (!ms.isValid())
continue;
143 tweakAstromMeasurementErrors(inPos, ms, _posError);
147 if (_fittingDistortions)
148 mapping->computeTransformAndDerivatives(inPos, outPos, H);
150 mapping->transformPosAndErrors(inPos, outPos);
152 unsigned ipar = npar_mapping;
153 double det = outPos.vx * outPos.vy - std::pow(outPos.vxy, 2);
154 if (det <= 0 || outPos.vx <= 0 || outPos.vy <= 0) {
155 LOGLS_WARN(_log,
"Inconsistent measurement errors: drop measurement at " 156 << Point(ms) <<
" in image " << ccdImage.getName());
159 transW(0, 0) = outPos.vy / det;
160 transW(1, 1) = outPos.vx / det;
161 transW(0, 1) = transW(1, 0) = -outPos.vxy / det;
164 alpha(0, 0) = sqrt(transW(0, 0));
166 alpha(1, 0) = transW(0, 1) / alpha(0, 0);
169 alpha(1, 1) = 1. / sqrt(det * transW(0, 0));
172 auto fs = ms.getFittedStar();
174 Point fittedStarInTP =
175 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
180 sky2TP->computeDerivative(*fs, dypdy, 1e-3);
183 H(npar_mapping, 0) = -dypdy.A11();
184 H(npar_mapping + 1, 0) = -dypdy.A12();
185 H(npar_mapping, 1) = -dypdy.A21();
186 H(npar_mapping + 1, 1) = -dypdy.A22();
187 indices[npar_mapping] = fs->getIndexInMatrix();
188 indices.at(npar_mapping + 1) = fs->getIndexInMatrix() + 1;
193 if (_fittingPM && fs->mightMove) {
195 H(ipar + 1, 1) = -mjd;
196 indices[ipar] = fs->getIndexInMatrix() + 2;
197 indices[ipar + 1] = fs->getIndexInMatrix() + 3;
200 if (_fittingRefrac) {
203 double color = fs->color - _referenceColor;
205 H(ipar, 0) = -refractionVector.x * color;
206 H(ipar, 1) = -refractionVector.y * color;
207 indices[ipar] = _refracPosInMatrix;
212 Eigen::Vector2d res(fittedStarInTP.x - outPos.x, fittedStarInTP.y - outPos.y);
220 for (
unsigned ipar = 0; ipar < npar_tot; ++ipar) {
221 for (
unsigned ic = 0; ic < 2; ++ic) {
222 double val = halpha(ipar, ic);
223 if (val == 0)
continue;
224 tripletList.addTriplet(indices[ipar], kTriplets + ic, val);
226 fullGrad(indices[ipar]) += grad(ipar);
230 tripletList.setNextFreeIndex(kTriplets);
233 void AstrometryFit::leastSquareDerivativesReference(FittedStarList
const &fittedStarList,
234 TripletList &tripletList,
235 Eigen::VectorXd &fullGrad)
const {
244 if (!_fittingPos)
return;
248 Eigen::Matrix2d W(2, 2);
249 Eigen::Matrix2d alpha(2, 2);
250 Eigen::Matrix2d H(2, 2), halpha(2, 2), HW(2, 2);
252 Eigen::Vector2d res, grad;
254 unsigned kTriplets = tripletList.getNextFreeIndex();
261 TanRaDec2Pix proj(GtransfoLin(), Point(0., 0.));
262 for (
auto const &i : fittedStarList) {
263 const FittedStar &fs = *i;
264 const RefStar *rs = fs.getRefStar();
265 if (rs ==
nullptr)
continue;
266 proj.setTangentPoint(fs);
269 proj.transformPosAndErrors(*rs, rsProj);
271 proj.computeDerivative(fs, der, 1e-4);
273 H(0, 0) = -der.A11();
274 H(1, 0) = -der.A12();
275 H(0, 1) = -der.A21();
276 H(1, 1) = -der.A22();
278 double det = rsProj.vx * rsProj.vy - std::pow(rsProj.vxy, 2);
279 if (rsProj.vx <= 0 || rsProj.vy <= 0 || det <= 0) {
280 LOGLS_WARN(_log,
"RefStar error matrix not positive definite for: " << *rs);
283 W(0, 0) = rsProj.vy / det;
284 W(0, 1) = W(1, 0) = -rsProj.vxy / det;
285 W(1, 1) = rsProj.vx / det;
288 alpha(0, 0) = sqrt(W(0, 0));
290 alpha(1, 0) = W(0, 1) / alpha(0, 0);
291 alpha(1, 1) = 1. / sqrt(det * W(0, 0));
293 indices[0] = fs.getIndexInMatrix();
294 indices[1] = fs.getIndexInMatrix() + 1;
295 unsigned npar_tot = 2;
311 for (
unsigned ipar = 0; ipar < npar_tot; ++ipar) {
312 for (
unsigned ic = 0; ic < 2; ++ic) {
313 double val = halpha(ipar, ic);
314 if (val == 0)
continue;
315 tripletList.addTriplet(indices[ipar], kTriplets + ic, val);
317 fullGrad(indices[ipar]) += grad(ipar);
321 tripletList.setNextFreeIndex(kTriplets);
324 void AstrometryFit::accumulateStatImage(CcdImage
const &ccdImage, Chi2Accumulator &accum)
const {
331 const Mapping *mapping = _astrometryModel->getMapping(ccdImage);
333 double mjd = ccdImage.getMjd() - _JDRef;
335 Point refractionVector = ccdImage.getRefractionVector();
337 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(ccdImage);
339 Eigen::Matrix2Xd transW(2, 2);
341 auto &catalog = ccdImage.getCatalogForFit();
342 for (
auto const &ms : catalog) {
343 if (!ms->isValid())
continue;
345 FatPoint inPos = *ms;
346 tweakAstromMeasurementErrors(inPos, *ms, _posError);
350 mapping->transformPosAndErrors(inPos, outPos);
351 double det = outPos.vx * outPos.vy - std::pow(outPos.vxy, 2);
352 if (det <= 0 || outPos.vx <= 0 || outPos.vy <= 0) {
353 LOGLS_WARN(_log,
" Inconsistent measurement errors :drop measurement at " 354 << Point(*ms) <<
" in image " << ccdImage.getName());
357 transW(0, 0) = outPos.vy / det;
358 transW(1, 1) = outPos.vx / det;
359 transW(0, 1) = transW(1, 0) = -outPos.vxy / det;
361 auto fs = ms->getFittedStar();
362 Point fittedStarInTP =
363 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
365 Eigen::Vector2d res(fittedStarInTP.x - outPos.x, fittedStarInTP.y - outPos.y);
366 double chi2Val = res.transpose() * transW * res;
368 accum.addEntry(chi2Val, 2, ms);
372 void AstrometryFit::accumulateStatImageList(
CcdImageList const &ccdImageList, Chi2Accumulator &accum)
const {
373 for (
auto const &ccdImage : ccdImageList) {
374 accumulateStatImage(*ccdImage, accum);
378 void AstrometryFit::accumulateStatRefStars(Chi2Accumulator &accum)
const {
386 FittedStarList &fittedStarList =
_associations->fittedStarList;
387 TanRaDec2Pix proj(GtransfoLin(), Point(0., 0.));
388 for (
auto const &fs : fittedStarList) {
389 const RefStar *rs = fs->getRefStar();
390 if (rs ==
nullptr)
continue;
391 proj.setTangentPoint(*fs);
394 proj.transformPosAndErrors(*rs, rsProj);
396 double rx = rsProj.x;
397 double ry = rsProj.y;
398 double det = rsProj.vx * rsProj.vy - std::pow(rsProj.vxy, 2);
399 double wxx = rsProj.vy / det;
400 double wyy = rsProj.vx / det;
401 double wxy = -rsProj.vxy / det;
402 accum.addEntry(wxx * std::pow(rx, 2) + 2 * wxy * rx * ry + wyy * std::pow(ry, 2), 2, fs);
409 void AstrometryFit::getIndicesOfMeasuredStar(MeasuredStar
const &measuredStar,
410 std::vector<unsigned> &indices)
const {
411 if (_fittingDistortions) {
412 const Mapping *mapping = _astrometryModel->getMapping(measuredStar.getCcdImage());
413 mapping->getMappingIndices(indices);
415 auto fs = measuredStar.getFittedStar();
416 unsigned fsIndex = fs->getIndexInMatrix();
418 indices.push_back(fsIndex);
419 indices.push_back(fsIndex + 1);
423 for (
unsigned k = 0; k <
NPAR_PM; ++k) indices.push_back(fsIndex + 2 + k);
431 LOGLS_INFO(_log,
"assignIndices: Now fitting " << whatToFit);
432 _fittingDistortions = (
_whatToFit.find(
"Distortions") != std::string::npos);
433 _fittingPos = (
_whatToFit.find(
"Positions") != std::string::npos);
434 _fittingRefrac = (
_whatToFit.find(
"Refrac") != std::string::npos);
435 if (_sigCol == 0 && _fittingRefrac) {
437 "Cannot fit refraction coefficients without a color lever arm. Ignoring refraction.");
438 _fittingRefrac =
false;
440 _fittingPM = (
_whatToFit.find(
"PM") != std::string::npos);
443 _nParDistortions = 0;
444 if (_fittingDistortions) _nParDistortions = _astrometryModel->assignIndices(0,
_whatToFit);
445 unsigned ipar = _nParDistortions;
449 for (
auto &fittedStar : fittedStarList) {
454 fittedStar->setIndexInMatrix(ipar);
456 if ((_fittingPM)&fittedStar->mightMove) ipar +=
NPAR_PM;
459 _nParPositions = ipar - _nParDistortions;
460 if (_fittingRefrac) {
461 _refracPosInMatrix = ipar;
469 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
470 "AstrometryFit::offsetParams : the provided vector length is not compatible with " 471 "the current whatToFit setting");
472 if (_fittingDistortions) _astrometryModel->offsetParams(delta);
476 for (
auto const &i : fittedStarList) {
482 fs.
x += delta(index);
483 fs.
y += delta(index + 1);
485 fs.
pmx += delta(index + 2);
486 fs.
pmy += delta(index + 3);
490 if (_fittingRefrac) {
491 _refractionCoefficient += delta(_refracPosInMatrix);
497 static void write_sparse_matrix_in_fits(
SpMat const &mat, std::string
const &fitsName) {
498 if (mat.rows() * mat.cols() > 2e8) {
500 "write_sparse_matrix_in_fits: yout matrix is too large. " << fitsName <<
" not generated");
503 Mat m(mat.rows(), mat.cols());
504 for (
int k = 0; k < mat.outerSize(); ++k)
505 for (SpMat::InnerIterator it(mat, k); it; ++it) {
506 m(it.row(), it.col()) = it.value();
508 m.writeFits(fitsName);
511 static void write_vect_in_fits(Eigen::VectorXd
const &vectorXd, std::string
const &fitsName) {
512 Vect v(vectorXd.size());
513 for (
int k = 0; k < vectorXd.size(); ++k) v(k) = V(k);
514 Mat(v).writeFits(fitsName);
521 const char *what2fit[] = {
"Positions",
524 "Positions Distortions",
526 "Distortions Refrac",
527 "Positions Distortions Refrac"};
529 const char *what2fit[] = {
"Positions",
"Distortions",
"Positions Distortions"};
531 for (
unsigned k = 0; k <
sizeof(what2fit) /
sizeof(what2fit[0]); ++k) {
538 jacobian.setFromTriplets(tripletList.begin(), tripletList.end());
539 SpMat hessian = jacobian * jacobian.transpose();
542 sprintf(name,
"H%d.fits", k);
543 write_sparse_matrix_in_fits(hessian, name);
544 sprintf(name,
"g%d.fits", k);
545 write_vect_in_fits(grad, name);
547 LOGLS_DEBUG(_log,
"npar : " <<
_nParTot <<
' ' << _nParDistortions);
554 size_t dot = tupleName.rfind(
'.');
555 size_t slash = tupleName.rfind(
'/');
556 if (dot == std::string::npos || (slash != std::string::npos && dot < slash)) dot = tupleName.size();
557 std::string meas_tuple(tupleName);
558 meas_tuple.insert(dot,
"-meas");
560 std::string ref_tuple(tupleName);
561 ref_tuple.insert(dot,
"-ref");
566 std::ofstream tuple(tupleName.c_str());
567 tuple <<
"#xccd: coordinate in CCD" << std::endl
568 <<
"#yccd: " << std::endl
569 <<
"#rx: residual in degrees in TP" << std::endl
570 <<
"#ry:" << std::endl
571 <<
"#xtp: transformed coordinate in TP " << std::endl
572 <<
"#ytp:" << std::endl
573 <<
"#mag: rough mag" << std::endl
574 <<
"#jd: Julian date of the measurement" << std::endl
575 <<
"#rvx: transformed measurement uncertainty " << std::endl
576 <<
"#rvy:" << std::endl
577 <<
"#rvxy:" << std::endl
578 <<
"#color : " << std::endl
579 <<
"#fsindex: some unique index of the object" << std::endl
580 <<
"#ra: pos of fitted star" << std::endl
581 <<
"#dec: pos of fitted star" << std::endl
582 <<
"#chi2: contribution to Chi2 (2D dofs)" << std::endl
583 <<
"#nm: number of measurements of this FittedStar" << std::endl
584 <<
"#chip: chip number" << std::endl
585 <<
"#visit: visit id" << std::endl
586 <<
"#end" << std::endl;
588 for (
auto const &i : L) {
591 const Mapping *mapping = _astrometryModel->getMapping(im);
593 double mjd = im.
getMjd() - _JDRef;
594 for (
auto const &is : cat) {
599 tweakAstromMeasurementErrors(inPos, ms, _posError);
601 const Gtransfo *sky2TP = _astrometryModel->getSky2TP(im);
604 Point fittedStarInTP =
605 transformFittedStar(*fs, sky2TP, refractionVector, _refractionCoefficient, mjd);
606 Point res = tpPos - fittedStarInTP;
607 double det = tpPos.
vx * tpPos.
vy - std::pow(tpPos.
vxy, 2);
608 double wxx = tpPos.
vy / det;
609 double wyy = tpPos.
vx / det;
610 double wxy = -tpPos.
vxy / det;
612 double chi2 = wxx * res.
x * res.
x + wyy * res.
y * res.
y + 2 * wxy * res.
x * res.
y;
613 tuple << std::setprecision(9);
614 tuple << ms.
x <<
' ' << ms.
y <<
' ' << res.
x <<
' ' << res.
y <<
' ' << tpPos.
x <<
' ' << tpPos.
y 615 <<
' ' << fs->getMag() <<
' ' << mjd <<
' ' << tpPos.
vx <<
' ' << tpPos.
vy <<
' ' 616 << tpPos.
vxy <<
' ' << fs->color <<
' ' << fs->getIndexInMatrix() <<
' ' << fs->x <<
' ' 617 << fs->y <<
' ' << chi2 <<
' ' << fs->getMeasurementCount() <<
' ' << im.
getCcdId() <<
' ' 624 std::ofstream tuple(tupleName.c_str());
625 tuple <<
"#ra: coordinates of FittedStar" << std::endl
626 <<
"#dec: " << std::endl
627 <<
"#rx: residual in degrees in TP" << std::endl
628 <<
"#ry:" << std::endl
629 <<
"#mag: mag" << std::endl
630 <<
"#rvx: transformed measurement uncertainty " << std::endl
631 <<
"#rvy:" << std::endl
632 <<
"#rvxy:" << std::endl
633 <<
"#color : " << std::endl
634 <<
"#fsindex: some unique index of the object" << std::endl
635 <<
"#chi2: contribution to Chi2 (2D dofs)" << std::endl
636 <<
"#nm: number of measurements of this FittedStar" << std::endl
637 <<
"#end" << std::endl;
641 for (
auto const &i : fittedStarList) {
644 if (rs ==
nullptr)
continue;
649 double rx = rsProj.
x;
650 double ry = rsProj.
y;
651 double det = rsProj.
vx * rsProj.
vy - std::pow(rsProj.
vxy, 2);
652 double wxx = rsProj.
vy / det;
653 double wyy = rsProj.
vx / det;
654 double wxy = -rsProj.
vxy / det;
655 double chi2 = wxx * std::pow(rx, 2) + 2 * wxy * rx * ry + wyy * std::pow(ry, 2);
656 tuple << std::setprecision(9);
657 tuple << fs.
x <<
' ' << fs.
y <<
' ' << rx <<
' ' << ry <<
' ' << fs.
getMag() <<
' ' << rsProj.
vx Objects used as position anchors, typically USNO stars.
VisitIdType getVisit() const
returns visit ID
implements the linear transformations (6 real coefficients).
virtual class needed in the abstraction of the distortion model
int getMeasurementCount() const
double getMjd() const
Julian Date.
void makeRefResTuple(std::string const &tupleName) const
Produces a tuple containing residuals of reference terms.
CcdIdType getCcdId() const
returns ccd ID
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
A Point with uncertainties.
A list of MeasuredStar. They are usually filled in Associations::AddImage.
Class for a simple mapping implementing a generic Gtransfo.
Eigen::SparseMatrix< double > SpMat
A list of FittedStar s. Such a list is typically constructed by Associations.
objects measured on actual images.
void makeMeasResTuple(std::string const &tupleName) const
Produces a tuple containing residuals of measurement terms.
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 setTangentPoint(const Point &tangentPoint)
Resets the projection (or tangent) point.
void saveResultTuples(std::string const &tupleName) const override
Save the full chi2 term per star that was used in the minimization, for debugging.
std::shared_ptr< const FittedStar > getFittedStar() const
This one is the Tangent Plane (called gnomonic) projection (from celestial sphere to tangent plane) ...
void transformPosAndErrors(const FatPoint &in, FatPoint &out) const
transform with analytical derivatives
Point getRefractionVector() const
MeasuredStarList const & getCatalogForFit() const
Gets the catalog to be used for fitting, which may have been cleaned-up.
a virtual (interface) class for geometric transformations.
const RefStar * getRefStar() const
Get the astrometric reference star associated with this star.
void leastSquareDerivatives(TripletList &tripletList, Eigen::VectorXd &grad) const
Evaluates the chI^2 derivatives (Jacobian and gradient) for the current whatToFit setting...
int getIndexInMatrix() const
std::list< std::shared_ptr< CcdImage > > CcdImageList
double getMag() const
derived using available zero points in input images. In the absence ofZP, ZP= 0.
Handler of an actual image from a single CCD.
bool isValid() const
Fits may use that to discard outliers.
void checkStuff()
DEBUGGING routine.
unsigned getNextFreeIndex() const
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)