LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
scaling.cc
Go to the documentation of this file.
1 /*
2  * Calculate some scalings. Legacy code from NAOJ, which could probably be rewritten in python
3  * if the need arose. It could certainly use more LSST primitives (e.g. line fitting)
4  */
5 #include <cstdint>
6 #include <cmath>
7 #include <algorithm>
8 #include <cstdio>
9 #include <vector>
10 #include <stdexcept>
11 
12 #include "lsst/pex/exceptions.h"
13 #include "lsst/afw/image/Image.h"
14 
15 namespace lsst { namespace afw { namespace display {
16 
17 template <class T>
18 static void getSample(
19  image::Image<T> const& image,
20  std::size_t const nSamples,
21  std::vector<T>& vSample
22  )
23 {
24  int const width = image.getWidth();
25  int const height = image.getHeight();
26 
27  // extract, from image, about nSample samples
28  // such that they form a grid.
29  vSample.reserve(nSamples);
30 
31  int const initialStride = std::max(
32  1,
33  static_cast<int>(std::sqrt(width*height/static_cast<double>(nSamples)))
34  );
35 
36  for (int stride = initialStride; stride >= 1; --stride) {
37  vSample.clear();
38 
39  for (std::size_t y = 0; y < height; y += stride) {
40  for (std::size_t x = 0; x < width; x += stride) {
41  T const elem = image(x, y);
42  if (std::isfinite(elem)) {
43  vSample.push_back(elem);
44  }
45  }
46  }
47 
48  // if more than 80% of nSamples were sampled, OK.
49  if (5*vSample.size() > 4*nSamples) {
50  break;
51  }
52  }
53 }
54 
55 static inline double
56 computeSigma(
57  std::vector<double> const& vFlat,
58  std::vector<int> const& vBadPix,
59  std::size_t const nGoodPix
60  )
61 {
62  if (nGoodPix <= 1) {
63  return 0;
64  }
65 
66  double sumz = 0, sumsq = 0;
67  for (unsigned i = 0; i < vFlat.size(); ++i) {
68  if (!vBadPix[i]) {
69  double const z = vFlat[i];
70 
71  sumz += z;
72  sumsq += z*z;
73  }
74  }
75 
76  double const goodPix = nGoodPix;
77  double const tmp = sumsq/(goodPix - 1) - sumz*sumz/(goodPix*(goodPix - 1));
78 
79  return (tmp > 0) ? std::sqrt(tmp) : 0;
80 
81 }
82 
83 template <class T>
84 static std::pair<double, double>
85 fitLine(
86  int *nGoodPixOut, // returned; it'd be nice to use std::tuple from C++11
87  std::vector<T> const& vSample,
88  double const nSigmaClip,
89  int const nGrow,
90  int const minpix,
91  int const nIter
92  )
93 {
94  // map the indices of vSample to [-1.0, 1.0]
95  double const xscale = 2.0/(vSample.size() - 1);
96  std::vector<double> xnorm;
97  xnorm.reserve(vSample.size());
98  for (std::size_t i = 0; i < vSample.size(); ++i) {
99  xnorm.push_back(i*xscale - 1.0);
100  }
101 
102  // Mask that is used in k-sigma clipping
103  std::vector<int> vBadPix(vSample.size(), 0);
104 
105  std::size_t nGoodPix = vSample.size();
106  std::size_t nGoodPixOld = nGoodPix + 1;
107 
108  // values to be obtained
109  double intercept = 0;
110  double slope = 0;
111 
112  for (int iteration = 0; iteration < nIter; ++iteration) {
113  if (nGoodPix < minpix || nGoodPix >= nGoodPixOld) {
114  break;
115  }
116 
117  double sum = nGoodPix;
118  double sumx = 0, sumy = 0, sumxx = 0, sumxy = 0;
119  for (std::size_t i = 0; i < vSample.size(); ++i) {
120  if (!vBadPix[i]) {
121  double const x = xnorm[i];
122  double const y = vSample[i];
123 
124  sumx += x;
125  sumy += y;
126  sumxx += x*x;
127  sumxy += x*y;
128  }
129  }
130 
131  double delta = sum * sumxx - sumx * sumx;
132 
133  // slope and intercept
134  intercept = (sumxx * sumy - sumx * sumxy) / delta;
135  slope = (sum * sumxy - sumx * sumy) / delta;
136 
137  // residue
138  std::vector<double> vFlat;
139  vFlat.reserve(vSample.size());
140  for (unsigned i = 0; i < vSample.size(); ++i) {
141  vFlat.push_back(vSample[i] - (xnorm[i]*slope + intercept));
142  }
143 
144  // Threshold of k-sigma clipping
145  double const sigma = computeSigma(vFlat, vBadPix, nGoodPix);
146  double const hcut = sigma*nSigmaClip;
147  double const lcut = -hcut;
148 
149  // revise vBadPix
150  nGoodPixOld = nGoodPix;
151  nGoodPix = 0;
152 
153  for (unsigned i = 0; i < vSample.size(); ++i) {
154  double val = vFlat[i];
155  if (val < lcut || hcut < val) {
156  vBadPix[i] = 1;
157  }
158  }
159 
160  // blurr vBadPix
161  std::vector<int>vBadPix_new;
162  vBadPix_new.reserve(vSample.size());
163  for (unsigned x = 0; x < vSample.size(); ++x) {
164  int imin = (static_cast<int>(x) > nGrow) ? x - nGrow : -1;
165  int val = 0;
166  for (int i = x; i > imin ; --i) {
167  val += vBadPix[i];
168  }
169  vBadPix_new.push_back(val ? 1 : 0);
170  if (!val) {
171  ++nGoodPix;
172  }
173  }
174  vBadPix = vBadPix_new;
175  }
176 
177  // return the scale of x-axis
178  *nGoodPixOut = nGoodPix;
179 
180  return std::make_pair(intercept - slope, slope*xscale);
181 }
182 
183 template <class T>
184 std::pair<double, double>
186  int const nSamples,
187  double const contrast
188  )
189 {
190  // extract samples
191  std::vector<T> vSample;
192  getSample(image, nSamples, vSample);
193  int nPix = vSample.size();
194 
195  if (vSample.empty()) {
196  throw LSST_EXCEPT(pex::exceptions::RuntimeError, "ZScale: No pixel in image is finite");
197  }
198 
199  std::sort(vSample.begin(), vSample.end());
200 
201  // max, min, median
202  // N.b. you can get a median in linear time, but we need the sorted array for fitLine()
203  // If we wanted to speed this up, the best option would be to quantize
204  // the pixel values and build a histogram
205  double const zmin = vSample.front();
206  double const zmax = vSample.back();
207  int const iCenter = nPix/2;
208  T median = (nPix & 1) ? vSample[iCenter] : (vSample[iCenter] + vSample[iCenter + 1])/2;
209 
210  // fit a line to the sorted sample
211  const int maxRejectionRatio = 2;
212  const int npixelsMin = 5;
213 
214  int minpix = std::max(npixelsMin, nPix/maxRejectionRatio);
215  int nGrow = std::max(1, nPix/100);
216 
217  const double nSigmaClip = 2.5;
218  const int nIterations = 5;
219 
220  int nGoodPix = 0;
221  std::pair<double, double> ret = fitLine(&nGoodPix, vSample, nSigmaClip, nGrow, minpix, nIterations);
222 #if 0 // unused, but calculated and potentially useful
223  double const zstart = ret.first;
224 #endif
225  double const zslope = ret.second;
226 
227  double z1, z2;
228  if (nGoodPix < minpix) {
229  z1 = zmin;
230  z2 = zmax;
231  } else {
232  double const slope = zslope/contrast;
233 
234  z1 = std::max(zmin, median - iCenter*slope);
235  z2 = std::min(zmax, median + (nPix - iCenter - 1)*slope);
236  }
237 
238  return std::make_pair(z1, z2);
239 }
240 //
241 // Explicit instantiations
242 #define INSTANTIATE_GETZSCALE(T) \
243  template std::pair<double, double> \
244  getZScale(image::Image<T> const& image, int const nSamples, double const contrast)
245 
246 INSTANTIATE_GETZSCALE(std::uint16_t);
247 INSTANTIATE_GETZSCALE(float);
248 }}}
int y
std::pair< double, double > getZScale(image::Image< T > const &image, int const nSamples=1000, double const contrast=0.25)
Definition: scaling.cc:185
#define INSTANTIATE_GETZSCALE(T)
Definition: scaling.cc:242
bool val
afw::table::Key< double > sigma
Definition: GaussianPsf.cc:43
table::Key< table::Array< Kernel::Pixel > > image
Definition: FixedKernel.cc:117
Support for 2-D images.
int getHeight() const
Return the number of rows in the image.
Definition: Image.h:241
double x
#define LSST_EXCEPT(type,...)
Definition: Exception.h:46
Include files required for standard LSST Exception handling.
int getWidth() const
Return the number of columns in the image.
Definition: Image.h:239
A class to represent a 2-dimensional array of pixels.
Definition: PSF.h:43