lsst.meas.algorithms  13.0-24-g22030a45+14
CR.cc
Go to the documentation of this file.
1 // -*- LSST-C++ -*-
2 
3 /*
4  * LSST Data Management System
5  * Copyright 2008-2016 AURA/LSST.
6  *
7  * This product includes software developed by the
8  * LSST Project (http://www.lsst.org/).
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the LSST License Statement and
21  * the GNU General Public License along with this program. If not,
22  * see <https://www.lsstcorp.org/LegalNotices/>.
23  */
24 
32 #include <stdexcept>
33 #include <algorithm>
34 #include <cassert>
35 #include <string>
36 #include <typeinfo>
37 
38 #include <iostream>
39 
40 
41 #include "boost/format.hpp"
42 
43 #include "lsst/pex/exceptions.h"
44 #include "lsst/log/Log.h"
45 #include "lsst/pex/exceptions.h"
47 #include "lsst/afw/geom.h"
48 #include "lsst/afw/detection/Psf.h"
50 #include "lsst/afw/math/Random.h"
53 
58 namespace lsst {
59 namespace afw {
60 namespace detection {
64 class IdSpan {
65 public:
68 
69  explicit IdSpan(int id, int y, int x0, int x1) : id(id), y(y), x0(x0), x1(x1) {}
70  int id; /* ID for object */
71  int y; /* Row wherein IdSpan dwells */
72  int x0, x1; /* inclusive range of columns */
73 };
77 struct IdSpanCompar : public std::binary_function<const IdSpan::ConstPtr, const IdSpan::ConstPtr, bool> {
79  if (a->id < b->id) {
80  return true;
81  } else if(a->id > b->id) {
82  return false;
83  } else {
84  if (a->y < b->y) {
85  return true;
86  } else if (a->y > b->y) {
87  return false;
88  } else {
89  return (a->x0 < b->x0) ? true : false;
90  }
91  }
92  }
93 };
97 int resolve_alias(const std::vector<int>& aliases, /* list of aliases */
98  int id) { /* alias to look up */
99  int resolved = id; /* resolved alias */
100 
101  while (id != aliases[id]) {
102  resolved = id = aliases[id];
103  }
104 
105  return(resolved);
106 }
107 }}} // namespace lsst::afw::detection
108 
109 namespace lsst {
110 namespace meas {
111 namespace algorithms {
112 
113 namespace geom = lsst::afw::geom;
114 namespace math = lsst::afw::math;
115 namespace image = lsst::afw::image;
116 namespace detection = lsst::afw::detection;
117 
118 namespace {
119 
120 template<typename ImageT, typename MaskT>
122  double const bkgd, MaskT const , MaskT const saturBit, MaskT const badMask,
123  bool const debias, bool const grow);
124 
125 template<typename ImageT>
126 bool condition_3(ImageT *estimate, double const peak,
127  double const mean_ns, double const mean_we, double const mean_swne, double const mean_nwse,
128  double const dpeak,
129  double const dmean_ns, double const dmean_we,double const dmean_swne,double const dmean_nwse,
130  double const thresH, double const thresV, double const thresD,
131  double const cond3Fac
132  );
133 
134 /************************************************************************************************************/
135 //
136 // A class to hold a detected pixel
137 template<typename ImageT>
138 struct CRPixel {
139  typedef typename std::shared_ptr<CRPixel> Ptr;
140 
141  CRPixel(int _col, int _row, ImageT _val, int _id = -1) :
142  id(_id), col(_col), row(_row), val(_val) {
143  _i = ++i;
144  }
145  ~CRPixel() {}
146 
147  bool operator< (const CRPixel& a) const {
148  return _i < a._i;
149  }
150 
151  int get_i() const {
152  return _i;
153  }
154 
155  int id; // Unique ID for cosmic ray (not cosmic ray pixel)
156  int col; // position
157  int row; // of pixel
158  ImageT val; // initial value of pixel
159 private:
160  static int i; // current value of running index
161  int mutable _i; // running index
162 };
163 
164 template<typename ImageT>
165 int CRPixel<ImageT>::i = 0;
166 
167 template<typename ImageT>
168 struct Sort_CRPixel_by_id {
169  bool operator() (CRPixel<ImageT> const & a, CRPixel<ImageT> const & b) const {
170  return a.id < b.id;
171  }
172 };
173 
174 /*****************************************************************************/
175 /*
176  * This is the code to see if a given pixel is bad
177  *
178  * Note that the pixel in question is at index 0, so its value is pt_0[0]
179  */
180 template <typename MaskedImageT>
181 bool is_cr_pixel(typename MaskedImageT::Image::Pixel *corr, // corrected value
182  typename MaskedImageT::xy_locator loc, // locator for this pixel
183  double const minSigma, // minSigma, or -threshold if negative
184  double const thresH, double const thresV, double const thresD, // for condition #3
185  double const bkgd, // unsubtracted background level
186  double const cond3Fac // fiddle factor for condition #3
187  )
188 {
189  typedef typename MaskedImageT::Image::Pixel ImagePixel;
190  //
191  // Unpack some values
192  //
193  ImagePixel const v_00 = loc.image(0, 0);
194 
195  if (v_00 < 0) {
196  return false;
197  }
198  /*
199  * condition #1 is not applied on a pixel-by-pixel basis
200  */
201  ;
202  /*
203  * condition #2
204  */
205  ImagePixel const mean_we = (loc.image(-1, 0) + loc.image( 1, 0))/2; // avgs of surrounding 8 pixels
206  ImagePixel const mean_ns = (loc.image( 0, 1) + loc.image( 0, -1))/2;
207  ImagePixel const mean_swne = (loc.image(-1, -1) + loc.image( 1, 1))/2;
208  ImagePixel const mean_nwse = (loc.image(-1, 1) + loc.image( 1, -1))/2;
209 
210  if (minSigma < 0) { /* |thres_sky_sigma| is threshold */
211  if (v_00 < -minSigma) {
212  return false;
213  }
214  } else {
215  double const thres_sky_sigma = minSigma*sqrt(loc.variance(0, 0));
216 
217  if (v_00 < mean_ns + thres_sky_sigma &&
218  v_00 < mean_we + thres_sky_sigma &&
219  v_00 < mean_swne + thres_sky_sigma &&
220  v_00 < mean_nwse + thres_sky_sigma) {
221  return false;
222  }
223  }
224 /*
225  * condition #3
226  *
227  * Note that this uses mean_ns etc. even if minSigma is negative
228  */
229  double const dv_00 = sqrt(loc.variance( 0, 0));
230  // standard deviation of means of surrounding pixels
231  double const dmean_we = sqrt(loc.variance(-1, 0) + loc.variance( 1, 0))/2;
232  double const dmean_ns = sqrt(loc.variance( 0, 1) + loc.variance( 0, -1))/2;
233  double const dmean_swne = sqrt(loc.variance(-1, -1) + loc.variance( 1, 1))/2;
234  double const dmean_nwse = sqrt(loc.variance(-1, 1) + loc.variance( 1, -1))/2;
235 
236  if (!condition_3(corr,
237  v_00 - bkgd, mean_ns - bkgd, mean_we - bkgd, mean_swne - bkgd, mean_nwse - bkgd,
238  dv_00, dmean_ns, dmean_we, dmean_swne, dmean_nwse,
239  thresH, thresV, thresD, cond3Fac)){
240  return false;
241  }
242 /*
243  * OK, it's a contaminated pixel
244  */
245  *corr += static_cast<ImagePixel>(bkgd);
246 
247  return true;
248 }
249 
250 /************************************************************************************************************/
251 //
252 // Worker routine to process the pixels adjacent to a span (including the points just
253 // to the left and just to the right)
254 //
255 template <typename MaskedImageT>
256 void checkSpanForCRs(detection::Footprint *extras, // Extra spans get added to this Footprint
257  std::vector<CRPixel<typename MaskedImageT::Image::Pixel> >& crpixels,
258  // a list of pixels containing CRs
259  int const y, // the row to process
260  int const x0, int const x1, // range of pixels in the span (inclusive)
261  MaskedImageT& image,
262  double const minSigma, // minSigma
263  double const thresH, double const thresV, double const thresD, // for cond. #3
264  double const bkgd, // unsubtracted background level
265  double const cond3Fac, // fiddle factor for condition #3
266  bool const keep // if true, don't remove the CRs
267  )
268 {
269  typedef typename MaskedImageT::Image::Pixel MImagePixel;
270  typename MaskedImageT::xy_locator loc = image.xy_at(x0 - 1, y); // locator for data
271 
272  int const imageX0 = image.getX0();
273  int const imageY0 = image.getY0();
274 
275  for (int x = x0 - 1; x <= x1 + 1; ++x) {
276  MImagePixel corr = 0; // new value for pixel
277  if (is_cr_pixel<MaskedImageT>(&corr, loc, minSigma, thresH, thresV, thresD,
278  bkgd, cond3Fac)) {
279  if (keep) {
280  crpixels.push_back(CRPixel<MImagePixel>(x + imageX0, y + imageY0, loc.image()));
281  }
282  loc.image() = corr;
283 
284  std::vector<geom::Span> spanList(extras->getSpans()->begin(),
285  extras->getSpans()->end());
286  spanList.push_back(geom::Span(y + imageY0, x + imageX0, x +imageX0));
287  extras->setSpans(std::make_shared<geom::SpanSet>(std::move(spanList)));
288  }
289  ++loc.x();
290  }
291 }
292 
293 /************************************************************************************************************/
294 /*
295  * Find the sum of the pixels in a Footprint
296  */
297 template <typename ImageT>
298 class CountsInCR {
299 public:
300  CountsInCR(double const bkgd) :
301  _bkgd(bkgd),
302  _sum(0.0) {}
303 
304  void operator()(geom::Point2I const & point, ImageT const & val){
305  _sum += val - _bkgd;
306  }
307 
308  virtual void reset(detection::Footprint const&) {}
309  virtual void reset() {
310  _sum = 0.0;
311  }
312 
313  double getCounts() const { return _sum; }
314 private:
315  double const _bkgd; // the Image's background level
316  ImageT _sum; // the sum of all DN in the Footprint, corrected for bkgd
317 };
318 }
319 
320 /*
321  * Restore all the pixels in crpixels to their pristine state
322  */
323 template <typename ImageT>
324 static void reinstateCrPixels(
325  ImageT *image, // the image in question
326  std::vector<CRPixel<typename ImageT::Pixel> > const& crpixels // a list of pixels with CRs
327  )
328 {
329  if (crpixels.empty()) return;
330 
331  typedef typename std::vector<CRPixel<typename ImageT::Pixel> >::const_iterator crpixel_iter;
332  for (crpixel_iter crp = crpixels.begin(), end = crpixels.end(); crp < end - 1 ; ++crp) {
333  *image->at(crp->col - image->getX0(), crp->row - image->getY0()) = crp->val;
334  }
335 }
336 
342 template <typename MaskedImageT>
344 findCosmicRays(MaskedImageT &mimage,
345  detection::Psf const &psf,
346  double const bkgd,
347  lsst::pex::policy::Policy const &policy,
348  bool const keep
349  ) {
350  typedef typename MaskedImageT::Image ImageT;
351  typedef typename ImageT::Pixel ImagePixel;
352  typedef typename MaskedImageT::Mask::Pixel MaskPixel;
353 
354  // Parse the Policy
355  double const minSigma = policy.getDouble("minSigma"); // min sigma over sky in pixel for CR candidate
356  double const minDn = policy.getDouble("min_DN"); // min number of DN in an CRs
357  double const cond3Fac = policy.getDouble("cond3_fac"); // fiddle factor for condition #3
358  double const cond3Fac2 = policy.getDouble("cond3_fac2"); // 2nd fiddle factor for condition #3
359  int const niteration = policy.getInt("niteration"); // Number of times to look for contaminated
360  // pixels near CRs
361  int const nCrPixelMax = policy.getInt("nCrPixelMax"); // maximum number of contaminated pixels
362 /*
363  * thresholds for 3rd condition
364  *
365  * Realise PSF at center of image
366  */
368  if (!kernel) {
369  throw LSST_EXCEPT(pexExcept::NotFoundError, "Psf is unable to return a kernel");
370  }
371  detection::Psf::Image psfImage = detection::Psf::Image(geom::ExtentI(kernel->getWidth(), kernel->getHeight()));
372  kernel->computeImage(psfImage, true);
373 
374  int const xc = kernel->getCtrX(); // center of PSF
375  int const yc = kernel->getCtrY();
376 
377  double const I0 = psfImage(xc, yc);
378  double const thresH = cond3Fac2*(0.5*(psfImage(xc - 1, yc) + psfImage(xc + 1, yc)))/I0; // horizontal
379  double const thresV = cond3Fac2*(0.5*(psfImage(xc, yc - 1) + psfImage(xc, yc + 1)))/I0; // vertical
380  double const thresD = cond3Fac2*(0.25*(psfImage(xc - 1, yc - 1) + psfImage(xc + 1, yc + 1) +
381  psfImage(xc - 1, yc + 1) + psfImage(xc + 1, yc - 1)))/I0; // diag
382 /*
383  * Setup desired mask planes
384  */
385  MaskPixel const badBit = mimage.getMask()->getPlaneBitMask("BAD"); // Generic bad pixels
386  MaskPixel const crBit = mimage.getMask()->getPlaneBitMask("CR"); // CR-contaminated pixels
387  MaskPixel const interpBit = mimage.getMask()->getPlaneBitMask("INTRP"); // Interpolated pixels
388  MaskPixel const saturBit = mimage.getMask()->getPlaneBitMask("SAT"); // Saturated pixels
389  MaskPixel const nodataBit = mimage.getMask()->getPlaneBitMask("NO_DATA"); // Non data pixels
390 
391  MaskPixel const badMask = (badBit | interpBit | saturBit | nodataBit); // naughty pixels
392 /*
393  * Go through the frame looking at each pixel (except the edge ones which we ignore)
394  */
395  int const ncol = mimage.getWidth();
396  int const nrow = mimage.getHeight();
397 
398  std::vector<CRPixel<ImagePixel> > crpixels; // storage for detected CR-contaminated pixels
399  typedef typename std::vector<CRPixel<ImagePixel> >::iterator crpixel_iter;
400  typedef typename std::vector<CRPixel<ImagePixel> >::reverse_iterator crpixel_riter;
401 
402  for (int j = 1; j < nrow - 1; ++j) {
403  typename MaskedImageT::xy_locator loc = mimage.xy_at(1, j); // locator for data
404 
405  for (int i = 1; i < ncol - 1; ++i, ++loc.x()) {
406  ImagePixel corr = 0;
407  if (!is_cr_pixel<MaskedImageT>(&corr, loc, minSigma,
408  thresH, thresV, thresD, bkgd, cond3Fac)) {
409  continue;
410  }
411 /*
412  * condition #4
413  */
414  if (loc.mask() & badMask) {
415  continue;
416  }
417  if ((loc.mask(-1, 1) | loc.mask(0, 1) | loc.mask(1, 1) |
418  loc.mask(-1, 0) | loc.mask(1, 0) |
419  loc.mask(-1, -1) | loc.mask(0, -1) | loc.mask(1, -1)) & interpBit) {
420  continue;
421  }
422 /*
423  * OK, it's a CR
424  *
425  * replace CR-contaminated pixels with reasonable values as we go through
426  * image, which increases the detection rate
427  */
428  crpixels.push_back(CRPixel<ImagePixel>(i + mimage.getX0(), j + mimage.getY0(), loc.image()));
429  loc.image() = corr; /* just a preliminary estimate */
430 
431  if (static_cast<int>(crpixels.size()) > nCrPixelMax) {
432  reinstateCrPixels(mimage.getImage().get(), crpixels);
433 
435  (boost::format("Too many CR pixels (max %d)") % nCrPixelMax).str());
436  }
437  }
438  }
439 /*
440  * We've found them on a pixel-by-pixel basis, now merge those pixels
441  * into cosmic rays
442  */
443  std::vector<int> aliases; // aliases for initially disjoint parts of CRs
444  aliases.reserve(1 + crpixels.size()/2); // initial size of aliases
445 
446  std::vector<detection::IdSpan::Ptr> spans; // y:x0,x1 for objects
447  spans.reserve(aliases.capacity()); // initial size of spans
448 
449  aliases.push_back(0); // 0 --> 0
450 
456  int ncr = 0; // number of detected cosmic rays
457  if (!crpixels.empty()) {
458  int id; // id number for a CR
459  int x0 = -1, x1 = -1, y = -1; // the beginning and end column, and row of this span in a CR
460 
461  // I am dummy
462  CRPixel<ImagePixel> dummy(0, -1, 0, -1);
463  crpixels.push_back(dummy);
464  //printf("Created dummy CR: i %i, id %i, col %i, row %i, val %g\n", dummy.get_i(), dummy.id, dummy.col, dummy.row, (double)dummy.val);
465  for (crpixel_iter crp = crpixels.begin(); crp < crpixels.end() - 1 ; ++crp) {
466  //printf("Looking at CR: i %i, id %i, col %i, row %i, val %g\n", crp->get_i(), crp->id, crp->col, crp->row, (double)crp->val);
467 
468  if (crp->id < 0) { // not already assigned
469  crp->id = ++ncr; // a new CR
470  aliases.push_back(crp->id);
471  y = crp->row;
472  x0 = x1 = crp->col;
473  //printf(" Assigned ID %i; looking at row %i, start col %i\n", crp->id, crp->row, crp->col);
474  }
475  id = crp->id;
476  //printf(" Next CRpix has i=%i, id=%i, row %i, col %i\n", crp[1].get_i(), crp[1].id, crp[1].row, crp[1].col);
477 
478  if (crp[1].row == crp[0].row && crp[1].col == crp[0].col + 1) {
479  //printf(" Adjoining! Set next CRpix id = %i; x1=%i\n", crp[1].id, x1);
480  crp[1].id = id;
481  ++x1;
482  } else {
483  assert (y >= 0 && x0 >= 0 && x1 >= 0);
484  spans.push_back(detection::IdSpan::Ptr(new detection::IdSpan(id, y, x0, x1)));
485  //printf(" Not adjoining; adding span id=%i, y=%i, x = [%i, %i]\n", id, y, x0, x1);
486  }
487  }
488  }
489 
490  // At the end of this loop, all crpixel entries have been assigned an ID,
491  // except for the "dummy" entry at the end of the array.
492  if (crpixels.size() > 0) {
493  for (crpixel_iter cp = crpixels.begin(); cp != crpixels.end() - 1; cp++) {
494  assert(cp->id >= 0);
495  assert(cp->col >= 0);
496  assert(cp->row >= 0);
497  }
498  // dummy:
499  assert(crpixels[crpixels.size()-1].id == -1);
500  assert(crpixels[crpixels.size()-1].col == 0);
501  assert(crpixels[crpixels.size()-1].row == -1);
502  }
503 
504  for (std::vector<detection::IdSpan::Ptr>::iterator sp = spans.begin(), end = spans.end(); sp != end; sp++) {
505  assert((*sp)->id >= 0);
506  assert((*sp)->y >= 0);
507  assert((*sp)->x0 >= 0);
508  assert((*sp)->x1 >= (*sp)->x0);
509  for (std::vector<detection::IdSpan::Ptr>::iterator sp2 = sp + 1; sp2 != end; sp2++) {
510  assert((*sp2)->y >= (*sp)->y);
511  if ((*sp2)->y == (*sp)->y) {
512  assert((*sp2)->x0 > (*sp)->x1);
513  }
514  }
515  }
516 
517 /*
518  * See if spans touch each other
519  */
520  for (std::vector<detection::IdSpan::Ptr>::iterator sp = spans.begin(), end = spans.end();
521  sp != end; ++sp) {
522  int const y = (*sp)->y;
523  int const x0 = (*sp)->x0;
524  int const x1 = (*sp)->x1;
525 
526  // this loop will probably run for only a few steps
527  for (std::vector<detection::IdSpan::Ptr>::iterator sp2 = sp + 1; sp2 != end; ++sp2) {
528  if ((*sp2)->y == y) {
529  // on this row (but not adjoining columns, since it would have been merged into this span);
530  // keep looking.
531  continue;
532  } else if ((*sp2)->y != (y + 1)) {
533  // sp2 is more than one row below; can't be connected.
534  break;
535  } else if ((*sp2)->x0 > (x1 + 1)) {
536  // sp2 is more than one column away to the right; can't be connected
537  break;
538  } else if ((*sp2)->x1 >= (x0 - 1)) {
539  // touches
540  int r1 = detection::resolve_alias(aliases, (*sp)->id);
541  int r2 = detection::resolve_alias(aliases, (*sp2)->id);
542  aliases[r1] = r2;
543  }
544  }
545  }
546 
547 
548 
549 
550 
551 /*
552  * Resolve aliases; first alias chains, then the IDs in the spans
553  */
554  for (unsigned int i = 0; i != spans.size(); ++i) {
555  spans[i]->id = detection::resolve_alias(aliases, spans[i]->id);
556  }
557 
558 /*
559  * Sort spans by ID, so we can sweep through them once
560  */
561  if (spans.size() > 0) {
562  std::sort(spans.begin(), spans.end(), detection::IdSpanCompar());
563  }
564 
565 /*
566  * Build Footprints from spans
567  */
569 
570  if (spans.size() > 0) {
571  int id = spans[0]->id;
572  unsigned int i0 = 0; // initial value of i
573  for (unsigned int i = i0; i <= spans.size(); ++i) { // <= size to catch the last object
574  if (i == spans.size() || spans[i]->id != id) {
576 
577  std::vector<geom::Span> spanList;
578  spanList.reserve(i - i0);
579  for (; i0 < i; ++i0) {
580  spanList.push_back(geom::Span(spans[i0]->y, spans[i0]->x0, spans[i0]->x1));
581  }
582  cr->setSpans(std::make_shared<geom::SpanSet>(std::move(spanList)));
583  CRs.push_back(cr);
584  }
585 
586  if (i < spans.size()) {
587  id = spans[i]->id;
588  }
589  }
590  }
591 
592  reinstateCrPixels(mimage.getImage().get(), crpixels);
593 /*
594  * apply condition #1
595  */
596  CountsInCR<typename ImageT::Pixel> CountDN(bkgd);
597  for (std::vector<std::shared_ptr<detection::Footprint>>::iterator cr = CRs.begin(), end = CRs.end();
598  cr != end; ++cr) {
599  // find the sum of pixel values within the CR
600  (*cr)->getSpans()->applyFunctor(CountDN, *mimage.getImage());
601 
602  LOGL_DEBUG("TRACE4.algorithms.CR", "CR at (%d, %d) has %g DN",
603  (*cr)->getBBox().getMinX(), (*cr)->getBBox().getMinY(), CountDN.getCounts());
604  if (CountDN.getCounts() < minDn) { /* not bright enough */
605  LOGL_DEBUG("TRACE5.algorithms.CR", "Erasing CR");
606 
607  cr = CRs.erase(cr);
608  --cr; // back up to previous CR (we're going to increment it)
609  --end;
610  }
611  CountDN.reset();
612  }
613  ncr = CRs.size(); /* some may have been too faint */
614 /*
615  * We've found them all, time to kill them all
616  */
617  bool const debias_values = true;
618  bool grow = false;
619  LOGL_DEBUG("TRACE2.algorithms.CR", "Removing initial list of CRs");
620  removeCR(mimage, CRs, bkgd, crBit, saturBit, badMask, debias_values, grow);
621 #if 0 // Useful to see phase 2 in ds9; debugging only
622  (void)setMaskFromFootprintList(mimage.getMask().get(), CRs,
623  mimage.getMask()->getPlaneBitMask("DETECTED"));
624 #endif
625 /*
626  * Now that we've removed them, go through image again, examining area around
627  * each CR for extra bad pixels. Note that we set cond3Fac = 0 for this pass
628  *
629  * We iterate niteration times; niter==1 was sufficient for SDSS data, but megacam
630  * CCDs are different -- who knows for other devices?
631  */
632  bool too_many_crs = false; // we've seen too many CR pixels
633  int nextra = 0; // number of pixels added to list of CRs
634  for (int i = 0; i != niteration && !too_many_crs; ++i) {
635  LOGL_DEBUG("TRACE1.algorithms.CR", "Starting iteration %d", i);
636  for (std::vector<std::shared_ptr<detection::Footprint>>::iterator fiter = CRs.begin();
637  fiter != CRs.end(); fiter++) {
639 /*
640  * Are all those `CR' pixels interpolated? If so, don't grow it
641  */
642  {
643  // this work should be taken on in DM-9538
644  //std::shared_ptr<detection::Footprint> om = footprintAndMask(cr, mimage.getMask(), interpBit);
645  // Placeholder until DM-9538 then remove empty footprint
646  auto om = std::make_shared<detection::Footprint>();
647  int const npix = (om) ? om->getArea() : 0;
648 
649  if (static_cast<std::size_t>(npix) == cr->getArea()) {
650  continue;
651  }
652  }
653 /*
654  * No; some of the suspect pixels aren't interpolated
655  */
656  detection::Footprint extra; // extra pixels added to cr
657  for (auto siter = cr->getSpans()->begin();
658  siter != cr->getSpans()->end(); siter++) {
659  auto const span = siter;
660 
661  /*
662  * Check the lines above and below the span. We're going to check a 3x3 region around
663  * the pixels, so we need a buffer around the edge. We check the pixels just to the
664  * left/right of the span, so the buffer needs to be 2 pixels (not just 1) in the
665  * column direction, but only 1 in the row direction.
666  */
667  int const y = span->getY() - mimage.getY0();
668  if (y < 2 || y >= nrow - 2) {
669  continue;
670  }
671  int x0 = span->getX0() - mimage.getX0();
672  int x1 = span->getX1() - mimage.getX0();
673  x0 = (x0 < 2) ? 2 : (x0 > ncol - 3) ? ncol - 3 : x0;
674  x1 = (x1 < 2) ? 2 : (x1 > ncol - 3) ? ncol - 3 : x1;
675 
676  checkSpanForCRs(&extra, crpixels, y - 1, x0, x1, mimage,
677  minSigma/2, thresH, thresV, thresD, bkgd, 0, keep);
678  checkSpanForCRs(&extra, crpixels, y, x0, x1, mimage,
679  minSigma/2, thresH, thresV, thresD, bkgd, 0, keep);
680  checkSpanForCRs(&extra, crpixels, y + 1, x0, x1, mimage,
681  minSigma/2, thresH, thresV, thresD, bkgd, 0, keep);
682  }
683 
684  if (extra.getSpans()->size() > 0) { // we added some pixels
685  if (nextra + static_cast<int>(crpixels.size()) > nCrPixelMax) {
686  too_many_crs = true;
687  break;
688  }
689 
690  nextra += extra.getArea();
691 
692  std::vector<geom::Span> tmpSpanList(cr->getSpans()->begin(),
693  cr->getSpans()->end());
694  for (auto const & spn : (*extra.getSpans())) {
695  tmpSpanList.push_back(spn);
696  }
697  cr->setSpans(std::make_shared<geom::SpanSet>(std::move(tmpSpanList)));
698  }
699  }
700 
701  if (nextra == 0) {
702  break;
703  }
704  }
705 /*
706  * mark those pixels as CRs
707  */
708  if (!too_many_crs) {
709  for (auto const & foot : CRs) {
710  foot->getSpans()->setMask(*mimage.getMask().get(), crBit);
711  }
712  }
713 /*
714  * Maybe reinstate initial values; n.b. the same pixel may appear twice, so we want the
715  * first value stored (hence the uses of rbegin/rend)
716  *
717  * We have to do this if we decide _not_ to remove certain CRs,
718  * for example those which lie next to saturated pixels
719  */
720  if (keep || too_many_crs) {
721  if (crpixels.size() > 0) {
722  int const imageX0 = mimage.getX0();
723  int const imageY0 = mimage.getY0();
724 
725  std::sort(crpixels.begin(), crpixels.end()); // sort into birth order
726 
727  crpixel_riter rend = crpixels.rend();
728  for (crpixel_riter crp = crpixels.rbegin(); crp != rend; ++crp) {
729  if (crp->row == -1)
730  // dummy; skip it.
731  continue;
732  mimage.at(crp->col - imageX0, crp->row - imageY0).image() = crp->val;
733  }
734  }
735  } else {
736  if (true || nextra > 0) {
737  grow = true;
738  LOGL_DEBUG("TRACE2.algorithms.CR", "Removing final list of CRs, grow = %d", grow);
739  removeCR(mimage, CRs, bkgd, crBit, saturBit, badMask, debias_values, grow);
740  }
741 /*
742  * we interpolated over all CR pixels, so set the interp bits too
743  */
744  for (auto const & foot : CRs) {
745  foot->getSpans()->setMask(*mimage.getMask().get(), static_cast<MaskPixel>(crBit | interpBit));
746  }
747  }
748 
749  if (too_many_crs) { // we've cleaned up, so we can throw the exception
751  (boost::format("Too many CR pixels (max %d)") % nCrPixelMax).str());
752  }
753 
754  return CRs;
755 }
756 
757 /*****************************************************************************/
758 namespace {
759 /*
760  * Is condition 3 true?
761  */
762 template<typename ImageT>
763 bool condition_3(ImageT *estimate, // estimate of true value of pixel
764  double const peak, // counts in central pixel (no sky)
765  double const mean_ns, // mean in NS direction (no sky)
766  double const mean_we, // " " WE " " " "
767  double const mean_swne, // " " SW-NE " " " "
768  double const mean_nwse, // " " NW-SE " " " "
769  double const dpeak, // standard deviation of peak value
770  double const dmean_ns, // s.d. of mean in NS direction
771  double const dmean_we, // " " " " WE " "
772  double const dmean_swne, // " " " " SW-NE " "
773  double const dmean_nwse, // " " " " NW-SE " "
774  double const thresH, // horizontal threshold
775  double const thresV, // vertical threshold
776  double const thresD, // diagonal threshold
777  double const cond3Fac // fiddle factor for noise
778  )
779 {
780  if (thresV*(peak - cond3Fac*dpeak) > mean_ns + cond3Fac*dmean_ns) {
781  *estimate = (ImageT)mean_ns;
782  return true;
783  }
784 
785  if (thresH*(peak - cond3Fac*dpeak) > mean_we + cond3Fac*dmean_we) {
786  *estimate = mean_we;
787  return true;
788  }
789 
790  if (thresD*(peak - cond3Fac*dpeak) > mean_swne + cond3Fac*dmean_swne) {
791  *estimate = mean_swne;
792  return true;
793  }
794 
795  if (thresD*(peak - cond3Fac*dpeak) > mean_nwse + cond3Fac*dmean_nwse) {
796  *estimate = mean_nwse;
797  return true;
798  }
799 
800  return false;
801 }
802 
803 /************************************************************************************************************/
804 /*
805  * Interpolate over a CR's pixels
806  */
807 template <typename MaskedImageT>
808 class RemoveCR {
809 public:
810  RemoveCR(MaskedImageT const& mimage,
811  double const bkgd,
812  typename MaskedImageT::Mask::Pixel badMask,
813  bool const debias,
815  ) :_image(mimage),
816  _bkgd(bkgd),
817  _ncol(mimage.getWidth()),
818  _nrow(mimage.getHeight()),
819  _badMask(badMask),
820  _debias(debias),
821  _rand(rand) {}
822 
823  void operator()(geom::Point2I const & point, int) {
824  int const & x = point.getX();
825  int const & y = point.getY();
826  typename MaskedImageT::xy_locator loc = _image.xy_at(x - _image.getX0(),
827  y - _image.getY0());
828  typedef typename MaskedImageT::Image::Pixel MImagePixel;
830  int ngood = 0; // number of good values on min
831 
832  MImagePixel const minval = _bkgd - 2*sqrt(loc.variance()); // min. acceptable pixel value after interp
833 /*
834  * W-E row
835  */
836  if (x - 2 >= 0 && x + 2 < _ncol) {
837  if ((loc.mask(-2, 0) | _badMask) || (loc.mask(-1, 0) | _badMask) ||
838  (loc.mask( 1, 0) | _badMask) || (loc.mask( 2, 0) | _badMask)) {
839  ; // estimate is contaminated
840  } else {
841  MImagePixel const v_m2 = loc.image(-2, 0);
842  MImagePixel const v_m1 = loc.image(-1, 0);
843  MImagePixel const v_p1 = loc.image( 1, 0);
844  MImagePixel const v_p2 = loc.image( 2, 0);
845 
846  MImagePixel const tmp =
847  interp::lpc_1_c1*(v_m1 + v_p1) + interp::lpc_1_c2*(v_m2 + v_p2);
848 
849  if (tmp > minval && tmp < min) {
850  min = tmp;
851  ngood++;
852  }
853  }
854  }
855 /*
856  * N-S column
857  */
858  if (y - 2 >= 0 && y + 2 < _nrow) {
859  if ((loc.mask(0, -2) | _badMask) || (loc.mask(0, -1) | _badMask) ||
860  (loc.mask(0, 1) | _badMask) || (loc.mask(0, 2) | _badMask)) {
861  ; /* estimate is contaminated */
862  } else {
863  MImagePixel const v_m2 = loc.image(0, -2);
864  MImagePixel const v_m1 = loc.image(0, -1);
865  MImagePixel const v_p1 = loc.image(0, 1);
866  MImagePixel const v_p2 = loc.image(0, 2);
867 
868  MImagePixel const tmp =
869  interp::lpc_1_c1*(v_m1 + v_p1) + interp::lpc_1_c2*(v_m2 + v_p2);
870 
871  if (tmp > minval && tmp < min) {
872  min = tmp;
873  ngood++;
874  }
875  }
876  }
877 /*
878  * SW--NE diagonal
879  */
880  if (x - 2 >= 0 && x + 2 < _ncol && y - 2 >= 0 && y + 2 < _nrow) {
881  if ((loc.mask(-2, -2) | _badMask) || (loc.mask(-1, -1) | _badMask) ||
882  (loc.mask( 1, 1) | _badMask) || (loc.mask( 2, 2) | _badMask)) {
883  ; /* estimate is contaminated */
884  } else {
885  MImagePixel const v_m2 = loc.image(-2, -2);
886  MImagePixel const v_m1 = loc.image(-1, -1);
887  MImagePixel const v_p1 = loc.image( 1, 1);
888  MImagePixel const v_p2 = loc.image( 2, 2);
889 
890  MImagePixel const tmp =
891  interp::lpc_1s2_c1*(v_m1 + v_p1) + interp::lpc_1s2_c2*(v_m2 + v_p2);
892 
893  if (tmp > minval && tmp < min) {
894  min = tmp;
895  ngood++;
896  }
897  }
898  }
899 /*
900  * SE--NW diagonal
901  */
902  if (x - 2 >= 0 && x + 2 < _ncol && y - 2 >= 0 && y + 2 < _nrow) {
903  if ((loc.mask( 2, -2) | _badMask) || (loc.mask( 1, -1) | _badMask) ||
904  (loc.mask(-1, 1) | _badMask) || (loc.mask(-2, 2) | _badMask)) {
905  ; /* estimate is contaminated */
906  } else {
907  MImagePixel const v_m2 = loc.image( 2, -2);
908  MImagePixel const v_m1 = loc.image( 1, -1);
909  MImagePixel const v_p1 = loc.image(-1, 1);
910  MImagePixel const v_p2 = loc.image(-2, 2);
911 
912  MImagePixel const tmp =
913  interp::lpc_1s2_c1*(v_m1 + v_p1) + interp::lpc_1s2_c2*(v_m2 + v_p2);
914 
915  if (tmp > minval && tmp < min) {
916  min = tmp;
917  ngood++;
918  }
919  }
920  }
921 /*
922  * Have we altogether failed to find an acceptable value? If so interpolate
923  * using the full-up interpolation code both vertically and horizontally
924  * and take the average. This can fail for large enough defects (e.g. CRs
925  * lying in bad columns), in which case the interpolator returns -1. If
926  * both directions fail, use the background value.
927  */
928  if (ngood == 0) {
930  interp::singlePixel(x, y, _image, true, minval);
932  interp::singlePixel(x, y, _image, false, minval);
933 
934  if (!val_h.first) {
935  if (!val_v.first) { // Still no good value. Guess wildly
936  min = _bkgd + sqrt(loc.variance())*_rand.gaussian();
937  } else {
938  min = val_v.second;
939  }
940  } else {
941  if (val_v.first) {
942  min = val_h.second;
943  } else {
944  min = (val_v.second + val_h.second)/2;
945  }
946  }
947  }
948 /*
949  * debias the minimum; If more than one uncontaminated estimate was
950  * available, estimate the bias to be simply that due to choosing the
951  * minimum of two Gaussians. In fact, even some of the "good" pixels
952  * may have some extra charge, so even if ngood > 2, still use this
953  * estimate
954  */
955  if (ngood > 0) {
956  LOGL_DEBUG("TRACE3.algorithms.CR", "Adopted min==%g at (%d, %d) (ngood=%d)",
957  static_cast<double>(min), x, y, ngood);
958  }
959 
960  if (_debias && ngood > 1) {
961  min -= interp::min2GaussianBias*sqrt(loc.variance())*_rand.gaussian();
962  }
963 
964  loc.image() = min;
965  }
966 private:
967  MaskedImageT const & _image;
968  double _bkgd;
969  int _ncol, _nrow;
970  typename MaskedImageT::Mask::Pixel _badMask;
971  bool _debias;
973 };
974 
975 /************************************************************************************************************/
976 /*
977  * actually remove CRs from the frame
978  */
979 template<typename ImageT, typename MaskT>
980 void removeCR(image::MaskedImage<ImageT, MaskT> & mi, // image to search
981  std::vector<std::shared_ptr<detection::Footprint>> & CRs, // list of cosmic rays
982  double const bkgd, // non-subtracted background
983  MaskT const , // Bit value used to label CRs
984  MaskT const saturBit, // Bit value used to label saturated pixels
985  MaskT const badMask, // Bit mask for bad pixels
986  bool const debias, // statistically debias values?
987  bool const grow // Grow CRs?
988  )
989 {
990  lsst::afw::math::Random rand; // a random number generator
991  /*
992  * replace the values of cosmic-ray contaminated pixels with 1-dim 2nd-order weighted means Cosmic-ray
993  * contaminated pixels have already been given a mask value, crBit
994  *
995  * If there are no good options (i.e. all estimates are contaminated), try using just pixels that are not
996  * CRs; failing that, interpolate in the row- or column direction over as large a distance as is required
997  *
998  * XXX SDSS (and we) go through this list backwards; why?
999  */
1000 
1001  // a functor to remove a CR
1002  RemoveCR<image::MaskedImage<ImageT, MaskT> > removeCR(mi, bkgd, badMask, debias, rand);
1003 
1004  for (std::vector<std::shared_ptr<detection::Footprint>>::reverse_iterator fiter = CRs.rbegin();
1005  fiter != CRs.rend(); ++fiter) {
1007 /*
1008  * If I grow this CR does it touch saturated pixels? If so, don't
1009  * interpolate and add CR pixels to saturated mask
1010  */
1011 /* This is commented out pending work from DM-9538
1012  if (grow && cr->getArea() < 100) {
1013  try {
1014  bool const isotropic = false; // use a slow isotropic grow?
1015  auto gcr = std::make_shared<detection::Footprint>(cr->getSpans()->dilate(1), cr.getRegion());
1016  std::shared_ptr<detection::Footprint> const saturPixels = footprintAndMask(gcr, mi.getMask(), saturBit);
1017  auto const saturPixels = footprintAndMask(gcr, mi.getMask(), saturBit);
1018 
1019  if (saturPixels->getArea() > 0) { // pixel is adjacent to a saturation trail
1020  setMaskFromFootprint(mi.getMask().get(), *saturPixels, saturBit);
1021 
1022  continue;
1023  }
1024  } catch(lsst::pex::exceptions::LengthError &) {
1025  continue;
1026  }
1027  }
1028  */
1029 /*
1030  * OK, fix it
1031  */
1032  // applyFunctor must have an argument other than the functor, however in this case
1033  // additional arguments are unneeded, so pass a constant 1 to be iterated over
1034  // and ignore
1035  cr->getSpans()->applyFunctor(removeCR, 1);
1036  }
1037 }
1038 }
1039 
1040 /************************************************************************************************************/
1041 //
1042 // Explicit instantiations
1043 // \cond
1044 #define INSTANTIATE(TYPE) \
1045  template \
1046  std::vector<std::shared_ptr<detection::Footprint>> \
1047  findCosmicRays(lsst::afw::image::MaskedImage<TYPE> &image, \
1048  detection::Psf const &psf, \
1049  double const bkgd, \
1050  lsst::pex::policy::Policy const& policy, \
1051  bool const keep \
1052  )
1053 
1054 INSTANTIATE(float);
1055 INSTANTIATE(double); // Why do we need double images?
1056 // \endcond
1057 }}} // namespace lsst::meas::algorithms
std::vector< std::shared_ptr< detection::Footprint > > findCosmicRays(MaskedImageT &mimage, detection::Psf const &psf, double const bkgd, lsst::pex::policy::Policy const &policy, bool const keep)
Find cosmic rays in an Image, and mask and remove them.
Definition: CR.cc:344
#define INSTANTIATE(TYPE)
Definition: ImagePca.cc:128
T empty(T... args)
double const min2GaussianBias
Mean value of the minimum of two N(0,1) variates.
Definition: Interp.h:60
Random & _rand
T rend(T... args)
int min
T end(T... args)
IdSpan(int id, int y, int x0, int x1)
Definition: CR.cc:69
bool operator()(const IdSpan::ConstPtr a, const IdSpan::ConstPtr b)
Definition: CR.cc:78
T at(T... args)
T push_back(T... args)
std::shared_ptr< IdSpan > Ptr
Definition: CR.cc:66
int end
int _id
int getInt(const std::string &name) const
T capacity(T... args)
std::size_t getArea() const
void setSpans(std::shared_ptr< geom::SpanSet > otherSpanSet)
comparison functor; sort by ID, then by row (y), then by column range start (x0)
Definition: CR.cc:77
double x
T max(T... args)
T move(T... args)
double getDouble(const std::string &name) const
int row
Definition: CR.cc:157
T size(T... args)
#define LSST_EXCEPT(type,...)
std::shared_ptr< const IdSpan > ConstPtr
Definition: CR.cc:67
T begin(T... args)
std::pair< bool, typename MaskedImageT::Image::Pixel > singlePixel(int x, int y, MaskedImageT const &image, bool horizontal, double minval)
Return a boolean status (true: interpolation is OK) and the interpolated value for a pixel...
Definition: Interp.cc:2134
int resolve_alias(const std::vector< int > &aliases, int id)
Follow a chain of aliases, returning the final resolved value.
Definition: CR.cc:97
#define LOGL_DEBUG(logger, message...)
afw::table::Key< double > b
std::shared_ptr< geom::SpanSet > getSpans() const
T sort(T... args)
ImageT val
Definition: CR.cc:158
double const lpc_1s2_c1
LPC coefficients for sigma = 1/sqrt(2), S/N = infty.
Definition: Interp.h:55
image::Image< Pixel > Image
int col
Definition: CR.cc:156
double const lpc_1_c1
LPC coefficients for sigma = 1, S/N = infty.
Definition: Interp.h:49
T reserve(T... args)
run-length code for part of object
Definition: CR.cc:64
T rbegin(T... args)
std::shared_ptr< math::Kernel const > getLocalKernel(geom::Point2D position=makeNullPoint(), image::Color color=image::Color()) const