29 #include "boost/math/tools/minima.hpp" 44 #define LSST_ScaledPolynomialTransformFitter_TEST_IN_PLACE 0 46 namespace lsst {
namespace meas {
namespace astrom {
69 static Keys const it(0);
86 schema,
"src",
"source positions in pixel coordinates.",
"pix" 91 schema,
"intermediate",
"reference positions in intermediate world coordinates",
"deg" 96 schema,
"initial",
"reference positions transformed by initial WCS",
"pix" 101 schema,
"model",
"result of applying transform to reference positions",
"pix" 106 schema,
"src", {
"x",
"y"},
"pix" 112 "True if the match should be rejected from the fit." 123 schema,
"output",
"grid output positions in intermediate world coordinates",
"deg" 128 schema,
"input",
"grid input positions in pixel coordinates.",
"pix" 133 schema,
"model",
"result of applying transform to input positions",
"deg" 150 for (
auto const & record : data) {
164 double intrinsicScatter
169 float var2 = intrinsicScatter*intrinsicScatter;
171 for (
auto const & match : matches) {
172 auto record = catalog.
addNew();
173 record->set(keys.
refId, match.first->getId());
174 record->set(keys.
srcId, match.second->getId());
175 record->set(keys.
input, initialIwcToSky->applyInverse(match.first->getCoord()));
177 record->set(keys.
output, match.second->getCentroid());
178 record->set(keys.
outputErr, match.second->getCentroidErr() + var2*Eigen::Matrix2f::Identity());
186 computeScaling(catalog, keys.
input),
187 computeScaling(catalog, keys.
output)
194 int nGridX,
int nGridY,
199 catalog.
reserve(nGridX*nGridY);
202 for (
int iy = 0; iy < nGridY; ++iy) {
203 for (
int ix = 0; ix < nGridX; ++ix) {
205 auto record = catalog.
addNew();
206 record->set(keys.
output, point);
207 record->set(keys.
input, toInvert(point));
215 computeScaling(catalog, keys.
input),
216 computeScaling(catalog, keys.
output)
220 ScaledPolynomialTransformFitter::ScaledPolynomialTransformFitter(
224 double intrinsicScatter,
229 _intrinsicScatter(intrinsicScatter),
231 _outputScaling(outputScaling),
250 for (
int n = 0, j = 0; n <= maxOrder; ++n) {
251 for (
int p = 0, q = n; p <= n; ++p, --q, ++j) {
252 _vandermonde(i, j) = _transform._poly._u[p] * _transform._poly._v[q];
259 int maxOrder = _transform.getPoly().getOrder();
263 if (order > maxOrder) {
266 (boost::format(
"Order (%d) exceeded maximum order for the fitter (%d)")
267 % order % maxOrder).str()
273 if (_keys.rejected.isValid()) {
274 for (
auto const & record : _data) {
275 if (!record.get(_keys.rejected)) {
280 nGood = _data.size();
285 Eigen::MatrixXd
m = Eigen::MatrixXd::Zero(nGood, packedSize);
287 Eigen::VectorXd vx = Eigen::VectorXd::Zero(nGood);
288 Eigen::VectorXd vy = Eigen::VectorXd::Zero(nGood);
291 Eigen::ArrayXd sxx(nGood);
292 Eigen::ArrayXd syy(nGood);
293 Eigen::ArrayXd sxy(nGood);
294 Eigen::Matrix2d outS = _outputScaling.getLinear().getMatrix();
295 for (
std::size_t i1 = 0, i2 = 0; i1 < _data.size(); ++i1) {
298 if (!_keys.rejected.isValid() || !_data[i1].get(_keys.rejected)) {
300 vx[i2] = output.getX();
301 vy[i2] = output.getY();
302 m.row(i2) = _vandermonde.row(i1).head(packedSize);
303 if (_keys.outputErr.isValid()) {
304 Eigen::Matrix2d modelErr = outS*_data[i1].get(_keys.outputErr).cast<
double>()*outS.adjoint();
305 sxx[i2] = modelErr(0, 0);
306 sxy[i2] = modelErr(0, 1);
307 syy[i2] = modelErr(1, 1);
317 Eigen::ArrayXd fxx = 1.0/(sxx - sxy.square()/syy);
318 Eigen::ArrayXd fyy = 1.0/(syy - sxy.square()/sxx);
319 Eigen::ArrayXd fxy = -(sxy/sxx)*fyy;
320 #ifdef LSST_ScaledPolynomialTransformFitter_TEST_IN_PLACE 321 assert((sxx*fxx + sxy*fxy).isApproxToConstant(1.0));
322 assert((syy*fyy + sxy*fxy).isApproxToConstant(1.0));
323 assert((sxx*fxy).isApprox(-sxy*fyy));
324 assert((sxy*fxx).isApprox(-syy*fxy));
328 Eigen::MatrixXd h(2*packedSize, 2*packedSize);
329 h.topLeftCorner(packedSize, packedSize) = m.adjoint() * fxx.matrix().asDiagonal() * m;
330 h.topRightCorner(packedSize, packedSize) = m.adjoint() * fxy.matrix().asDiagonal() * m;
331 h.bottomLeftCorner(packedSize, packedSize) = h.topRightCorner(packedSize, packedSize).adjoint();
332 h.bottomRightCorner(packedSize, packedSize) = m.adjoint() * fyy.matrix().asDiagonal() * m;
334 Eigen::VectorXd g(2*packedSize);
335 g.head(packedSize) = m.adjoint() * (fxx.matrix().asDiagonal()*vx + fxy.matrix().asDiagonal()*vy);
336 g.tail(packedSize) = m.adjoint() * (fxy.matrix().asDiagonal()*vx + fyy.matrix().asDiagonal()*vy);
339 auto solution = lstsq.getSolution();
341 for (
int n = 0, j = 0; n <= order; ++n) {
342 for (
int p = 0, q = n; p <= n; ++p, --q, ++j) {
343 _transform._poly._xCoeffs(p, q) = solution[j];
344 _transform._poly._yCoeffs(p, q) = solution[j + packedSize];
350 for (
auto & record : _data) {
353 _transform(record.get(_keys.input))
359 if (!_keys.rejected.isValid()) {
362 "Cannot compute intrinsic scatter on fitter initialized with fromGrid." 365 double newIntrinsicScatter = computeIntrinsicScatter();
366 float varDiff = newIntrinsicScatter*newIntrinsicScatter - _intrinsicScatter*_intrinsicScatter;
367 for (
auto & record : _data) {
368 record.set(_keys.outputErr, record.get(_keys.outputErr) + varDiff*Eigen::Matrix2f::Identity());
370 _intrinsicScatter = newIntrinsicScatter;
371 return _intrinsicScatter;
374 double ScaledPolynomialTransformFitter::computeIntrinsicScatter()
const {
380 double directVariance = 0.0;
381 double maxMeasurementVariance = 0.0;
382 double oldIntrinsicVariance = _intrinsicScatter*_intrinsicScatter;
384 for (
auto const & record : _data) {
385 if (!_keys.rejected.isValid() || !record.get(_keys.rejected)) {
386 auto delta = record.get(_keys.output) - record.get(_keys.model);
387 directVariance += 0.5*delta.computeSquaredNorm();
388 double cxx = _keys.outputErr.getElement(record, 0, 0) - oldIntrinsicVariance;
389 double cyy = _keys.outputErr.getElement(record, 1, 1) - oldIntrinsicVariance;
390 double cxy = _keys.outputErr.getElement(record, 0, 1);
392 double ca2 = 0.5*(cxx + cyy +
std::sqrt(cxx*cxx + cyy*cyy + 4*cxy*cxy - 2*cxx*cyy));
393 maxMeasurementVariance =
std::max(maxMeasurementVariance, ca2);
397 directVariance /= nGood;
401 auto logLikelihood = [
this](
double intrinsicVariance) {
404 double varDiff = intrinsicVariance - this->_intrinsicScatter*this->_intrinsicScatter;
406 for (
auto & record : this->_data) {
407 double x = record.get(this->_keys.output.getX()) - record.get(_keys.model.getX());
408 double y = record.get(this->_keys.output.getY()) - record.get(_keys.model.getY());
409 double cxx = this->_keys.outputErr.getElement(record, 0, 0) + varDiff;
410 double cyy = this->_keys.outputErr.getElement(record, 1, 1) + varDiff;
411 double cxy = this->_keys.outputErr.getElement(record, 0, 1);
412 double det = cxx*cyy - cxy*cxy;
413 q += (x*x*cyy - 2*x*y*cxy + y*y*cxx)/det +
std::log(det);
420 double minIntrinsicVariance =
std::max(0.0, directVariance - maxMeasurementVariance);
423 static constexpr
int BITS_REQUIRED = 16;
424 boost::uintmax_t maxIterations = 20;
425 auto result = boost::math::tools::brent_find_minima(
427 minIntrinsicVariance,
441 if (!_keys.rejected.isValid()) {
444 "Cannot reject outliers on fitter initialized with fromGrid." 447 if (static_cast<std::size_t>(ctrl.
nClipMin) >= _data.size()) {
450 (boost::format(
"Not enough values (%d) to clip %d.")
451 % _data.size() % ctrl.
nClipMin).str()
455 for (
auto & record : _data) {
456 Eigen::Matrix2d cov = record.get(_keys.outputErr).cast<
double>();
457 Eigen::Vector2d d = (record.get(_keys.output) - record.get(_keys.model)).asEigen();
458 double r2 = d.dot(cov.inverse() * d);
461 auto cutoff = rankings.upper_bound(ctrl.
nSigma * ctrl.
nSigma);
462 int nClip = 0, nGood = 0;
463 for (
auto iter = rankings.begin(); iter != cutoff; ++iter) {
464 iter->second->set(_keys.rejected,
false);
467 for (
auto iter = cutoff; iter != rankings.end(); ++iter) {
468 iter->second->set(_keys.rejected,
true);
471 assert(static_cast<std::size_t>(nGood + nClip) == _data.size());
474 cutoff->second->set(_keys.rejected,
true);
477 while (nClip > ctrl.
nClipMax && cutoff != rankings.end()) {
478 cutoff->second->set(_keys.rejected,
false);
483 if (cutoff != rankings.end()) {
daf::base::Citizen & getCitizen()
static PointKey addFields(Schema &schema, std::string const &name, std::string const &doc, std::string const &unit)
int nClipMax
"Never clip more than this many matches." ;
static LeastSquares fromNormalEquations(ndarray::Array< T1, 2, C1 > const &fisher, ndarray::Array< T2, 1, C2 > const &rhs, Factorization factorization=NORMAL_EIGENSYSTEM)
int nClipMin
"Always clip at least this many matches." ;
Key< T > addField(Field< T > const &field, bool doReplace=false)
int computePackedSize(int order)
Compute this size of a packed 2-d polynomial coefficient array.
void markPersistent(void)
Point2D const getCenter() const
void include(Point2D const &point)
Point2D skyToPixel(coord::IcrsCoord const &sky) const
void reserve(size_type n)
#define LSST_EXCEPT(type,...)
double nSigma
"Number of sigma to clip at." ;
Control object for outlier rejection in ScaledPolynomialTransformFitter.
std::shared_ptr< TransformPoint2ToIcrsCoord > getIntermediateWorldCoordsToSky(SkyWcs const &wcs, bool simplify=true)
std::shared_ptr< RecordT > addNew()
void computePowers(Eigen::VectorXd &r, double x)
Fill an array with integer powers of x, so .
Point2D const getMin() const