lsst.jointcal  16.0-23-gcb65559
ListMatch.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 <cmath>
27 #include <list>
28 #include <memory>
29 #include <algorithm>
30 #ifndef M_PI
31 #define M_PI 3.14159265358979323846 /* pi */
32 #endif
33 
34 #include "lsst/log/Log.h"
35 #include "lsst/jointcal/BaseStar.h"
37 #include "lsst/jointcal/Gtransfo.h"
38 #include "lsst/jointcal/Histo2d.h"
39 #include "lsst/jointcal/Histo4d.h"
42 
43 namespace {
44 LOG_LOGGER _log = LOG_GET("jointcal.ListMatch");
45 }
46 
47 namespace lsst {
48 namespace jointcal {
49 
50 // cuts.. limits, etc for combinatorial match
51 
52 /* a Segment is a pair of stars form the same image. it is used for matching starlists */
53 
54 struct Segment {
55  /* data */
56  double r, dx, dy;
58  int s1rank;
59 
60  /* constructor (could set last argument to identity by default) */
62  const Gtransfo &gtransfo) {
63  s1rank = star1Rank;
64  s1 = std::move(star1);
65  s2 = std::move(star2);
66  Point P1 = gtransfo.apply(*star1);
67  Point P2 = gtransfo.apply(*star2);
68  dx = P2.x - P1.x;
69  dy = P2.y - P1.y;
70  r = sqrt(dx * dx + dy * dy);
71  }
72 
73  /* arg(other/(*this)) if considered as complex(dx,dy) */
74  double relativeAngle(Segment *other) {
75  return atan2(other->dx * dy - dx * other->dy, dx * other->dx + dy * other->dy);
76  }
77 
78  friend std::ostream &operator<<(std::ostream &stream, const Segment &segment) {
79  stream << " dx " << segment.dx << " dy " << segment.dy << " r " << segment.r << std::endl;
80  return stream;
81  }
82 };
83 
84 class SegmentList : public std::list<Segment> {
85 public:
86  // SegmentList(const BaseStarList &list, const int nStar);
87  SegmentList(const BaseStarList &list, const int nStar, const Gtransfo &gtransfo = GtransfoIdentity());
88 };
89 
92 
93 static bool DecreasingLength(const Segment &first, const Segment &second) { return (first.r > second.r); }
94 
95 SegmentList::SegmentList(const BaseStarList &list, const int nStars, const Gtransfo &gtransfo) {
96  BaseStarCIterator siStop;
97 
98  /* find the fence */
99  siStop = list.begin();
100  int limit = std::min(nStars, int(list.size())) - 1; // -1 because test happens after incrementation
101  for (int count = 0; count < limit; count++) ++siStop;
102 
103  // iterate on star pairs
104  int rank = 0;
105  for (auto si1 = list.begin(); si1 != siStop; ++si1, rank++)
106  for (auto si2 = siStop; si2 != si1; --si2) {
107  push_back(Segment(*si1, *si2, rank, gtransfo));
108  }
109  sort(DecreasingLength); /* allows a break in loops */
110 }
111 
112 //#include <pair>
113 
114 struct SegmentPair : public std::pair<Segment *, Segment *> {
115  SegmentPair(Segment *f, Segment *s) : std::pair<Segment *, Segment *>(f, s){};
116 };
117 
119 typedef SegmentPairList::iterator SegmentPairListIterator;
120 typedef SegmentPairList::const_iterator SegmentPairListCIterator;
121 
122 static std::unique_ptr<StarMatchList> MatchListExtract(const SegmentPairList &pairList, int rank1, int rank2,
123  const Gtransfo &gtransfo) {
124  /* first Select in the segment pairs list the ones which make use of star rank1 in segment1
125  and star s2 in segment2 */
126 
128 
129  for (SegmentPairListCIterator spi = pairList.begin(); spi != pairList.end(); spi++) {
130  const SegmentPair &a_pair = *spi;
131  if (a_pair.first->s1rank != rank1 || a_pair.second->s1rank != rank2) continue;
132  /* now we store as star matches both ends of segment pairs ,
133  but only once the beginning of segments because they all have the same,
134  given the selection 3 lines above */
135  if (matchList->size() == 0)
136  matchList->push_back(StarMatch(gtransfo.apply(*(a_pair.first->s1)), *(a_pair.second->s1),
137  a_pair.first->s1, a_pair.second->s1));
138  /* always store the match at end */
139  matchList->push_back(StarMatch(gtransfo.apply(*(a_pair.first->s2)), *(a_pair.second->s2),
140  a_pair.first->s2, a_pair.second->s2));
141  }
142  return matchList;
143 }
144 
145 static bool DecreasingQuality(const std::unique_ptr<StarMatchList> &first,
147  int idiff = first->size() - second->size();
148  if (idiff != 0)
149  return (idiff > 0);
150  else
151  return (first->getDist2() < second->getDist2());
152 }
153 
154 /* many matching solutions (StarMatchList) will be compared. Store them in a SolList : */
155 
157 
158 /* This one searches a general transformation by histogramming the relative size and orientation
159 of star pairs ( Segment's) built from the 2 lists */
160 
161 static std::unique_ptr<StarMatchList> ListMatchupRotShift_Old(BaseStarList &list1, BaseStarList &list2,
162  const Gtransfo &gtransfo,
163  const MatchConditions &conditions) {
164  SegmentList sList1(list1, conditions.nStarsList1, gtransfo);
165  SegmentList sList2(list2, conditions.nStarsList2, GtransfoIdentity());
166 
167  /* choose the binning of the histogram so that
168  1: ratio = 1 and rotation angle = n * (pi/2) are bin centers. since
169  the angle is computed using atan2, its range is [-pi,pi],
170  and the histogram range is [-pi-eps, pi-eps], so
171  if (angle>pi- angleOffset) angle -= 2*pi before filling. */
172  int nBinsR = 21;
173  int nBinsAngle = 180; /* can be divided by 4 */
174  double angleOffset = M_PI / nBinsAngle;
175  double minRatio = conditions.minSizeRatio();
176  double maxRatio = conditions.maxSizeRatio();
177  Histo2d histo(nBinsR, minRatio, maxRatio, nBinsAngle, -M_PI - angleOffset, M_PI - angleOffset);
178 
179  SegmentIterator segi1, segi2;
180  Segment *seg1, *seg2;
181  double ratio, angle;
182  for (segi1 = sList1.begin(); segi1 != sList1.end(); ++segi1) {
183  seg1 = &(*segi1);
184  if (seg1->r == 0) continue;
185  for (segi2 = sList2.begin(); segi2 != sList2.end(); ++segi2) {
186  seg2 = &(*segi2);
187  /* if one considers the 2 segments as complex numbers z1 and z2, ratio=mod(z1/z2) and angle =
188  * arg(z1/z2) */
189  /* I did not put a member function in Segment to compute both because we apply a cut on ratio
190  before actually
191  computing the angle (which involves a call to atan2 (expensive)) */
192  ratio = seg2->r / seg1->r;
193  if (ratio > maxRatio) continue;
194  if (ratio < minRatio) break; /* use th fact that segment lists are sorted by decresing length */
195  angle = seg1->relativeAngle(seg2);
196  if (angle > M_PI - angleOffset) angle -= 2. * M_PI;
197  histo.fill(ratio, angle);
198  }
199  }
200  double binr, bina;
201  histo.binWidth(binr, bina);
202 
203  SolList Solutions;
204  /* now we want to find in the (r,theta) bins that have the highest counts, the star pair
205  (one in l1, one in list2) that contribute to the largest number of segment pairs in this bin :
206  so, we histogram a couple of integer that uniquely defines the stars, for the segment pairs
207  that contribute to the maximum bin. We choose to histogram the rank of s1 of segment 1
208  versus the rank of s1 for segment 2 */
209 
210  for (int i = 0; i < conditions.maxTrialCount; ++i) {
211  double ratioMax, angleMax;
212  double maxContent = histo.maxBin(ratioMax, angleMax);
213  histo.fill(ratioMax, angleMax, -maxContent);
214 
215  if (conditions.printLevel >= 1)
216  LOGLS_DEBUG(_log, " valMax " << maxContent << " ratio " << ratioMax << " angle " << angleMax);
217 
218  minRatio = ratioMax - binr / 2;
219  maxRatio = ratioMax + binr / 2;
220  double minAngle = angleMax - bina / 2;
221  double maxAngle = angleMax + bina / 2;
222  SegmentPairList pairList;
223  Histo2d historank(conditions.nStarsList1, 0., conditions.nStarsList1, conditions.nStarsList2, 0.,
224  conditions.nStarsList2);
225  /* reloop on segment pairs to select the ones in this specific bin */
226 
227  for (segi1 = sList1.begin(); segi1 != sList1.end(); ++segi1) {
228  seg1 = &(*segi1);
229  if (seg1->r == 0) continue;
230  for (segi2 = sList2.begin(); segi2 != sList2.end(); ++segi2) {
231  seg2 = &(*segi2);
232  ratio = seg2->r / seg1->r;
233  if (ratio > maxRatio) continue;
234  if (ratio < minRatio)
235  break; /* use the fact that segment lists are sorted by decresing length */
236  angle = seg1->relativeAngle(seg2);
237  if (angle > M_PI - angleOffset) angle -= 2. * M_PI;
238  if (angle < minAngle || angle > maxAngle) continue;
239  pairList.push_back(SegmentPair(seg1, seg2)); /* store the match */
240  historank.fill(seg1->s1rank + 0.5, seg2->s1rank + 0.5);
241  }
242  }
243  for (int iteration = 0; iteration < conditions.maxTrialCount; iteration++) {
244  double dr1, dr2;
245  double maxval = historank.maxBin(dr1, dr2);
246  /* set this bin to zero so that next iteration will find next maximum */
247  historank.fill(dr1, dr2, -maxval);
248  auto a_list = MatchListExtract(pairList, int(dr1), int(dr2), GtransfoIdentity());
249  a_list->refineTransfo(conditions.nSigmas); // mandatory for the sorting fields to be filled
250  Solutions.push_back(std::move(a_list));
251  }
252  } /* end of loop on (r,theta) bins */
253  Solutions.sort(DecreasingQuality);
255  best.swap(*Solutions.begin());
256  /* remove the first one from the list */
257  Solutions.pop_front();
258  if (conditions.printLevel >= 1) {
259  LOGLS_DEBUG(_log, "Best solution " << best->computeResidual() << " npairs " << best->size());
260  LOGLS_DEBUG(_log, *(best->getTransfo()));
261  LOGLS_DEBUG(_log, "Chi2 " << best->getChi2() << ',' << " Number of solutions " << Solutions.size());
262  }
263  return best;
264 }
265 
266 /* this matching routine searches brutally a match between lists in
267  the 4 parameter space: size ratio, rotation angle, x and y
268  shifts. This is done by histogramming where combinations of four
269  objets (2 on each list) fall in this 4 parameter space.
270 
271  One trick is that rather than using actual offsets, we histogram
272  object indices of the combination:
273 */
274 
275 static std::unique_ptr<StarMatchList> ListMatchupRotShift_New(BaseStarList &list1, BaseStarList &list2,
276  const Gtransfo &gtransfo,
277  const MatchConditions &conditions) {
278  if (list1.size() <= 4 || list2.size() <= 4) {
279  LOGL_FATAL(_log, "ListMatchupRotShift_New : (at least) one of the lists is too short.");
280  return nullptr;
281  }
282 
283  SegmentList sList1(list1, conditions.nStarsList1, gtransfo);
284  SegmentList sList2(list2, conditions.nStarsList2, GtransfoIdentity());
285 
286  /* choose the binning of the histogram so that
287  1: ratio = 1 and rotation angle = n * (pi/2) are bin centers. since
288  the angle is computed using atan2, its range is [-pi,pi],
289  and the histogram range is [-pi-eps, pi-eps], so
290  if (angle>pi- angleOffset) angle -= 2*pi before filling. */
291  int nBinsR = 21;
292  int nBinsAngle = 180; /* can be divided by 4 */
293  double angleOffset = M_PI / nBinsAngle;
294  double minRatio = conditions.minSizeRatio();
295  double maxRatio = conditions.maxSizeRatio();
296  SparseHisto4d histo(nBinsR, minRatio, maxRatio, nBinsAngle, -M_PI - angleOffset, M_PI - angleOffset,
297  conditions.nStarsList1, 0., conditions.nStarsList1, conditions.nStarsList2, 0.,
298  conditions.nStarsList2, sList1.size() * sList2.size());
299 
300  SegmentIterator segi1, segi2;
301  Segment *seg1, *seg2;
302  double ratio, angle;
303 
304  for (segi1 = sList1.begin(); segi1 != sList1.end(); ++segi1) {
305  seg1 = &(*segi1);
306  if (seg1->r == 0) continue;
307  for (segi2 = sList2.begin(); segi2 != sList2.end(); ++segi2) {
308  seg2 = &(*segi2);
309  /* if one considers the 2 segments as complex numbers z1 and z2, ratio=mod(z1/z2) and angle =
310  * arg(z1/z2) */
311  /* I did not put a member function in Segment to compute both because we apply a cut on ratio
312  before actually
313  computing the angle (which involves a call to atan2 (expensive)) */
314  ratio = seg2->r / seg1->r;
315  if (ratio > maxRatio) continue;
316  if (ratio < minRatio) break; /* use th fact that segment lists are sorted by decresing length */
317  angle = seg1->relativeAngle(seg2);
318  if (angle > M_PI - angleOffset) angle -= 2. * M_PI;
319  histo.fill(ratio, angle, seg1->s1rank + 0.5, seg2->s1rank + 0.5);
320  }
321  }
322 
323  SolList Solutions;
324  /* now we find the highest bins of the histogram, and recover the original objects.
325  This involves actually re-looping on the combinations, but it is much
326  faster that the original histogram filling loop, since we only compute
327  angle and ratio for Segments that have the right first object
328  */
329 
330  int oldMaxContent = 0;
331 
332  for (int i = 0; i < 4 * conditions.maxTrialCount;
333  ++i) // leave a limit to make avoid (almost) infinite loops
334  {
335  double pars[4];
336  int maxContent = histo.maxBin(pars);
337  if (maxContent == 0) break;
338  if (conditions.printLevel >= 1) {
339  LOGLS_DEBUG(_log, "ValMax " << maxContent << " ratio " << pars[0] << " angle " << pars[1]);
340  }
341  histo.zeroBin(pars);
342  if (i > 0) { /* the match possibilities come out in a random order when they have the same content.
343  so, we stop investigating guesses when the content goes down AND the requested search
344  depth
345  (maxTrialCount) is reached */
346  if (maxContent < oldMaxContent && i >= conditions.maxTrialCount) break;
347  }
348  oldMaxContent = maxContent;
349  /* reloop on segment pairs to select the ones in this specific bin */
350  int rank1L1 = int(pars[2]);
351  int rank1L2 = int(pars[3]);
352  double minAngle, maxAngle;
353  histo.binLimits(pars, 0, minRatio, maxRatio);
354  histo.binLimits(pars, 1, minAngle, maxAngle);
355 
357 
358  for (segi1 = sList1.begin(); segi1 != sList1.end(); ++segi1) {
359  seg1 = &(*segi1);
360  if (seg1->s1rank != rank1L1) continue;
361  if (seg1->r == 0) continue;
362  for (segi2 = sList2.begin(); segi2 != sList2.end(); ++segi2) {
363  seg2 = &(*segi2);
364  if (seg2->s1rank != rank1L2) continue;
365  // push in the list the match corresponding to end number 1 of segments
366  if (a_list->size() == 0)
367  a_list->push_back(StarMatch(*(seg1->s1), *(seg2->s1), seg1->s1, seg2->s1));
368  ratio = seg2->r / seg1->r;
369  if (ratio > maxRatio) continue;
370  if (ratio < minRatio)
371  break; /* use the fact that segment lists are sorted by decresing length */
372  angle = seg1->relativeAngle(seg2);
373  if (angle > M_PI - angleOffset) angle -= 2. * M_PI;
374  if (angle < minAngle || angle > maxAngle) continue;
375  /* here we have 2 segments which have the right
376  - length ratio
377  - relative angle
378  - first objects (objects on the end number 1).
379  The objects on the end number 2 are the actual matches : */
380  a_list->push_back(StarMatch(*(seg1->s2), *(seg2->s2), seg1->s2, seg2->s2));
381  }
382  }
383 
384  // a basic check for sanity of the algorithm :
385 
386  if (int(a_list->size()) != maxContent + 1) {
387  LOGLS_ERROR(_log, "There is an internal inconsistency in ListMatchupRotShift.");
388  LOGLS_ERROR(_log, "maxContent = " << maxContent);
389  LOGLS_ERROR(_log, "matches->size() = " << a_list->size());
390  }
391  a_list->refineTransfo(conditions.nSigmas);
392  Solutions.push_back(std::move(a_list));
393  }
394 
395  if (Solutions.size() == 0) {
396  LOGLS_ERROR(_log, "Error In ListMatchup : not a single pair match.");
397  LOGLS_ERROR(_log, "Probably, the relative scale of lists is not within bounds.");
398  LOGLS_ERROR(_log, "min/max ratios: " << minRatio << ' ' << maxRatio);
399  return nullptr;
400  }
401 
402  Solutions.sort(DecreasingQuality);
404  best.swap(*Solutions.begin());
405  /* remove the first one from the list */
406  Solutions.pop_front();
407  if (conditions.printLevel >= 1) {
408  LOGLS_INFO(_log, "Best solution " << best->computeResidual() << " npairs " << best->size());
409  LOGLS_INFO(_log, *(best->getTransfo()));
410  LOGLS_INFO(_log, "Chi2 " << best->getChi2() << ", Number of solutions " << Solutions.size());
411  }
412  return best;
413 }
414 
415 static std::unique_ptr<StarMatchList> ListMatchupRotShift(BaseStarList &list1, BaseStarList &list2,
416  const Gtransfo &gtransfo,
417  const MatchConditions &conditions) {
418  if (conditions.algorithm == 1)
419  return ListMatchupRotShift_Old(list1, list2, gtransfo, conditions);
420  else
421  return ListMatchupRotShift_New(list1, list2, gtransfo, conditions);
422 }
423 
425  const MatchConditions &conditions) {
426  list1.fluxSort();
427  list2.fluxSort();
428 
429  return ListMatchupRotShift(list1, list2, GtransfoIdentity(), conditions);
430 }
431 
433  const MatchConditions &conditions) {
434  list1.fluxSort();
435  list2.fluxSort();
436 
437  GtransfoLin flip(0, 0, 1, 0, 0, -1);
438  std::unique_ptr<StarMatchList> flipped(ListMatchupRotShift(list1, list2, flip, conditions));
440  ListMatchupRotShift(list1, list2, GtransfoIdentity(), conditions));
441  if (!flipped || !unflipped) return std::unique_ptr<StarMatchList>(nullptr);
442  if (conditions.printLevel >= 1) {
443  LOGLS_DEBUG(_log,
444  "unflipped Residual " << unflipped->computeResidual() << " nused " << unflipped->size());
445  LOGLS_DEBUG(_log, "flipped Residual " << flipped->computeResidual() << " nused " << flipped->size());
446  }
447  if (DecreasingQuality(flipped, unflipped)) {
448  if (conditions.printLevel >= 1) LOGL_DEBUG(_log, "Keeping flipped solution.");
449  // One should NOT apply the flip to the result because the matchlist
450  // (even the flipped one) contains the actual coordinates of stars.
451  // MatchListExtract is always called with GtransfoIdentity() as last parameter
452  return flipped;
453  } else {
454  if (conditions.printLevel >= 1) LOGL_DEBUG(_log, "Keeping unflipped solution.");
455  return unflipped;
456  }
457 }
458 
459 #ifdef STORAGE
460 // timing : 2.5 s for l1 of 1862 objects and l2 of 2617 objects
462  const Gtransfo &gtransfo, double maxShift) {
463  int ncomb = list1.size() * list2.size();
464  if (!ncomb) return nullptr;
465  int nx;
466  if (ncomb > 10000)
467  nx = 100;
468  else
469  nx = (int)sqrt(ncomb);
470 
471  Histo2d histo(nx, -maxShift, maxShift, nx, -maxShift, maxShift);
472 
474  double x1, y1;
475  for (s1 = list1.begin(); s1 != list1.end(); ++s1) {
476  gtransfo.apply((*s1)->x, (*s1)->y, x1, y1);
477  for (s2 = list2.begin(); s2 != list2.end(); ++s2) {
478  histo.fill((*s2)->x - x1, (*s2)->y - y1);
479  }
480  }
481  double dx = 0, dy = 0;
482  histo.maxBin(dx, dy);
484 }
485 #endif /*STORAGE*/
486 
487 // timing : 140 ms for l1 of 1862 objects and l2 of 2617 objects (450 MHz, "-O4") maxShift = 200.
489  const Gtransfo &gtransfo, double maxShift, double binSize) {
490  int nx;
491  if (binSize == 0) {
492  int ncomb = list1.size() * list2.size();
493  if (ncomb > 10000)
494  nx = 100;
495  else
496  nx = (int)sqrt(double(ncomb));
497  if (!ncomb) return std::unique_ptr<GtransfoLin>(nullptr);
498  } else
499  nx = int(2 * maxShift / binSize + 0.5);
500 
501  Histo2d histo(nx, -maxShift, maxShift, nx, -maxShift, maxShift);
502  double binSizeNew = 2 * maxShift / nx;
503 
505  FastFinder finder(list2);
506  double x1, y1;
507  for (s1 = list1.begin(); s1 != list1.end(); ++s1) {
508  gtransfo.apply((*s1)->x, (*s1)->y, x1, y1);
509  FastFinder::Iterator it = finder.beginScan(Point(x1, y1), maxShift);
510  while (*it) {
511  auto s2 = *it;
512  histo.fill(s2->x - x1, s2->y - y1);
513  ++it;
514  }
515  }
516  SolList Solutions;
517  for (int i = 0; i < 4; ++i) {
518  double dx = 0, dy = 0;
519  double count = histo.maxBin(dx, dy);
520  histo.fill(dx, dy, -count); // zero the maxbin
521  GtransfoLinShift shift(dx, dy);
522  auto newGuess = gtransfoCompose(shift, gtransfo);
523  auto raw_matches = listMatchCollect(list1, list2, newGuess.get(), binSizeNew);
525  raw_matches->applyTransfo(*matches, &gtransfo);
526  matches->setTransfoOrder(1);
527  matches->refineTransfo(3.);
528  Solutions.push_back(std::move(matches));
529  }
530  Solutions.sort(DecreasingQuality);
531  std::unique_ptr<GtransfoLin> best(new GtransfoLin(*std::const_pointer_cast<GtransfoLin>(
532  std::dynamic_pointer_cast<const GtransfoLin>(Solutions.front()->getTransfo()))));
533  return best;
534 }
535 
536 #ifdef STORAGE
537 
538 // this is the old fashioned way...
539 
540 std::unique_ptr<StarMatchList> listMatchCollect_Slow(const BaseStarList &list1, const BaseStarList &list2,
541  const Gtransfo *guess, const double maxDist) {
543  /****** Collect ***********/
544  for (BaseStarCIterator si = list1.begin(); si != list1.end(); ++si) {
545  const Point *p1 = (*si);
546  const Point p2 = guess->apply(*p1);
547  const BaseStar *neighbour = list2.findClosest(p2);
548  if (!neighbour) continue;
549  double distance = p2.Distance(*neighbour);
550  if (distance < maxDist) {
551  matches->push_back(StarMatch(*p1, *neighbour, *si, neighbour));
552  // assign the distance, since we have it in hand:
553  matches->back().distance = distance;
554  }
555  }
556  return matches;
557 }
558 #endif
559 
560 // here is the real active routine:
561 
563  const Gtransfo *guess, const double maxDist) {
565  /****** Collect ***********/
566  FastFinder finder(list2);
567  for (BaseStarCIterator si = list1.begin(); si != list1.end(); ++si) {
568  auto p1 = (*si);
569  Point p2 = guess->apply(*p1);
570  auto neighbour = finder.findClosest(p2, maxDist);
571  if (!neighbour) continue;
572  double distance = p2.Distance(*neighbour);
573  if (distance < maxDist) {
574  matches->push_back(StarMatch(*p1, *neighbour, p1, neighbour));
575  // assign the distance, since we have it in hand:
576  matches->back().distance = distance;
577  }
578  }
579  matches->setTransfo(guess);
580 
581  return matches;
582 }
583 
584 #ifdef STORAGE
585 // unused
587 std::unique_ptr<StarMatchList> CollectAndFit(const BaseStarList &list1, const BaseStarList &list2,
588  const Gtransfo *guess, const double maxDist) {
589  const Gtransfo *bestTransfo = guess;
591  while (true) {
592  auto m = listMatchCollect(list1, list2, bestTransfo, maxDist);
593  m->setTransfo(bestTransfo);
594  m->refineTransfo(3.);
595  LOGLS_INFO(_log, "Iterating: resid " << m->computeResidual() << " size " << m->size());
596  if (!prevMatch ||
597  (prevMatch && m->computeResidual() < prevMatch->computeResidual() * 0.999 && m->Chi2() > 0)) {
598  prevMatch.swap(m);
599  bestTransfo = prevMatch->Transfo();
600  } else {
601  break;
602  }
603  }
604  return prevMatch;
605 }
606 #endif
607 
609  const double maxDist) {
611  FastFinder finder(list2);
612  for (BaseStarCIterator si = list1.begin(); si != list1.end(); ++si) {
613  auto p1 = (*si);
614  auto neighbour = finder.findClosest(*p1, maxDist);
615  if (!neighbour) continue;
616  double distance = p1->Distance(*neighbour);
617  if (distance < maxDist) {
618  matches->push_back(StarMatch(*p1, *neighbour, p1, neighbour));
619  // assign the distance, since we have it in hand:
620  matches->back().distance = distance;
621  }
622  }
623 
624  matches->setTransfo(std::make_shared<GtransfoIdentity>());
625 
626  return matches;
627 }
628 
629 static bool is_transfo_ok(const StarMatchList *match, double pixSizeRatio2, const size_t nmin) {
630  if ((fabs(fabs(std::dynamic_pointer_cast<const GtransfoLin>(match->getTransfo())->determinant()) -
631  pixSizeRatio2) /
632  pixSizeRatio2 <
633  0.2) &&
634  (match->size() > nmin))
635  return true;
636  LOGL_ERROR(_log, "transfo is not ok!");
637  match->dumpTransfo();
638  return false;
639 }
640 
641 // utility to check current transfo difference
642 static double transfo_diff(const BaseStarList &List, const Gtransfo *T1, const Gtransfo *T2) {
643  double diff2 = 0;
644  FatPoint tf1;
645  Point tf2;
646  int count = 0;
647  for (BaseStarCIterator it = List.begin(); it != List.end(); ++it) {
648  const BaseStar &s = **it;
649  T1->transformPosAndErrors(s, tf1);
650  T2->apply(s, tf2);
651  double dx = tf1.x - tf2.x;
652  double dy = tf1.y - tf2.y;
653  diff2 += (tf1.vy * dx * dx + tf1.vx * dy * dy - 2 * tf1.vxy * dx * dy) /
654  (tf1.vx * tf1.vy - tf1.vxy * tf1.vxy);
655  count++;
656  }
657  if (count) return diff2 / double(count);
658  return 0;
659 }
660 
661 static double median_distance(const StarMatchList *match, const Gtransfo *transfo) {
662  size_t nstars = match->size();
663  std::vector<double> resid(nstars);
665  for (auto it = match->begin(); it != match->end(); ++it, ++ir)
666  *ir = sqrt(transfo->apply(it->point1).computeDist2(it->point2));
667  sort(resid.begin(), resid.end());
668  return (nstars & 1) ? resid[nstars / 2] : (resid[nstars / 2 - 1] + resid[nstars / 2]) * 0.5;
669 }
670 
672  const MatchConditions &conditions) {
673  BaseStarList list1, list2;
674  List1.copyTo(list1);
675  list1.fluxSort();
676  List2.copyTo(list2);
677  list2.fluxSort();
678 
679  LOGLS_INFO(_log, "listMatchCombinatorial: find match between " << list1.size() << " and " << list2.size()
680  << " stars...");
681  auto match = matchSearchRotShiftFlip(list1, list2, conditions);
682  double pixSizeRatio2 = std::pow(conditions.sizeRatio, 2);
683  size_t nmin =
684  std::min(size_t(10), size_t(std::min(List1.size(), List2.size()) * conditions.minMatchRatio));
685 
687  if (is_transfo_ok(match.get(), pixSizeRatio2, nmin))
688  transfo = match->getTransfo()->clone();
689  else {
690  LOGL_ERROR(_log, "listMatchCombinatorial: direct transfo failed, trying reverse");
691  match = matchSearchRotShiftFlip(list2, list1, conditions);
692  if (is_transfo_ok(match.get(), pixSizeRatio2, nmin))
693  transfo = match->inverseTransfo();
694  else {
695  LOGL_FATAL(_log, "FAILED");
696  }
697  }
698 
699  if (transfo) {
700  LOGL_INFO(_log, "FOUND");
701  if (conditions.printLevel >= 1) {
702  LOGL_DEBUG(_log, " listMatchCombinatorial: found the following transfo.");
703  LOGLS_DEBUG(_log, *transfo);
704  }
705  } else
706  LOGL_ERROR(_log, "listMatchCombinatorial: failed to find a transfo");
707  return transfo;
708 }
709 
711  std::unique_ptr<Gtransfo> transfo, const int maxOrder) {
712  if (!transfo) {
713  return std::unique_ptr<Gtransfo>(nullptr);
714  }
715 
716  // some hard-coded constants that could go in a param file
717  const double brightDist = 2.; // distance in pixels in a match
718  const double fullDist = 4.; // distance in pixels in a match between entire lists
719  const double nSigmas = 3.; // k-sigma clipping on residuals
720  const size_t nStars = 500; // max number of bright stars to fit
721 
722  int order = 1;
723  size_t nstarmin = 3;
724 
725  BaseStarList list1, list2;
726  List1.copyTo(list1);
727  list1.fluxSort();
728  list1.cutTail(nStars);
729  List2.copyTo(list2);
730  list2.fluxSort();
731  list2.cutTail(nStars);
732 
733  auto fullMatch = listMatchCollect(List1, List2, transfo.get(), fullDist);
734  auto brightMatch = listMatchCollect(list1, list2, transfo.get(), brightDist);
735  double curChi2 = computeChi2(*brightMatch, *transfo) / brightMatch->size();
736 
737  LOGLS_INFO(_log, "listMatchRefine: start: med.resid " << median_distance(fullMatch.get(), transfo.get())
738  << " #match " << fullMatch->size());
739 
740  do { // loop on transfo order on full list of stars
741  auto curTransfo = brightMatch->getTransfo()->clone();
742  unsigned iter = 0;
743  double transDiff;
744  do { // loop on transfo diff only on bright stars
745  brightMatch->setTransfoOrder(order);
746  brightMatch->refineTransfo(nSigmas);
747  transDiff = transfo_diff(list1, brightMatch->getTransfo().get(), curTransfo.get());
748  curTransfo = brightMatch->getTransfo()->clone();
749  brightMatch = listMatchCollect(list1, list2, curTransfo.get(), brightDist);
750  } while (brightMatch->size() > nstarmin && transDiff > 0.05 && ++iter < 5);
751 
752  double prevChi2 = curChi2;
753  curChi2 = computeChi2(*brightMatch, *curTransfo) / brightMatch->size();
754 
755  fullMatch = listMatchCollect(List1, List2, curTransfo.get(), fullDist);
756  LOGLS_INFO(_log, "listMatchRefine: order " << order << " med.resid "
757  << median_distance(fullMatch.get(), curTransfo.get())
758  << " #match " << fullMatch->size());
759  if (((prevChi2 - curChi2) > 0.01 * curChi2) && curChi2 > 0) {
760  LOGLS_INFO(_log, " listMatchRefine: order " << order << " was a better guess.");
761  transfo = brightMatch->getTransfo()->clone();
762  }
763  nstarmin = brightMatch->getTransfo()->getNpar();
764  } while (++order <= maxOrder);
765 
766  return transfo;
767 }
768 } // namespace jointcal
769 } // namespace lsst
#define LOGL_ERROR(logger, message...)
implements the linear transformations (6 real coefficients).
Definition: Gtransfo.h:414
BaseStarList::const_iterator BaseStarCIterator
Definition: BaseStar.h:121
std::shared_ptr< const BaseStar > s1
Definition: ListMatch.cc:57
void binWidth(double &Hdx, double &Hdy) const
Definition: Histo2d.h:42
SegmentPair(Segment *f, Segment *s)
Definition: ListMatch.cc:115
A hanger for star associations.
Definition: StarMatch.h:54
A point in a plane.
Definition: Point.h:36
std::shared_ptr< const BaseStar > s2
Definition: ListMatch.cc:57
std::list< Segment >::const_iterator SegmentCIterator
Definition: ListMatch.cc:91
T swap(T... args)
T front(T... args)
void fill(float x, float y, float weight=1.)
Definition: Histo2d.cc:73
T endl(T... args)
A class to histogram in 4 dimensions.
Definition: Histo4d.h:33
STL namespace.
std::unique_ptr< Gtransfo > listMatchCombinatorial(const BaseStarList &list1, const BaseStarList &list2, const MatchConditions &conditions=MatchConditions())
Definition: ListMatch.cc:671
T end(T... args)
SegmentList(const BaseStarList &list, const int nStar, const Gtransfo &gtransfo=GtransfoIdentity())
Definition: ListMatch.cc:95
double Distance(const Point &other) const
Definition: Point.h:50
SegmentPairList::const_iterator SegmentPairListCIterator
Definition: ListMatch.cc:120
A Point with uncertainties.
Definition: FatPoint.h:34
void cutTail(const int nKeep)
cuts the end of the std::list
Definition: StarList.cc:48
#define M_PI
Definition: ListMatch.cc:31
std::unique_ptr< StarMatchList > matchSearchRotShift(BaseStarList &list1, BaseStarList &list2, const MatchConditions &conditions)
searches a geometrical transformation that goes from list1 to list2.
Definition: ListMatch.cc:424
T atan2(T... args)
#define LOGLS_INFO(logger, message)
double maxBin(double &x, double &y) const
Definition: Histo2d.cc:78
double x
coordinate
Definition: Point.h:41
T min(T... args)
pairs of points
The base class for handling stars. Used by all matching routines.
Definition: BaseStar.h:50
std::shared_ptr< const BaseStar > findClosest(const Point &where, const double maxDist, bool(*SkipIt)(const BaseStar &)=nullptr) const
Find the closest with some rejection capability.
Definition: FastFinder.cc:84
std::unique_ptr< StarMatchList > matchSearchRotShiftFlip(BaseStarList &list1, BaseStarList &list2, const MatchConditions &conditions)
same as above but searches also a flipped solution.
Definition: ListMatch.cc:432
T push_back(T... args)
first
std::lists of Stars.
Definition: StarList.h:58
std::shared_ptr< const Gtransfo > getTransfo() const
carries out a fit with outlier rejection
Definition: StarMatch.h:164
std::list< Segment >::iterator SegmentIterator
Definition: ListMatch.cc:90
Class for a simple mapping implementing a generic Gtransfo.
void dumpTransfo(std::ostream &stream=std::cout) const
print the matching transformation quality (transfo, chi2, residual)
Definition: StarMatch.cc:227
#define LOGL_FATAL(logger, message...)
std::unique_ptr< Gtransfo > listMatchRefine(const BaseStarList &list1, const BaseStarList &list2, std::unique_ptr< Gtransfo > transfo, const int maxOrder=3)
Definition: ListMatch.cc:710
double relativeAngle(Segment *other)
Definition: ListMatch.cc:74
Iterator beginScan(const Point &where, double maxDist) const
Definition: FastFinder.cc:175
double computeChi2(const StarMatchList &L, const Gtransfo &gtransfo)
the actual chi2
Definition: StarMatch.cc:243
std::unique_ptr< StarMatchList > listMatchCollect(const BaseStarList &list1, const BaseStarList &list2, const Gtransfo *guess, const double maxDist)
assembles star matches.
Definition: ListMatch.cc:562
std::list< SegmentPair > SegmentPairList
Definition: ListMatch.cc:118
virtual void transformPosAndErrors(const FatPoint &in, FatPoint &out) const
Definition: Gtransfo.cc:140
Iterator meant to traverse objects within some limiting distance.
Definition: FastFinder.h:90
STL class.
T fabs(T... args)
T move(T... args)
T count(T... args)
T get(T... args)
#define LOGL_INFO(logger, message...)
void copyTo(StarList< Star > &copy) const
clears copy and makes a copy of the std::list to copy
Definition: StarList.cc:68
A do-nothing transformation. It anyway has dummy routines to mimick a Gtransfo.
Definition: Gtransfo.h:219
second
T size(T... args)
just here to provide a specialized constructor, and fit.
Definition: Gtransfo.h:478
STL class.
distance
void fluxSort()
a model routine to sort the std::list
Definition: StarList.cc:42
T begin(T... args)
T pow(T... args)
This is an auxillary class for matching objects from starlists.
Definition: FastFinder.h:54
table::Key< double > angle
Combinatorial searches for linear transformations to go from list1 to list2.
a virtual (interface) class for geometric transformations.
Definition: Gtransfo.h:65
#define LOGLS_DEBUG(logger, message)
#define LOGL_DEBUG(logger, message...)
friend std::ostream & operator<<(std::ostream &stream, const Segment &segment)
Definition: ListMatch.cc:78
T sort(T... args)
T sqrt(T... args)
m
Segment(std::shared_ptr< const BaseStar > star1, std::shared_ptr< const BaseStar > star2, const int star1Rank, const Gtransfo &gtransfo)
Definition: ListMatch.cc:61
T pop_front(T... args)
Parameters to be provided to combinatorial searches.
Definition: ListMatch.h:40
virtual std::unique_ptr< Gtransfo > clone() const =0
returns a copy (allocated by new) of the transformation.
Fast locator in starlists.
STL class.
#define LOG_GET(logger)
virtual void apply(const double xIn, const double yIn, double &xOut, double &yOut) const =0
SegmentPairList::iterator SegmentPairListIterator
Definition: ListMatch.cc:119
#define LOGLS_ERROR(logger, message)
std::unique_ptr< GtransfoLin > listMatchupShift(const BaseStarList &list1, const BaseStarList &list2, const Gtransfo &gtransfo, double maxShift, double binSize=0)
searches for a 2 dimensional shift using a very crude histogram method.
Definition: ListMatch.cc:488
std::unique_ptr< Gtransfo > gtransfoCompose(Gtransfo const &left, Gtransfo const &right)
Returns a pointer to a composition of gtransfos, representing left(right()).
Definition: Gtransfo.cc:408