LSSTApplications  11.0-24-g0a022a1,14.0+77,15.0,15.0+1
LSSTDataManagementBasePackage
matchOptimisticB.cc
Go to the documentation of this file.
1 #include <algorithm>
2 #include <cmath>
3 #include <fstream>
4 #include <iostream>
5 #include <memory>
6 #include <utility>
7 #include <vector>
8 
9 #include "boost/shared_array.hpp"
10 #include "boost/multi_index_container.hpp"
11 #include "boost/multi_index/sequenced_index.hpp"
12 #include "boost/multi_index/ordered_index.hpp"
13 #include "boost/multi_index/global_fun.hpp"
14 
15 #include "gsl/gsl_linalg.h"
16 
17 #include "lsst/pex/exceptions.h"
18 #include "lsst/afw/geom/SkyWcs.h"
19 #include "lsst/afw/geom/Angle.h"
21 
23 namespace afwTable = lsst::afw::table;
24 
25 namespace {
26  using namespace lsst::meas::astrom;
27 
28  afwTable::RecordId getRefId(ProxyPair const &proxyPair) {
29  return proxyPair.first.record->getId();
30  }
31 
32  // a collection of ReferenceMatch indexed by insertion order and by reference object ID
33  struct refIdTag{}; // tag for retrieval by reference object ID
34  struct insertionOrderTag{}; // tag for retrieval by insertion order
35  typedef boost::multi_index_container<
36  ProxyPair,
37  boost::multi_index::indexed_by< // index by insertion order, so brightest sources first
38  boost::multi_index::sequenced<
39  boost::multi_index::tag<insertionOrderTag>
40  >,
41  boost::multi_index::ordered_unique< // index by reference object ID
42  boost::multi_index::tag<refIdTag>,
43  boost::multi_index::global_fun<
44  ProxyPair const &, afwTable::RecordId , &getRefId
45  > >
46  >
47  > MultiIndexedProxyPairList;
48 
49  // Algorithm is based on V.Tabur 2007, PASA, 24, 189-198
50  // "Fast Algorithms for Matching CCD Images to a Stellar Catalogue"
51 
58  inline double absDeltaAngle(double ang1, double ang2) {
59  return std::fmod(std::fabs(ang1 - ang2), M_PI*2);
60  }
61 
62  bool cmpPair(ProxyPair const &a, ProxyPair const &b) {
63  return a.distance > b.distance;
64  }
65 
71  struct CompareProxyFlux {
72 
73  bool operator()(RecordProxy const & a, RecordProxy const & b) const {
74  double aFlux = a.record->get(key);
75  double bFlux = b.record->get(key);
76  if (std::isnan(aFlux)) {
77  aFlux = 0.0;
78  }
79  if (std::isnan(bFlux)) {
80  bFlux = 0.0;
81  }
82  return aFlux > bFlux;
83  }
84 
86  };
87 
98  ProxyVector selectPoint(
99  ProxyVector const &a,
100  afwTable::Key<double> const & key,
101  std::size_t num,
102  std::size_t startInd=0
103  ) {
104  if (startInd >= a.size()) {
105  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "startInd too big");
106  }
107  CompareProxyFlux cmp = {key};
108  ProxyVector b(a);
109  std::sort(b.begin(), b.end(), cmp);
110  std::size_t const endInd = std::min(startInd + num, b.size());
111  return ProxyVector(b.begin() + startInd, b.begin() + endInd);
112  }
113 
114  std::vector<ProxyPair> searchPair(
115  std::vector<ProxyPair> const &a,
116  ProxyPair const &p,
117  double e,
118  double e_dpa
119  ) {
121 
122  for (size_t i = 0; i < a.size(); i++) {
123  double dd = std::fabs(a[i].distance - p.distance);
124  double dpa = absDeltaAngle(a[i].pa, p.pa);
125  if (dd < e && dpa < e_dpa) {
126  v.push_back(a[i]);
127  }
128  }
129 
130  return v;
131  }
132 
135  ProxyPair const &p,
136  ProxyPair const &q,
137  double e,
138  double dpa,
139  double e_dpa = 0.02
140  ) {
142  double dd_min = 1.E+10;
143  //double dpa_min = e_dpa;
144 
145  for (auto i = a.begin(); i != a.end(); ++i) {
146  double dd = std::fabs(i->distance - p.distance);
147  #if 1
148  if (dd < e &&
149  absDeltaAngle(p.pa, i->pa - dpa) < e_dpa &&
150  dd < dd_min &&
151  (i->first == q.first)) {
152  dd_min = dd;
153  idx = i;
154  }
155  #else
156  if (dd < e &&
157  absDeltaAngle(p.pa, i->pa - dpa) < dpa_min) {
158  dpa_min = std::fabs(p.pa - i->pa - dpa);
159  idx = i;
160  }
161  #endif
162  }
163 
164  return idx;
165  }
166 
167  void transform(
168  int order,
169  boost::shared_array<double> const & coeff,
170  double x,
171  double y,
172  double *xn,
173  double *yn
174  ) {
175  int ncoeff = (order + 1) * (order + 2) / 2;
176  *xn = 0.0;
177  *yn = 0.0;
178  int n = 0;
179  for (int i = 0; i <= order; i++) {
180  for (int k = 0; k <= i; k++) {
181  int j = i - k;
182  *xn += coeff[n] * pow(x, j) * pow(y, k);
183  *yn += coeff[n+ncoeff] * pow(x, j) * pow(y, k);
184  n++;
185  }
186  }
187  }
188 
189  boost::shared_array<double> polyfit(
190  int order,
191  ProxyVector const &img,
192  ProxyVector const &posRefCat
193  ) {
194  int ncoeff = (order + 1) * (order + 2) / 2;
195  std::unique_ptr<int[]> xorder(new int[ncoeff]);
196  std::unique_ptr<int[]> yorder(new int[ncoeff]);
197 
198  int n = 0;
199  for (int i = 0; i <= order; i++) {
200  for (int k = 0; k <= i; k++) {
201  int j = i - k;
202  xorder[n] = j;
203  yorder[n] = k;
204  n++;
205  }
206  }
207 
208  std::unique_ptr<int[]> flag(new int[img.size()]);
209  for (size_t k = 0; k < img.size(); k++) {
210  flag[k] = 1;
211  }
212 
213  std::unique_ptr<double[]> a_data(new double[ncoeff*ncoeff]);
214  std::unique_ptr<double[]> b_data(new double[ncoeff]);
215  std::unique_ptr<double[]> c_data(new double[ncoeff]);
216 
217  boost::shared_array<double> coeff(new double[ncoeff*2]);
218 
219  for (int loop = 0; loop < 1; loop++) {
220  for (int i = 0; i < ncoeff; i++) {
221  for (int j = 0; j < ncoeff; j++) {
222  a_data[i*ncoeff+j] = 0.0;
223  for (size_t k = 0; k < img.size(); k++) {
224  if (flag[k] == 1) {
225  a_data[i*ncoeff+j] += pow(img[k].getX(), xorder[i]) *
226  pow(img[k].getY(), yorder[i]) *
227  pow(img[k].getX(), xorder[j]) *
228  pow(img[k].getY(), yorder[j]);
229  }
230  }
231  }
232  b_data[i] = c_data[i] = 0.0;
233  for (unsigned int k = 0; k < img.size(); k++) {
234  if (flag[k] == 1) {
235  b_data[i] += pow(img[k].getX(), xorder[i]) *
236  pow(img[k].getY(), yorder[i]) *
237  posRefCat[k].getX();
238  c_data[i] += pow(img[k].getX(), xorder[i]) *
239  pow(img[k].getY(), yorder[i]) *
240  posRefCat[k].getY();
241  }
242  }
243  }
244 
245  gsl_matrix_view a = gsl_matrix_view_array(a_data.get(), ncoeff, ncoeff);
246  gsl_vector_view b = gsl_vector_view_array(b_data.get(), ncoeff);
247  gsl_vector_view c = gsl_vector_view_array(c_data.get(), ncoeff);
248 
249  std::shared_ptr<gsl_vector> x(gsl_vector_alloc(ncoeff), gsl_vector_free);
250  std::shared_ptr<gsl_vector> y(gsl_vector_alloc(ncoeff), gsl_vector_free);
251 
252  int s;
253 
254  std::shared_ptr<gsl_permutation> p(gsl_permutation_alloc(ncoeff), gsl_permutation_free);
255 
256  gsl_linalg_LU_decomp(&a.matrix, p.get(), &s);
257  gsl_linalg_LU_solve(&a.matrix, p.get(), &b.vector, x.get());
258  gsl_linalg_LU_solve(&a.matrix, p.get(), &c.vector, y.get());
259 
260  for (int i = 0; i < ncoeff; i++) {
261  coeff[i] = x->data[i];
262  coeff[i+ncoeff] = y->data[i];
263  }
264 
265  double S, Sx, Sy, Sxx, Syy;
266  S = Sx = Sy = Sxx = Syy = 0.0;
267  for (size_t k = 0; k < img.size(); k++) {
268  if (flag[k] == 1) {
269  double x0 = img[k].getX();
270  double y0 = img[k].getY();
271  double x1, y1;
272  transform(order, coeff, x0, y0, &x1, &y1);
273  S += 1.;
274  Sx += (x1 - posRefCat[k].getX());
275  Sxx += (x1 - posRefCat[k].getX()) * (x1 - posRefCat[k].getX());
276  Sy += (y1 - posRefCat[k].getY());
277  Syy += (y1 - posRefCat[k].getY()) * (y1 - posRefCat[k].getY());
278  }
279  }
280  double x_sig = std::sqrt((Sxx - Sx * Sx / S) / S);
281  double y_sig = std::sqrt((Syy - Sy * Sy / S) / S);
282  //std::cout << x_sig << " " << y_sig << std::endl;
283 
284  for (size_t k = 0; k < img.size(); k++) {
285  double x0 = img[k].getX();
286  double y0 = img[k].getY();
287  double x1, y1;
288  transform(order, coeff, x0, y0, &x1, &y1);
289  if (std::fabs(x1-posRefCat[k].getX()) > 2. * x_sig ||
290  std::fabs(y1-posRefCat[k].getY()) > 2. * y_sig) {
291  flag[k] = 0;
292  }
293  }
294 
295  }
296 
297  return coeff;
298  }
299 
313  ProxyVector const &posRefCat,
314  double x,
315  double y,
316  double matchingAllowancePix
317  ) {
318  auto minDistSq = matchingAllowancePix*matchingAllowancePix;
319  auto foundPtr = posRefCat.end();
320  for (auto posRefPtr = posRefCat.begin(); posRefPtr != posRefCat.end(); ++posRefPtr) {
321  auto const dx = posRefPtr->getX() - x;
322  auto const dy = posRefPtr->getY() - y;
323  auto const distSq = dx*dx + dy*dy;
324  if (distSq < minDistSq) {
325  foundPtr = posRefPtr;
326  minDistSq = distSq;
327  }
328  }
329  return std::make_pair(foundPtr, std::sqrt(minDistSq));
330  }
331 
350  void addNearestMatch(
351  MultiIndexedProxyPairList &proxyPairList,
352  boost::shared_array<double> coeff,
353  ProxyVector const & posRefCat,
354  RecordProxy const &source,
355  double matchingAllowancePix
356  ) {
357  double x1, y1;
358  auto x0 = source.getX();
359  auto y0 = source.getY();
360  transform(1, coeff, x0, y0, &x1, &y1);
361  auto refObjDist = searchNearestPoint(posRefCat, x1, y1, matchingAllowancePix);
362  if (refObjDist.first == posRefCat.end()) {
363  // no reference object sufficiently close; do nothing
364  return;
365  }
366  auto existingMatch = proxyPairList.get<refIdTag>().find(refObjDist.first->record->getId());
367  if (existingMatch == proxyPairList.get<refIdTag>().end()) {
368  // reference object not used before; add new entry
369  auto proxyPair = ProxyPair(*refObjDist.first, source);
370  proxyPairList.get<refIdTag>().insert(proxyPair);
371  return;
372 #if 0
373  } else {
374  // This code keeps the closest match. It is disabled because it appears to be unnecessary
375  // and may be undesirable, since the image may have very faint sources which may give
376  // false matches. Note that the calling algorithm checks for matches starting with the
377  // brightest source and works down in source brightness.
378  if (existingMatch->distance <= refObjDist.second) {
379  // reference object used before and old source is closer; do nothing
380  return;
381  } else {
382  // reference object used before, but new source is closer; delete old entry and add new
383  proxyPairList.get<refIdTag>().erase(existingMatch);
384  auto proxyPair = ProxyPair(*refObjDist.first, source);
385  proxyPairList.get<refIdTag>().insert(proxyPair);
386  return;
387  }
388 #endif
389  }
390  }
391 
400  afwTable::ReferenceMatchVector FinalVerify(
401  boost::shared_array<double> coeff,
402  ProxyVector const & posRefCat,
403  ProxyVector const & sourceCat,
404  double matchingAllowancePix,
405  bool verbose
406  ) {
407  MultiIndexedProxyPairList proxyPairList;
408 
409  for (auto sourcePtr = sourceCat.begin(); sourcePtr != sourceCat.end(); ++sourcePtr) {
410  addNearestMatch(proxyPairList, coeff, posRefCat, *sourcePtr, matchingAllowancePix);
411  }
412  int order = 1;
413  if (proxyPairList.size() > 5) {
414  for (auto j = 0; j < 100; j++) {
415  auto prevNumMatches = proxyPairList.size();
416  auto srcMat = ProxyVector();
417  auto catMat = ProxyVector();
418  srcMat.reserve(proxyPairList.size());
419  catMat.reserve(proxyPairList.size());
420  for (auto matchPtr = proxyPairList.get<refIdTag>().begin();
421  matchPtr != proxyPairList.get<refIdTag>().end(); ++matchPtr) {
422  catMat.push_back(matchPtr->first);
423  srcMat.push_back(matchPtr->second);
424  }
425  coeff = polyfit(order, srcMat, catMat);
426  proxyPairList.clear();
427 
428  for (ProxyVector::const_iterator sourcePtr = sourceCat.begin();
429  sourcePtr != sourceCat.end(); ++sourcePtr) {
430  addNearestMatch(proxyPairList, coeff, posRefCat, *sourcePtr, matchingAllowancePix);
431  }
432  if (proxyPairList.size() == prevNumMatches) {
433  break;
434  }
435  }
436  }
437  auto matPair = afwTable::ReferenceMatchVector();
438  matPair.reserve(proxyPairList.size());
439  for (auto proxyPairIter = proxyPairList.get<insertionOrderTag>().begin();
440  proxyPairIter != proxyPairList.get<insertionOrderTag>().end(); ++proxyPairIter) {
441  matPair.push_back(afwTable::ReferenceMatch(
442  proxyPairIter->first.record,
443  std::static_pointer_cast<afwTable::SourceRecord>(proxyPairIter->second.record),
444  proxyPairIter->distance
445  ));
446  }
447 
448  return matPair;
449  }
450 
451 } // anonymous namespace
452 
453 namespace lsst {
454 namespace meas {
455 namespace astrom {
456 
458  if (refFluxField.empty()) {
459  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "refFluxField must be specified");
460  }
461  if (sourceFluxField.empty()) {
462  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "sourceFluxField must be specified");
463  }
464  if (numBrightStars <= 0) {
465  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "numBrightStars must be positive");
466  }
467  if (minMatchedPairs < 0) {
468  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "minMatchedPairs must not be negative");
469  }
470  if (matchingAllowancePix <= 0) {
471  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "matchingAllowancePix must be positive");
472  }
473  if (maxOffsetPix <= 0) {
474  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "maxOffsetPix must be positive");
475  }
476  if (maxRotationDeg <= 0) {
477  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "maxRotationRad must be positive");
478  }
479  if (allowedNonperpDeg <= 0) {
480  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "allowedNonperpDeg must be positive");
481  }
482  if (numPointsForShape <= 0) {
483  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "numPointsForShape must be positive");
484  }
485  if (maxDeterminant <= 0) {
486  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "maxDeterminant must be positive");
487  }
488  }
489 
491  afw::geom::SkyWcs const& distortedWcs,
492  afw::geom::SkyWcs const& tanWcs
493  )
494  {
495  ProxyVector r;
496  r.reserve(sourceCat.size());
497  for (afwTable::SourceCatalog::const_iterator sourcePtr = sourceCat.begin();
498  sourcePtr != sourceCat.end(); ++sourcePtr) {
499  r.push_back(RecordProxy(sourcePtr,
500  tanWcs.skyToPixel(distortedWcs.pixelToSky(sourcePtr->getCentroid()))));
501  }
502  return r;
503  }
504 
506  afw::geom::SkyWcs const& tanWcs
507  )
508  {
509  auto coordKey = afwTable::CoordKey(posRefCat.getSchema()["coord"]);
510  ProxyVector r;
511  r.reserve(posRefCat.size());
512  for (afwTable::SimpleCatalog::const_iterator posRefPtr = posRefCat.begin();
513  posRefPtr != posRefCat.end(); ++posRefPtr) {
514  r.push_back(RecordProxy(posRefPtr, tanWcs.skyToPixel(posRefPtr->get(coordKey))));
515  }
516  return r;
517  }
518 
520  afwTable::SimpleCatalog const &posRefCat,
521  afwTable::SourceCatalog const &sourceCat,
522  MatchOptimisticBControl const &control,
523  afw::geom::SkyWcs const& wcs,
524  int posRefBegInd,
525  bool verbose
526  ) {
527  control.validate();
528  if (posRefCat.empty()) {
529  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "no entries in posRefCat");
530  }
531  if (sourceCat.empty()) {
532  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "no entries in sourceCat");
533  }
534  if (posRefBegInd < 0) {
535  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "posRefBegInd < 0");
536  }
537  if (static_cast<size_t>(posRefBegInd) >= posRefCat.size()) {
538  throw LSST_EXCEPT(pexExcept::InvalidParameterError, "posRefBegInd too big");
539  }
540  double const maxRotationRad = afw::geom::degToRad(control.maxRotationDeg);
541 
542  // Create an undistorted Wcs to project everything with
543  // We'll anchor it at the center of the area.
544  afw::geom::Extent2D srcCenter(0, 0);
545  for (auto iter = sourceCat.begin(); iter != sourceCat.end(); ++iter) {
546  srcCenter += afw::geom::Extent2D(iter->getCentroid());
547  }
548  srcCenter /= sourceCat.size();
549 
550  afw::geom::Extent3D refCenter(0, 0, 0);
551  for (auto iter = posRefCat.begin(); iter != posRefCat.end(); ++iter) {
552  refCenter += afw::geom::Extent3D(iter->getCoord().toIcrs().getVector());
553  }
554  refCenter /= posRefCat.size();
555 
556  auto tanWcs = afw::geom::makeSkyWcs(
557  afw::geom::Point2D(srcCenter),
558  afw::coord::IcrsCoord(afw::geom::Point3D(refCenter)).getPosition(),
559  wcs.getCdMatrix()
560  );
561 
562  ProxyVector posRefProxyCat = makeProxies(posRefCat, *tanWcs);
563  ProxyVector sourceProxyCat = makeProxies(sourceCat, wcs, *tanWcs);
564 
565  // sourceSubCat contains at most the numBrightStars brightest sources, sorted by decreasing flux
566  ProxyVector sourceSubCat = selectPoint(
567  sourceProxyCat,
568  sourceCat.getSchema().find<double>(control.sourceFluxField).key,
569  control.numBrightStars);
570 
571  // posRefSubCat skips the initial posRefBegInd brightest reference objects and contains
572  // at most the next len(sourceSubCat) + 25 brightest reference objects, sorted by decreasing flux
573  ProxyVector posRefSubCat = selectPoint(
574  posRefProxyCat,
575  posRefCat.getSchema().find<double>(control.refFluxField).key,
576  sourceSubCat.size()+25,
577  posRefBegInd);
578  if (verbose) {
579  std::cout << "Catalog sizes: " << sourceSubCat.size() << " " << posRefSubCat.size() << std::endl;
580  }
581 
582  // Construct a list of pairs of position reference stars sorted by increasing separation
583  std::vector<ProxyPair> posRefPairList;
584  size_t const posRefCatSubSize = posRefSubCat.size();
585  for (size_t i = 0; i < posRefCatSubSize-1; i++) {
586  for (size_t j = i+1; j < posRefCatSubSize; j++) {
587  posRefPairList.push_back(ProxyPair(posRefSubCat[i], posRefSubCat[j]));
588  }
589  }
590  std::sort(posRefPairList.begin(), posRefPairList.end(), cmpPair);
591 
592  // Construct a list of pairs of sources sorted by increasing separation
593  std::vector<ProxyPair> sourcePairList;
594  size_t const sourceSubCatSize = sourceSubCat.size();
595  for (size_t i = 0; i < sourceSubCatSize-1; i++) {
596  for (size_t j = i+1; j < sourceSubCatSize; j++) {
597  sourcePairList.push_back(ProxyPair(sourceSubCat[i], sourceSubCat[j]));
598  }
599  }
600  std::sort(sourcePairList.begin(), sourcePairList.end(), cmpPair);
601 
603  afwTable::ReferenceMatchVector matPairSave;
605 
606  size_t const fullShapeSize = control.numPointsForShape - 1; // Max size of shape array
607  for (size_t ii = 0; ii < sourcePairList.size(); ii++) {
608  ProxyPair p = sourcePairList[ii];
609 
610  std::vector<ProxyPair> q = searchPair(posRefPairList, p,
611  control.matchingAllowancePix, maxRotationRad);
612 
613  // If candidate pairs are found
614  if (q.size() != 0) {
615 
616  std::vector<ProxyPair> srcMatPair;
617  std::vector<ProxyPair> catMatPair;
618 
619  // Go through candidate pairs
620  for (size_t l = 0; l < q.size(); l++) {
621  // sign matters, so don't use deltaAng; no need to wrap because
622  // the result is used with deltaAng later
623  double dpa = p.pa - q[l].pa;
624 
625  srcMatPair.clear();
626  catMatPair.clear();
627 
628  srcMatPair.push_back(p);
629  catMatPair.push_back(q[l]);
630 
631  if (verbose) {
632  std::cout << "p dist: " << p.distance << " pa: " << p.pa << std::endl;
633  std::cout << "q dist: " << q[l].distance << " pa: " << q[l].pa << std::endl;
634  }
635 
636  for (size_t k = 0; k < sourceSubCat.size(); k++) {
637  if (p.first == sourceSubCat[k] || p.second == sourceSubCat[k]) continue;
638 
639  ProxyPair pp(p.first, sourceSubCat[k]);
640 
641  std::vector<ProxyPair>::iterator r = searchPair3(posRefPairList, pp, q[l],
642  control.matchingAllowancePix, dpa, maxRotationRad);
643  if (r != posRefPairList.end()) {
644  srcMatPair.push_back(pp);
645  catMatPair.push_back(*r);
646  if (verbose) {
647  std::cout << " p dist: " << pp.distance << " pa: " << pp.pa << std::endl;
648  std::cout << " r dist: " << (*r).distance << " pa: " << (*r).pa << std::endl;
649  }
650  if (srcMatPair.size() == fullShapeSize) {
651  break;
652  }
653  }
654  }
655 
656  bool goodMatch = false;
657  if (srcMatPair.size() == fullShapeSize) {
658  goodMatch = true;
659  for (size_t k = 1; k < catMatPair.size(); k++) {
660  if (catMatPair[0].first != catMatPair[k].first) {
661  goodMatch = false;
662  break;
663  }
664  }
665  }
666 
667  if (goodMatch && srcMatPair.size() == fullShapeSize) {
668 
669  ProxyVector srcMat;
670  ProxyVector catMat;
671 
672  srcMat.push_back(srcMatPair[0].first);
673  catMat.push_back(catMatPair[0].first);
674  for (size_t k = 0; k < srcMatPair.size(); k++) {
675  srcMat.push_back(srcMatPair[k].second);
676  catMat.push_back(catMatPair[k].second);
677  }
678 
679  boost::shared_array<double> coeff = polyfit(1, srcMat, catMat);
680 
681  if (verbose) {
682  for (size_t k = 0; k < srcMat.size(); k++) {
683  std::cout << "circle(" << srcMat[k].getX() << ","
684  << srcMat[k].getY() << ",10) # color=green" << std::endl;
685  std::cout << "circle(" << catMat[k].getX() << ","
686  << catMat[k].getY() << ",10) # color=red" << std::endl;
687  std::cout << "line(" << srcMat[0].getX() << "," << srcMat[0].getY() << ","
688  << srcMat[k].getX() << "," << srcMat[k].getY()
689  << ") # line=0 0 color=green" << std::endl;
690  std::cout << "line(" << catMat[0].getX() << "," << catMat[0].getY() << ","
691  << catMat[k].getX() << "," << catMat[k].getY()
692  << ") # line=0 0 color=red" << std::endl;
693  }
694  }
695 
696  double a = coeff[1];
697  double b = coeff[2];
698  double c = coeff[4];
699  double d = coeff[5];
700  afw::geom::Angle const theta(std::acos((a*b+c*d)/
701  (std::sqrt(a*a+c*c)*std::sqrt(b*b+d*d))),
703  if (verbose) {
704  std::cout << "Linear fit from match:" << std::endl;
705  std::cout << coeff[0] << " " << coeff[1] << " " << coeff[2] << std::endl;
706  std::cout << coeff[3] << " " << coeff[4] << " " << coeff[5] << std::endl;
707  std::cout << "Determinant (max " << control.maxDeterminant << "): ";
708  std::cout << coeff[1] * coeff[5] - coeff[2] * coeff[4] - 1. << std::endl;
709  std::cout << "Angle between axes (deg; allowed 90 +/- ";
710  std::cout << control.allowedNonperpDeg << "): ";
711  std::cout << theta.asDegrees() << std::endl;
712  }
713  if (std::fabs(coeff[1] * coeff[5] - coeff[2] * coeff[4] - 1.)
714  > control.maxDeterminant ||
715  std::fabs(theta.asDegrees() - 90) > control.allowedNonperpDeg ||
716  std::fabs(coeff[0]) > control.maxOffsetPix ||
717  std::fabs(coeff[3]) > control.maxOffsetPix) {
718  if (verbose) {
719  std::cout << "Bad; continuing" << std::endl;
720  }
721  continue;
722  } else {
723  double x0, y0, x1, y1;
724  int num = 0;
725  srcMat.clear();
726  catMat.clear();
727  for (size_t i = 0; i < sourceSubCat.size(); i++) {
728  x0 = sourceSubCat[i].getX();
729  y0 = sourceSubCat[i].getY();
730  transform(1, coeff, x0, y0, &x1, &y1);
731  auto refObjDist = searchNearestPoint(posRefSubCat, x1, y1,
732  control.matchingAllowancePix);
733  if (refObjDist.first != posRefSubCat.end()) {
734  num++;
735  srcMat.push_back(sourceSubCat[i]);
736  catMat.push_back(*refObjDist.first);
737  if (verbose) {
738  std::cout << "Match: " << x0 << "," << y0 << " --> "
739  << x1 << "," << y1 << " <==> "
740  << refObjDist.first->getX() << "," << refObjDist.first->getY()
741  << std::endl;
742  }
743  }
744  }
745  if (num <= control.numPointsForShape) {
746  // Can get matrix = 0,0,0,0; everything matches a single catalog object
747  if (verbose) {
748  std::cout << "Insufficient initial matches; continuing" << std::endl;
749  }
750  continue;
751  }
752  coeff = polyfit(1, srcMat, catMat);
753  if (verbose) {
754  std::cout << "Coefficients from initial matching:" << std::endl;
755  for (size_t i = 0; i < 6; ++i) {
756  std::cout << coeff[i] << " ";
757  }
758  std::cout << std::endl;
759  }
760 
761  matPair = FinalVerify(coeff, posRefProxyCat, sourceProxyCat,
762  control.matchingAllowancePix, verbose);
763  if (verbose) {
764  std::cout << "Number of matches: " << matPair.size() << " vs " <<
765  control.minMatchedPairs << std::endl;
766  }
767  if (matPair.size() <= static_cast<std::size_t>(control.minMatchedPairs)) {
768  if (verbose) {
769  std::cout << "Insufficient final matches; continuing" << std::endl;
770  }
771  if (matPair.size() > matPairSave.size()) {
772  matPairSave = matPair;
773  }
774  continue;
775  } else {
776  if (verbose) {
777  std::cout << "Finish" << std::endl;
778  }
779  matPairCand.push_back(matPair);
780  if (matPairCand.size() == 3) {
781  goto END;
782  }
783  }
784  }
785  }
786  }
787  }
788  }
789 
790  END:
791  if (matPairCand.size() == 0) {
792  return matPairSave;
793  } else {
794  size_t nmatch = matPairCand[0].size();
795  afwTable::ReferenceMatchVector matPairRet = matPairCand[0];
796  for (size_t i = 1; i < matPairCand.size(); i++) {
797  if (matPairCand[i].size() > nmatch) {
798  nmatch = matPairCand[i].size();
799  matPairRet = matPairCand[i];
800  }
801  }
802  return matPairRet;
803  }
804  }
805 
806 }}} // namespace lsst::afw::astrom
std::int64_t RecordId
Type used for unique IDs for records.
Definition: misc.h:22
table::Key< int > ncoeff
Definition: PsfexPsf.cc:313
solver_t * s
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Definition: SkyWcs.h:114
Eigen::Matrix2d getCdMatrix(Point2D const &pixel) const
Get the 2x2 CD matrix at the specified pixel position.
def erase(frame=None)
Definition: ds9.py:105
Extent< double, 2 > Extent2D
Definition: Extent.h:383
T acos(T... args)
int numPointsForShape
"number of points in a matching shape" ;
AngleUnit constexpr radians
constant with units of radians
Definition: Angle.h:87
T endl(T... args)
T fmod(T... args)
tbl::Key< int > wcs
double matchingAllowancePix
"maximum allowed distance between reference objects and sources (pixels)" ;
std::vector< ReferenceMatch > ReferenceMatchVector
Definition: fwd.h:108
T end(T... args)
SchemaItem< T > find(std::string const &name) const
Find a SchemaItem in the Schema by name.
Schema getSchema() const
Return the schema associated with the catalog&#39;s table.
Definition: Catalog.h:117
ProxyVector makeProxies(lsst::afw::table::SourceCatalog const &sourceCat, afw::geom::SkyWcs const &distortedWcs, afw::geom::SkyWcs const &tanWcs)
#define M_PI
Definition: ListMatch.cc:7
std::vector< RecordProxy > ProxyVector
T min(T... args)
Include files required for standard LSST Exception handling.
T push_back(T... args)
A class representing an angle.
Definition: Angle.h:102
A base class for image defects.
Definition: cameraGeom.dox:3
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:63
int end
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
iterator end()
Iterator access.
Definition: Catalog.h:397
Lightweight representation of a geometric match between two records.
Definition: fwd.h:101
table::Key< int > b
T make_pair(T... args)
const char * source()
Source function that allows astChannel to source from a Stream.
Definition: Stream.h:224
double x
coord::IcrsCoord pixelToSky(Point2D const &pixel) const
Compute sky position(s) from pixel position(s)
Definition: SkyWcs.h:324
T clear(T... args)
Point2D skyToPixel(coord::IcrsCoord const &sky) const
Compute pixel position(s) from sky position(s)
Definition: SkyWcs.h:335
T fabs(T... args)
table::Key< table::Array< double > > coeff
Definition: PsfexPsf.cc:348
bool empty() const
Return true if the catalog has no records.
Definition: Catalog.h:405
double maxOffsetPix
"maximum allowed frame translation (pixels)" ;
double degToRad(long double angleInDegrees)
Definition: RaDecStr.cc:66
T find(T... args)
T size(T... args)
Extent< double, 3 > Extent3D
Definition: Extent.h:384
#define LSST_EXCEPT(type,...)
Create an exception with a given type and message and optionally other arguments (dependent on the ty...
Definition: Exception.h:46
STL class.
int numBrightStars
"maximum number of bright reference stars to use" ;
std::string refFluxField
"name of flux field in reference catalog" ;
Base::const_iterator const_iterator
Definition: SortedCatalog.h:50
T begin(T... args)
A wrapper around a SimpleRecord or SourceRecord that allows us to record a pixel position in a way th...
T pow(T... args)
lsst::afw::table::ReferenceMatchVector matchOptimisticB(lsst::afw::table::SimpleCatalog const &posRefCat, lsst::afw::table::SourceCatalog const &sourceCat, MatchOptimisticBControl const &control, afw::geom::SkyWcs const &wcs, int posRefBegInd=0, bool verbose=false)
Match sources to stars in a position reference catalog using optimistic pattern matching B...
T isnan(T... args)
double maxRotationDeg
"maximum allowed frame rotation (deg)" ;
table::Key< int > a
size_type size() const
Return the number of elements in the catalog.
Definition: Catalog.h:408
T sort(T... args)
T sqrt(T... args)
std::string sourceFluxField
"name of flux field in source catalog" ;
iterator begin()
Iterator access.
Definition: Catalog.h:396
constexpr double asDegrees() const noexcept
Return an Angle&#39;s value in degrees.
Definition: Angle.h:144
table::Key< int > transform
double allowedNonperpDeg
"allowed non-perpendicularity of x and y axes (deg)" ;
A class to handle Icrs coordinates (inherits from Coord)
Definition: Coord.h:291
int minMatchedPairs
"minimum number of matches" ;
boost::shared_ptr< lsst::afw::table::SimpleRecord > record
T reserve(T... args)
A FunctorKey used to get or set celestial coordinates from a pair of Angle keys.
Definition: aggregates.h:205