mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
imageFilters.hpp
Go to the documentation of this file.
1 /** \file imageFilters.hpp
2  * \brief Image filters (smoothing, radial profiles, etc.)
3  * \ingroup image_processing_files
4  * \author Jared R. Males (jaredmales@gmail.com)
5  *
6  */
7 
8 #ifndef __imageFilters_hpp__
9 #define __imageFilters_hpp__
10 
11 #include <cstdlib>
12 
13 #include "../math/gslInterpolator.hpp"
14 #include "../math/vectorUtils.hpp"
15 #include "../math/geo.hpp"
16 
17 #include "imageMasks.hpp"
18 
19 namespace mx
20 {
21 namespace improc
22 {
23 
24 
25 ///Symetric Gaussian smoothing kernel
26 /** \ingroup image_filters_kernels
27  *
28  */
29 template<typename _arrayT, size_t _kernW=4>
31 {
32  typedef _arrayT arrayT;
33  typedef typename _arrayT::Scalar arithT;
34  static const int kernW = _kernW;
35 
36  arrayT kernel;
37 
38  arithT _fwhm;
39 
40  explicit gaussKernel(arithT fwhm)
41  {
42  _fwhm = fwhm;
43 
44  int w = kernW*_fwhm;
45 
46  if(w % 2 == 0) w++;
47 
48  kernel.resize(w, w);
49 
50  arithT kcen = 0.5*(w-1.0);
51 
52  arithT sig2 = _fwhm/2.354820045030949327;
53  sig2 *= sig2;
54 
55  arithT r2;
56  for(int i=0; i < w; ++i)
57  {
58  for(int j=0; j < w; ++j)
59  {
60  r2 = pow(i-kcen,2) + pow(j-kcen,2) ;
61  kernel(i,j) = exp(-r2/(2.0*sig2));
62  }
63  }
64 
65  kernel /= kernel.sum();
66  }
67 
68  int maxWidth()
69  {
70  return _kernW*_fwhm;
71  }
72 
73  void setKernel(arithT x, arithT y, arrayT & kernelArray)
74  {
75  //Unused parts of interface:
76  static_cast<void>(x);
77  static_cast<void>(y);
78 
79  kernelArray = kernel;
80  }
81 
82 };
83 
84 /// Azimuthally variable boxcare kernel.
85 /** Averages the image in a boxcare defined by a radial and azimuthal extent.
86  *
87  * \ingroup image_filters_kernels
88  */
89 template<typename _arrayT, size_t _kernW=2>
91 {
92  typedef _arrayT arrayT;
93  typedef typename _arrayT::Scalar arithT;
94 
95  static const int kernW = _kernW;
96 
97  arithT m_radWidth {0}; ///< the half-width of the averaging box, in the radial direction, in pixels.
98  arithT m_azWidth {0}; ///< the half-width of the averaging box, in the azimuthal direction, in pixels.
99  arithT m_maxAz {0}; ///< the maximum half-width of the averging box in the azimuthal direction, in degrees. >= 0. If 0 or >= 180, then no maximum is enforced.
100 
101  int m_maxWidth;
102 
103  azBoxKernel( arithT radWidth, ///< [in] the half-width of the averaging box, in the radial direction, in pixels.
104  arithT azWidth ///< [in] the half-width of the averaging box, in the azimuthal direction, in pixels.
105  ) : m_radWidth(fabs(radWidth)), m_azWidth(fabs(azWidth))
106  {
107  setMaxWidth();
108  }
109 
110  azBoxKernel( arithT radWidth, ///< [in] the half-width of the averaging box, in the radial direction, in pixels.
111  arithT azWidth, ///< [in] the half-width of the averaging box, in the azimuthal direction, in pixels.
112  arithT maxAz ///< [in] the maximum half-width of the averging box in the azimuthal direction, in degrees. >= 0. If 0 or >= 180, then no maximum is enforced.
113  ) : m_radWidth(fabs(radWidth)), m_azWidth(fabs(azWidth))
114  {
115  setMaxWidth();
116 
117  maxAz = fabs(maxAz);
118  if(maxAz >= 180) maxAz = 0; //Larger than 180 means no limit.
119 
120  m_maxAz = math::dtor( maxAz );
121 
122  }
123 
124  /// Sets the max width based on the configured az and rad widths.
125  void setMaxWidth()
126  {
127  // atan is where derivative of width/height is 0.
128 
129  arithT qmax = atan(m_azWidth/m_radWidth);
130  arithT mx1 = kernW*( (int) ( fabs(m_azWidth*sin(qmax)) + fabs(m_radWidth*cos(qmax)) ) + 1 );
131 
132  qmax = atan(m_radWidth/m_azWidth);
133  arithT mx2 = kernW*( (int) ( fabs(m_azWidth*cos(qmax)) + fabs(m_radWidth*sin(qmax)) ) + 1 );
134 
135  m_maxWidth = 0.5*std::max(mx1, mx2);
136  }
137 
138  int maxWidth()
139  {
140  return m_maxWidth;
141  }
142 
143  void setKernel(arithT x, arithT y, arrayT & kernel)
144  {
145  arithT rad0 = sqrt((arithT) (x*x + y*y));
146 
147  arithT sinq = y/rad0;
148  arithT cosq = x/rad0;
149 
150  //Only calc q if we're going to use it.
151  arithT q = 0;
152  if(m_maxAz > 0) q = atan2(sinq, cosq);
153 
154  int w = kernW*( (int) ( fabs(m_azWidth*sinq) + fabs(m_radWidth*cosq) ) + 1 );
155  int h = kernW*( (int) ( fabs(m_azWidth*cosq) + fabs(m_radWidth*sinq) ) + 1 );
156 
157  if(w > m_maxWidth*2)
158  {
159  std::stringstream errs;
160  errs << "|" << kernW << "|" << m_radWidth << "|" << m_azWidth << "|" << m_maxWidth << "|" << x << "|" << y << "|" << rad0 << "|" << sinq << "|" << cosq;
161  errs << "|" << w << "|" << h << "|";
162  mxThrowException(err::sizeerr, "azBoxKernel::setKernel", "width bigger than 2*maxWidth. This is a bug. Details: " + errs.str());
163  }
164 
165  if(h > m_maxWidth*2)
166  {
167  std::stringstream errs;
168  errs << "|" << kernW << "|" << m_radWidth << "|" << m_azWidth << "|" << m_maxWidth << "|" << x << "|" << y << "|" << rad0 << "|" << sinq << "|" << cosq;
169  errs << "|" << w << "|" << h << "|";
170  mxThrowException(err::sizeerr, "azBoxKernel::setKernel", "height bigger than 2*maxWidth. This is a bug. Details: " + errs.str());
171  }
172 
173  kernel.resize(w, h);
174 
175  arithT xcen = 0.5*(w-1.0);
176  arithT ycen = 0.5*(h-1.0);
177 
178  arithT xP, yP;
179  arithT rad, radP;
180  arithT sinq2, cosq2, sindq, q2, dq;
181  for(int j=0; j < h; ++j)
182  {
183  yP = j;
184  for(int i=0; i < w; ++i)
185  {
186  xP = i;
187  rad = sqrt( pow(xP-xcen,2) + pow(yP-ycen,2) );
188  radP = sqrt( pow(x+xP-xcen,2) + pow(y+yP-ycen,2) );
189 
190  if( fabs(radP-rad0) > m_radWidth)
191  {
192  kernel(i,j) = 0;
193  continue;
194  }
195 
196  sinq2 = (yP-ycen)/rad;
197  cosq2 = (xP-xcen)/rad;
198 
199  sindq = sinq2*cosq - cosq2*sinq;
200  if( fabs(rad*sindq) <= m_azWidth)
201  {
202  if(m_maxAz > 0) //Only check this if needed.
203  {
204  q2 = atan2(y+yP-ycen, x+xP-xcen);
205  dq = math::angleDiff<math::radiansT<arithT>>(q,q2);
206  if(fabs(dq) > m_maxAz)
207  {
208  kernel(i,j) = 0;
209  continue;
210  }
211  }
212  kernel(i,j) = 1;
213  }
214  else kernel(i,j) = 0;
215  }
216  }
217 
218  arithT ksum = kernel.sum();
219  if(ksum == 0)
220  {
221  mxThrowException(err::invalidconfig, "azBoxKernel::setKernel", "kernel sum 0 at " + std::to_string(x) + "," + std::to_string(y));
222  }
223  kernel /= ksum;
224  }
225 
226 };
227 
228 
229 
230 ///Filter an image with a mean kernel.
231 /** Applies the kernel to each pixel in the image and sums, storing the filtered result in the output image.
232  * The kernel-type (kernelT) must have the following interface:
233  * \code
234  * template<typename _arrayT, size_t kernW>
235  * struct filterKernel
236  * {
237  * typedef _arrayT arrayT; // arrayT must have an eigen-like interface
238  * typedef typename _arrayT::Scalar arithT;
239  *
240  * filterKernel()
241  * {
242  * //constructor
243  * }
244  *
245  * //The maxWidth function returns the maximum possible full-width (in either direction) of the kernel
246  * //Called once
247  * int maxWidth()
248  * {
249  * //returns the maximum half-width given the configuration
250  * }
251  *
252  * //The setKernel function is called for each pixel.
253  * void setKernel(arithT x, arithT y, arrayT & kernel)
254  * {
255  * //This must resize and populate the passed in kernel array each time
256  * //so that it is re-entrant.
257  * //Note: On output kernel array should be normalized so that sum() = 1.0
258  * //Note: the width and height of the kernel array should always be odd
259  * }
260  * };
261  * \endcode
262  *
263  * \param [out] fim will be allocated with resize, and on output contains the filtered image
264  * \param [in] im is the image to be filtered
265  * \param [in] kernel a fully configured obect of type kernelT
266  * \param [in] maxr is the maximum radius from the image center to apply the kernel. pixels
267  * outside this radius are set to 0.
268  *
269  * \tparam imageOutT the type of the output image (must have an Eigen like interface)
270  * \tparam imageInT the type of the input image (must have an Eigen like interface)
271  * \tparam kernelT is the kernel type (see above)
272  *
273  * \ingroup image_filters_kernels
274  *
275  */
276 template<typename imageOutT, typename imageInT, typename kernelT>
277 void filterImage(imageOutT & fim, imageInT im, kernelT kernel, int maxr= 0)
278 {
279  fim.resize(im.rows(), im.cols());
280 
281  float xcen = 0.5*(im.rows()-1);
282  float ycen = 0.5*(im.cols()-1);
283 
284  if(maxr == 0) maxr = 0.5*im.rows() - kernel.maxWidth();
285 
286  int mini = 0.5*im.rows() - maxr;
287  int maxi = 0.5*im.rows() + maxr;
288  int minj = 0.5*im.cols() - maxr;
289  int maxj = 0.5*im.cols() + maxr;
290 
291 
292  typename kernelT::arrayT kernelArray;
293 
294  #pragma omp parallel private(kernelArray)
295  {
296  int im_i, im_j, im_p,im_q;
297  int kern_i, kern_j, kern_p,kern_q;
298  typename imageOutT::Scalar norm;
299 
300  #pragma omp for
301  for(int i=0; i< im.rows(); ++i)
302  {
303  for(int j=0; j<im.cols(); ++j)
304  {
305  if((i >= mini && i< maxi) && (j>= minj && j<maxj))
306  {
307  kernel.setKernel(i-xcen, j-ycen, kernelArray);
308  fim(i,j) = (im.block(i-0.5*(kernelArray.rows()-1), j-0.5*(kernelArray.cols()-1), kernelArray.rows(), kernelArray.cols())*kernelArray).sum();
309  }
310  else
311  {
312  kernel.setKernel(i-xcen, j-ycen, kernelArray);
313 
314  im_i = i - 0.5*(kernelArray.rows()-1);
315  if(im_i < 0) im_i = 0;
316 
317  im_j = j - 0.5*(kernelArray.cols()-1);
318  if(im_j < 0) im_j = 0;
319 
320  im_p = im.rows() - im_i;
321  if(im_p > kernelArray.rows()) im_p = kernelArray.rows();
322 
323  im_q = im.cols() - im_j;
324  if(im_q > kernelArray.cols()) im_q = kernelArray.cols();
325 
326  kern_i = 0.5*(kernelArray.rows()-1) - i;
327  if(kern_i < 0) kern_i = 0;
328 
329  kern_j = 0.5*(kernelArray.cols()-1) - j;
330  if(kern_j < 0) kern_j = 0;
331 
332  kern_p = kernelArray.rows() - kern_i;
333  if(kern_p > kernelArray.rows()) kern_p = kernelArray.rows();
334 
335  kern_q = kernelArray.cols() - kern_j;
336  if(kern_q > kernelArray.cols()) kern_q = kernelArray.cols();
337 
338  //Pick only the smallest widths
339  if(im_p < kern_p) kern_p = im_p;
340  if(im_q < kern_q) kern_q = im_q;
341 
342  norm = kernelArray.block(kern_i, kern_j, kern_p, kern_q ).sum();
343 
344  fim(i,j) = ( im.block(im_i, im_j, kern_p, kern_q) * kernelArray.block(kern_i, kern_j, kern_p, kern_q )).sum()/norm;
345  if( !std::isfinite(fim(i,j))) fim(i,j) = 0.0;
346  }
347  }
348  }
349  }// pragma omp parallel
350 }
351 
352 template<typename imageOutT, typename imageInT, typename kernelT>
353 void medianFilterImage( imageOutT & fim,
354  imageInT im,
355  kernelT kernel,
356  int maxr = 0,
357  int maxrproc = 1)
358 {
359  fim.resize(im.rows(), im.cols());
360 
361  float xcen = 0.5*(im.rows()-1);
362  float ycen = 0.5*(im.cols()-1);
363 
364  if(maxr == 0) maxr = 0.5*im.rows() - kernel.maxWidth();
365 
366  int mini = 0.5*im.rows() - maxr;
367  int maxi = 0.5*im.rows() + maxr;
368  int minj = 0.5*im.cols() - maxr;
369  int maxj = 0.5*im.cols() + maxr;
370 
371 
372  typename kernelT::arrayT kernelArray;
373 
374  #pragma omp parallel private(kernelArray)
375  {
376  int im_i, im_j, im_p,im_q;
377  int kern_i, kern_j, kern_p,kern_q;
378  typename imageOutT::Scalar norm;
379 
380  std::vector<typename imageOutT::Scalar> pixels;
381 
382  #pragma omp for
383  for(int i=0; i< im.rows(); ++i)
384  {
385  for(int j=0; j<im.cols(); ++j)
386  {
387  if((i >= mini && i< maxi) && (j>= minj && j<maxj))
388  {
389  kernel.setKernel(i-xcen, j-ycen, kernelArray);
390 
391  pixels.clear();
392  for(int cc=0; cc<kernelArray.cols(); ++cc)
393  {
394  for(int rr=0; rr<kernelArray.rows(); ++rr)
395  {
396  if(kernelArray(rr,cc) != 0) pixels.push_back(im.block(i-0.5*(kernelArray.rows()-1), j-0.5*(kernelArray.cols()-1), kernelArray.rows(), kernelArray.cols())(rr,cc));
397  }
398  }
399 
400  fim(i,j) = math::vectorMedianInPlace(pixels);
401  }
402  else
403  {
404  if(maxrproc == 2)
405  {
406  fim(i,j) = 0;
407  continue;
408  }
409  kernel.setKernel(i-xcen, j-ycen, kernelArray);
410 
411  im_i = i - 0.5*(kernelArray.rows()-1);
412  if(im_i < 0) im_i = 0;
413 
414  im_j = j - 0.5*(kernelArray.cols()-1);
415  if(im_j < 0) im_j = 0;
416 
417  im_p = im.rows() - im_i;
418  if(im_p > kernelArray.rows()) im_p = kernelArray.rows();
419 
420  im_q = im.cols() - im_j;
421  if(im_q > kernelArray.cols()) im_q = kernelArray.cols();
422 
423  kern_i = 0.5*(kernelArray.rows()-1) - i;
424  if(kern_i < 0) kern_i = 0;
425 
426  kern_j = 0.5*(kernelArray.cols()-1) - j;
427  if(kern_j < 0) kern_j = 0;
428 
429  kern_p = kernelArray.rows() - kern_i;
430  if(kern_p > kernelArray.rows()) kern_p = kernelArray.rows();
431 
432  kern_q = kernelArray.cols() - kern_j;
433  if(kern_q > kernelArray.cols()) kern_q = kernelArray.cols();
434 
435  //Pick only the smallest widths
436  if(im_p < kern_p) kern_p = im_p;
437  if(im_q < kern_q) kern_q = im_q;
438 
439  norm = kernelArray.block(kern_i, kern_j, kern_p, kern_q ).sum();
440 
441  pixels.clear();
442  for(int cc=0; cc<kern_q; ++cc)
443  {
444  for(int rr=0; rr<kern_p; ++rr)
445  {
446  if( kernelArray.block(kern_i, kern_j, kern_p, kern_q )(rr,cc) != 0)
447  {
448  pixels.push_back( im.block(im_i, im_j, kern_p, kern_q)(rr,cc));
449  }
450  }
451  }
452 
453  fim(i,j) = math::vectorMedianInPlace(pixels);
454 
455  //fim(i,j) = ( im.block(im_i, im_j, kern_p, kern_q) * kernelArray.block(kern_i, kern_j, kern_p, kern_q )).sum()/norm;
456 
457  }
458  }
459  }
460  }// pragma omp parallel
461 }
462 
463 ///@}
464 
465 
466 /// Smooth an image using the mean in a rectangular box, optionally rejecting the highest and lowest values.
467 /** Calculates the mean value in a rectangular box of imIn, of size meanFullSidth X meanFullWidth and stores it in the corresonding center pixel of imOut.
468  * Does not smooth the 0.5*meanFullwidth rows and columns of the input image, and the values of these pixels are not
469  * changed in imOut (i.e. you should 0 them before the call).
470  *
471  * imOut is not re-allocated.
472  *
473  * If rejectMinMax is `true` then the minimum and maximum value in the box are not included in the mean. Rejection makes
474  * the algorithm somewhat slower, depending on box width.
475  *
476  * \tparam imageTout is an eigen-like image array
477  * \tparam imageTin is an eigen-like image array
478  *
479  * \returns 0 on success
480  * \returns -1 on error.
481  *
482  * \ingroup image_filters_average
483  */
484 template<typename imageTout, typename imageTin>
485 int meanSmooth( imageTout & imOut, ///< [out] the smoothed image. Not re-allocated, and the edge pixels are not modified.
486  const imageTin & imIn, ///< [in] the image to smooth
487  int meanFullWidth, ///< [in] the full-width of the smoothing box
488  bool rejectMinMax = false ///< [in] whether or not to reject the min and max value.
489  )
490 {
491  typedef typename imageTout::Scalar scalarT;
492 
493  int buff = 0.5*meanFullWidth;
494 
495  int nPix = meanFullWidth*meanFullWidth;
496 
497  if(rejectMinMax) //avoid the branch on every pixel
498  {
499  nPix -= 2;
500  for(int jj=buff; jj< imIn.cols()-buff; ++jj)
501  {
502  for(int ii=buff; ii < imIn.rows()-buff; ++ii)
503  {
504  scalarT sum = 0;
505  scalarT max = sum;
506  scalarT min = sum;
507  for(int ll = jj-buff; ll < jj+buff+1; ++ll)
508  {
509  for(int kk = ii-buff; kk < ii+buff+1; ++kk)
510  {
511  //scalarT val = imIn(kk,ll);
512  sum += imIn(kk,ll);
513  if(imIn(kk,ll) > max) max = imIn(kk,ll);
514  if(imIn(kk,ll) < min) min = imIn(kk,ll);
515  }
516  }
517  imOut(ii,jj) = (sum-max-min)/nPix;
518  }
519  }
520  }
521  else
522  {
523  for(int jj=buff; jj< imIn.cols()-buff; ++jj)
524  {
525  for(int ii=buff; ii < imIn.rows()-buff; ++ii)
526  {
527  scalarT sum = 0;
528  for(int ll = jj-buff; ll < jj+buff+1; ++ll)
529  {
530  for(int kk = ii-buff; kk < ii+buff+1; ++kk)
531  {
532  sum += imIn(kk,ll);
533  }
534  }
535  imOut(ii,jj) = sum/nPix;
536  }
537  }
538  }
539 
540  return 0;
541 }
542 
543 /// Smooth an image using the mean in a rectangular box, optionally rejecting the highest and lowest values. Determines the location and value of the highest pixel.
544 /** Calculates the mean value in a rectangular box of imIn, of size meanFullSidth X meanFullWidth and stores it in the corresonding center pixel of imOut.
545  * Does not smooth the 0.5*meanFullwidth rows and columns of the input image, and the values of these pixels are not
546  * changed in imOut (i.e. you should 0 them before the call).
547  *
548  * imOut is not re-allocated.
549  *
550  * If rejectMinMax is `true` then the minimum and maximum value in the box are not included in the mean. Rejection makes
551  * the somewhat slower, depending on box width.
552  *
553  * This version also determines the location and value of the maximum pixel. This adds some overhead, maybe on the order of 10% slower than without.
554  *
555  * \overload
556  *
557  * \tparam imageTout is an eigen-like image array
558  * \tparam imageTin is an eigen-like image array
559  *
560  * \returns 0 on success
561  * \returns -1 on error.
562  *
563  * \ingroup image_filters_average
564  */
565 template<typename imageTout, typename imageTin>
566 int meanSmooth( imageTout & imOut, ///< [out] the smoothed image. Not re-allocated, and the edge pixels are not modified.
567  int & xMax, ///< [out] the x-locatioin of the max pixel
568  int & yMax, ///< [out] the y-locatioin of the max pixel
569  typename imageTout::Scalar & pMax, ///< [out] the value of the max pixel
570  const imageTin & imIn, ///< [in] the image to smooth
571  int meanFullWidth, ///< [in] the full-width of the smoothing box
572  bool rejectMinMax = false ///< [in] whether or not to reject the min and max value.
573  )
574 {
575  typedef typename imageTout::Scalar scalarT;
576 
577  int buff = 0.5*meanFullWidth;
578 
579  int nPix = meanFullWidth*meanFullWidth;
580 
581  pMax = std::numeric_limits<scalarT>::lowest();
582 
583  if(rejectMinMax) //avoid the branch on every pixel.
584  {
585  nPix -= 2;
586  for(int jj=buff; jj< imIn.cols()-buff; ++jj)
587  {
588  for(int ii=buff; ii < imIn.rows()-buff; ++ii)
589  {
590  scalarT sum = 0;
591  scalarT max = sum;
592  scalarT min = sum;
593  for(int ll = jj-buff; ll < jj+buff+1; ++ll)
594  {
595  for(int kk = ii-buff; kk < ii+buff+1; ++kk)
596  {
597  //scalarT val = imIn(kk,ll);
598  sum += imIn(kk,ll);
599  if(imIn(kk,ll) > max) max = imIn(kk,ll);
600  if(imIn(kk,ll) < min) min = imIn(kk,ll);
601  }
602  }
603  imOut(ii,jj) = (sum-max-min)/nPix;
604  if(imOut(ii,jj) > pMax)
605  {
606  pMax = imOut(ii,jj);
607  xMax = ii;
608  yMax = jj;
609  }
610  }
611  }
612  }
613  else
614  {
615  for(int jj=buff; jj< imIn.cols()-buff; ++jj)
616  {
617  for(int ii=buff; ii < imIn.rows()-buff; ++ii)
618  {
619  scalarT sum = 0;
620  for(int ll = jj-buff; ll < jj+buff+1; ++ll)
621  {
622  for(int kk = ii-buff; kk < ii+buff+1; ++kk)
623  {
624  sum += imIn(kk,ll);
625  }
626  }
627  imOut(ii,jj) = sum/nPix;
628  if(imOut(ii,jj) > pMax)
629  {
630  pMax = imOut(ii,jj);
631  xMax = ii;
632  yMax = jj;
633  }
634  }
635  }
636  }
637 
638  return 0;
639 }
640 
641 
642 /// Smooth an image using the median in a rectangular box. Also Determines the location and value of the highest pixel in the smoothed image.
643 /** Calculates the median value in a rectangular box of imIn, of size medianFullSidth X medianFullWidth and stores it in the corresonding center pixel of imOut.
644  * Does not smooth the 0.5*medianFullwidth rows and columns of the input image, and the values of these pixels are not
645  * changed in imOut (i.e. you should 0 them before the call).
646  *
647  * imOut is not re-allocated.
648  *
649  * Also determines the location and value of the maximum pixel. This is a negligble overhead compared to the median operation.
650  *
651  *
652  * \tparam imageTout is an eigen-like image array
653  * \tparam imageTin is an eigen-like image array
654  *
655  * \returns 0 on success
656  * \returns -1 on error.
657  *
658  * \ingroup image_filters_average
659  */
660 template<typename imageTout, typename imageTin>
661 int medianSmooth( imageTout & imOut, ///< [out] the smoothed image. Not re-allocated, and the edge pixels are not modified.
662  int & xMax, ///< [out] the x-locatioin of the max pixel
663  int & yMax, ///< [out] the y-locatioin of the max pixel
664  typename imageTout::Scalar & pMax, ///< [out] the value of the max pixel
665  const imageTin & imIn, ///< [in] the image to smooth
666  int medianFullWidth ///< [in] the full-width of the smoothing box
667  )
668 {
669  typedef typename imageTout::Scalar scalarT;
670 
671  int buff = 0.5*medianFullWidth;
672 
673  std::vector<scalarT> pixs(medianFullWidth*medianFullWidth);
674 
675  pMax = std::numeric_limits<scalarT>::lowest();
676 
677  for(int jj=buff; jj< imIn.cols()-buff; ++jj)
678  {
679  for(int ii=buff; ii < imIn.rows()-buff; ++ii)
680  {
681  int n=0;
682  for(int ll = jj-buff; ll < jj+buff+1; ++ll)
683  {
684  for(int kk = ii-buff; kk < ii+buff+1; ++kk)
685  {
686  pixs[n] = imIn(kk,ll);
687  ++n;
688  }
689  }
690 
691  imOut(ii,jj) = math::vectorMedianInPlace(pixs);
692  if(imOut(ii,jj) > pMax)
693  {
694  pMax = imOut(ii,jj);
695  xMax = ii;
696  yMax = jj;
697  }
698  }
699  }
700 
701  return 0;
702 }
703 
704 /// Smooth an image using the median in a rectangular box.
705 /** Calculates the median value in a rectangular box of imIn, of size medianFullSidth X medianFullWidth and stores it in the corresponding center pixel of imOut.
706  * Does not smooth the outer 0.5*medianFullwidth rows and columns of the input image, and the values of these pixels are not
707  * changed in imOut (i.e. you should 0 them before the call).
708  *
709  * imOut is not re-allocated.
710  *
711  * \overload
712  *
713  * \tparam imageTout is an eigen-like image array
714  * \tparam imageTin is an eigen-like image array
715  *
716  * \returns 0 on success
717  * \returns -1 on error.
718  *
719  * \ingroup image_filters_average
720  */
721 template<typename imageTout, typename imageTin>
722 int medianSmooth( imageTout & imOut, ///< [out] the smoothed image. Not re-allocated, and the edge pixels are not modified.
723  const imageTin & imIn, ///< [in] the image to smooth
724  int medianFullWidth ///< [in] the full-width of the smoothing box
725  )
726 {
727  int xMax, yMax;
728  typename imageTout::Scalar pMax;
729  return medianSmooth(imOut, xMax, yMax, pMax, imIn, medianFullWidth);
730 }
731 
732 template<typename eigenImT>
733 void rowEdgeMedSubtract( eigenImT & im, ///< The image to filter
734  int ncols ///< The number of columns on each side of the image to use as the reference
735  )
736 {
737  typedef typename eigenImT::Scalar realT;
738 
739  std::vector<realT> edge(2*ncols);
740  for(int rr=0; rr<im.rows(); ++rr)
741  {
742  for(int cc=0; cc< ncols; ++cc)
743  {
744  edge[cc] = im(rr,cc);
745  }
746 
747  for(int cc=0; cc< ncols; ++cc)
748  {
749  edge[ncols+cc] = im(rr,im.cols()-ncols + cc);
750  }
751 
752  realT med = math::vectorMedian(edge);
753  im.row(rr) -= med;
754  }
755 
756  return;
757 }
758 
759 template<typename eigenImT>
760 void colEdgeMedSubtract( eigenImT & im, ///< The image to filter
761  int nrows ///< The number of rows on each side of the image to use as the reference
762  )
763 {
764  typedef typename eigenImT::Scalar realT;
765 
766  std::vector<realT> edge(2*nrows);
767  for(int cc=0; cc<im.cols(); ++cc)
768  {
769  for(int rr=0; rr< nrows; ++rr)
770  {
771  edge[rr] = im(rr,cc);
772  }
773 
774  for(int rr=0; rr< nrows; ++rr)
775  {
776  edge[nrows+rr] = im(im.rows()-nrows + rr, cc);
777  }
778 
779  realT med = math::vectorMedian(edge);
780  im.col(cc) -= med;
781  }
782 
783  return;
784 }
785 
786 //------------ Radial Profile --------------------//
787 
788 template<typename floatT>
789 struct radval
790 {
791  floatT r;
792  floatT v;
793 };
794 
795 template<typename floatT>
796 struct radvalRadComp
797 {
798  bool operator()(radval<floatT> rv1, radval<floatT> rv2)
799  {
800  return (rv1.r < rv2.r);
801  }
802 };
803 
804 template<typename floatT>
805 struct radvalValComp
806 {
807  bool operator()(radval<floatT> rv1, radval<floatT> rv2)
808  {
809  return (rv1.v < rv2.v);
810  }
811 };
812 
813 
814 /// Calculate the the radial profile
815 /** The median radial profile is calculated by rebinning to a 1 pixel grid.
816  *
817  *
818  * \tparam vecT the std::vector-like type to contain the profile
819  * \tparam eigenImT1 the eigen-array-like type of the input image
820  * \tparam eigenImT2 the eigen-array-like type of the radius and mask image
821  * \tparam eigenImT3 the eigen-array-like type of the mask image
822  *
823  * \ingroup rad_prof
824  */
825 template<typename vecT, typename eigenImT1, typename eigenImT2, typename eigenImT3>
826 void radprof( vecT & rad, ///< [out] the radius points for the profile. Should be empty.
827  vecT & prof, ///< [out] the median image value at the corresponding radius. Should be empty.
828  const eigenImT1 & im, ///< [in] the image of which to calculate the profile
829  const eigenImT2 & radim, ///< [in] image of radius values per pixel
830  const eigenImT3 * mask, ///< [in] [optional] 1/0 mask, only pixels with a value of 1 are included in the profile. Set to 0 to not use.
831  bool mean = false, ///< [in] [optional] set to true to use the mean. If false (default) the median is used.
832  typename eigenImT1::Scalar minr = 0
833  )
834 {
835  typedef typename eigenImT1::Scalar floatT;
836 
837  int dim1 = im.rows();
838  int dim2 = im.cols();
839 
840  floatT maxr;
841  //floatT minr;
842 
843  size_t nPix;
844  if(mask)
845  {
846  nPix = mask->sum();
847  }
848  else
849  {
850  nPix = dim1*dim2;
851  }
852 
853  /* A vector of radvals will be sorted, then binned*/
854  std::vector<radval<floatT> > rv(nPix);
855 
856  size_t i=0;
857 
858  for(int c=0;c<im.cols();++c)
859  {
860  for(int r=0;r<im.rows();++r)
861  {
862  if(mask)
863  {
864  if( (*mask)(r,c) == 0) continue;
865  }
866 
867  rv[i].r = radim(r,c);
868  rv[i].v = im(r,c);
869  ++i;
870  }
871  }
872 
873  sort(rv.begin(), rv.end(), radvalRadComp<floatT>());
874 
875 // for(auto it=rv.begin(); it != rv.end(); ++it)
876 // {
877 // std::cout << it->r << " " << it->v << "\n";
878 // }
879 //
880 // exit(0);
881 
882 
883  if(minr == 0) minr = rv[0].r;
884  maxr = rv.back().r;
885 
886  /*Now bin*/
887  floatT dr = 1.0;
888  floatT r0 = minr;
889  floatT r1 = minr + dr;
890  int i1=0, i2, n;
891 
892  floatT med;
893 
894  while(r1 < maxr)
895  {
896  while(rv[i1].r < r0) ++i1;
897  i2 = i1;
898  while(rv[i2].r <= r1) ++i2;
899 
900  if(mean)
901  {
902  med = 0;
903  for(int in=i1; in<i2; ++in) med += rv[in].v;
904  med /= (i2-i1);
905  }
906  else
907  {
908  n = 0.5*(i2-i1);
909 
910  std::nth_element(rv.begin()+i1, rv.begin()+i1+n, rv.begin()+i2, radvalValComp<floatT>());
911 
912  med = (rv.begin()+i1+n)->v;
913 
914  //Average two points if even number of points
915  if((i2-i1)%2 == 0)
916  {
917  med = 0.5*(med + (*std::max_element(rv.begin()+i1, rv.begin()+i1+n, radvalValComp<floatT>())).v);
918  }
919  }
920 
921  rad.push_back(.5*(r0+r1));
922  prof.push_back(med);
923  i1 = i2;
924  r0 += dr;
925  r1 += dr;
926  }
927 
928 
929 
930 }
931 
932 /// Calculate the the radial profile
933 /** The median radial profile is calculated by rebinning to a 1 pixel grid.
934  * This version calculates a centered radius image.
935  *
936  * \overload
937  *
938  * \tparam vecT the std::vector-like type to contain the profile
939  * \tparam eigenImT1 the eigen-array-like type of the input image
940  * \tparam eigenImT2 the eigen-array-like type of the radius and mask image
941  * \tparam eigenImT3 the eigen-array-like type of the mask image
942  *
943  * \ingroup rad_prof
944  */
945 template<typename vecT, typename eigenImT1, typename eigenImT2>
946 void radprof( vecT & rad, ///< [out] the radius points for the profile. Should be empty.
947  vecT & prof, ///< [out] the median image value at the corresponding radius. Should be empty.
948  const eigenImT1 & im, ///< [in] the image of which to calculate the profile
949  const eigenImT2 & mask, ///< [in] 1/0 mask, only pixels with a value of 1 are included in the profile
950  bool mean = false ///< [in] [optional] set to true to use the mean. If false (default) the median is used.
951  )
952 {
954  radim.resize(im.cols(), im.rows());
955 
956  radiusImage(radim);
957 
958  radprof(rad, prof, im, radim, &mask, mean);
959 }
960 
961 /// Calculate the the radial profile
962 /** The median radial profile is calculated by rebinning to a 1 pixel grid.
963  * This version calculates a centered radius image.
964  *
965  * \overload
966  *
967  * \tparam vecT the std::vector-like type to contain the profile
968  * \tparam eigenImT1 the eigen-array-like type of the input image
969  *
970  * \ingroup rad_prof
971  */
972 template<typename vecT, typename eigenImT1>
973 void radprof( vecT & rad, ///< [out] the radius points for the profile. Should be empty.
974  vecT & prof, ///< [out] the median image value at the corresponding radius. Should be empty.
975  const eigenImT1 & im, ///< [in] the image of which to calculate the profile
976  bool mean = false, ///< [in] [optional] set to true to use the mean. If false (default) the median is used.
977  double dr = 1
978  )
979 {
981  radim.resize(im.cols(), im.rows());
982 
983  radiusImage(radim);
984 
985  radprof(rad, prof, im, radim, (eigenImage<typename eigenImT1::Scalar> *) nullptr, mean, dr);
986 }
987 
988 ///Form a radial profile image, and optionally subtract it from the input
989 /** The radial profile is calculated using linear interpolation on a 1 pixel grid
990  *
991  *
992  * \tparam radprofT the eigen array type of the output
993  * \tparam eigenImT1 the eigen array type of the input image
994  * \tparam eigenImT2 the eigen array type of the radius image
995  * \tparam eigenImT3 the eigen array type of the mask image
996  *
997  * \ingroup rad_prof
998  */
999 template<typename radprofT, typename eigenImT1, typename eigenImT2, typename eigenImT3>
1000 void radprofim( radprofT & radprofIm, ///< [out] the radial profile image. This will be resized.
1001  eigenImT1 & im, ///< [in the image to form the profile of.
1002  const eigenImT2 & rad, ///< [in] an array of radius values for each pixel
1003  const eigenImT3 * mask, ///< [in] [optional 1/0 mask, only pixels with a value of 1 are included in the profile. Can be nullptr.
1004  bool subtract, ///< [in] if true, then on ouput im will have had its radial profile subtracted.
1005  bool mean = false ///< [in] [optional] set to true to use the mean. If false (default) the median is used.
1006  )
1007 {
1008 
1009 
1010  std::vector<double> med_r, med_v; //Must be double for GSL interpolator
1011 
1012  radprof(med_r, med_v, im, rad, mask);
1013 
1014  /* And finally, interpolate onto the radius image */
1015  radprofIm.resize(im.rows(), im.cols() );
1016 
1018 
1019  for(int c=0;c<im.cols();++c)
1020  {
1021  for(int r=0;r<im.rows();++r)
1022  {
1023  if(mask)
1024  {
1025  if( (*mask)(r,c) == 0)
1026  {
1027  radprofIm(r,c) = 0;
1028  continue;
1029  }
1030  }
1031 
1032  radprofIm(r,c) = interp( ((double) rad(r,c)) );
1033  if(subtract) im(r,c) -= radprofIm(r,c);
1034  }
1035  }
1036 
1037 }
1038 
1039 
1040 
1041 
1042 ///Form a radial profile image, and optionally subtract it from the input
1043 /** The radial profile is calculated using linear interpolation on a 1 pixel grid.
1044  * This version calculates a centered radius image.
1045  *
1046  * \tparam radprofT the eigen array type of the output
1047  * \tparam eigenImT the eigen array type of the input
1048  *
1049  * \ingroup rad_prof
1050  */
1051 template<typename radprofT, typename eigenImT>
1052 void radprofim( radprofT & radprof, ///< [out] the radial profile image. This will be resized.
1053  eigenImT & im, ///< [in] the image to form the profile of.
1054  bool subtract = false, ///< [in] [optional] if true, then on ouput im will have had its radial profile subtracted.
1055  bool mean = false ///< [in] [optional] set to true to use the mean. If false (default) the median is used.
1056  )
1057 {
1059  rad.resize(im.rows(), im.cols());
1060 
1061  radiusImage(rad);
1062 
1063  radprofim(radprof, im, rad, (eigenImage<typename eigenImT::Scalar> *) nullptr, subtract);
1064 
1065 }
1066 
1067 
1068 /** \ingroup std_prof
1069  * @{
1070  */
1071 
1072 /// Form a standard deviation image, and optionally divide the input by it to form a S/N map.
1073 /** The standard deviation profile is calculated using linear interpolation on a 1 pixel grid
1074  *
1075  * \tparam eigenImT the eigen array type of the output and non-reference images. Each image input can be a different type to allow references, etc.
1076  *
1077  */
1078 template<typename eigenImT, typename eigenImT1, typename eigenImT2, typename eigenImT3>
1079 void stddevImage( eigenImT & stdIm, ///< [out] the standard deviation image. This will be resized.
1080  const eigenImT1 & im, ///< [in] the image to form the standard deviation profile of, never altered.
1081  const eigenImT2 & rad, ///< [in] array of radius values
1082  const eigenImT3 & mask, ///< [in] a 1/0 mask. 0 pixels are excluded from the std-dev calculations.
1083  typename eigenImT::Scalar minRad, ///< [in] the minimum radius to analyze
1084  typename eigenImT::Scalar maxRad, ///< [in] the maximum radius to analyze
1085  bool divide ///< [in] if true, the output is the input image is divided by the std-dev profile, i.e. a S/N map. default is false.
1086  )
1087 {
1088  typedef typename eigenImT::Scalar floatT;
1089 
1090  int dim1 = im.cols();
1091  int dim2 = im.rows();
1092 
1093  floatT mr = rad.maxCoeff();
1094 
1095  /* A vector of radvals will be sorted, then binned*/
1096  std::vector<radval<floatT> > rv(dim1*dim2);
1097 
1098  for(int i=0;i<rv.size();++i)
1099  {
1100  if(mask(i) == 0) continue;
1101 
1102  rv[i].r = rad(i);
1103  rv[i].v = im(i);
1104  }
1105 
1106  sort(rv.begin(), rv.end(), radvalRadComp<floatT>());
1107 
1108  /*Now bin*/
1109  floatT dr = 1;
1110  floatT r0 = 0;
1111  floatT r1 = dr;
1112  int i1=0, i2, n;
1113 
1114  floatT stdVal;
1115 
1116  std::vector<double> std_r, std_v;
1117 
1118  while(r1 < mr)
1119  {
1120  while(rv[i1].r < r0 && i1 < rv.size())
1121  {
1122  ++i1;
1123  }
1124  if(i1 == rv.size())
1125  {
1126  break;
1127  }
1128 
1129  i2 = i1;
1130  while(rv[i2].r <= r1 && i2 < rv.size()) ++i2;
1131  n = 0.5*(i2-i1);
1132 
1133  std::vector<double> vals;
1134 
1135  for(int i=i1; i< i2; ++i)
1136  {
1137  vals.push_back( rv[i].v);
1138  }
1139 
1140  std_r.push_back(.5*(r0+r1));
1141 
1142  std_v.push_back( std::sqrt(math::vectorVariance(vals)) ) ;
1143  i1 = i2;
1144  r0 += dr;
1145  r1 += dr;
1146  }
1147 
1148  std::cerr << std_r.size() << " " << std_v.size() << "\n";
1149 
1150  /* And finally, interpolate onto the radius image */
1151  stdIm.resize(dim1, dim2);
1153 
1154  for(int i=0;i<dim1;++i)
1155  {
1156  for(int j=0;j<dim2;++j)
1157  {
1158  if(rad(i,j) < minRad || rad(i,j) > maxRad)
1159  {
1160  stdIm(i,j) = 0;
1161  }
1162  else
1163  {
1164  stdIm(i,j) = interp( ((double) rad(i,j)) );
1165  if(divide) stdIm(i,j) = im(i,j) / stdIm(i,j);
1166  }
1167  }
1168  }
1169 
1170 }
1171 
1172 /// Form a standard deviation image, and optionally divide the input by it to form a S/N map.
1173 /** The standard deviation profile is calculated using linear interpolation on a 1 pixel grid
1174  *
1175  * This version creates a radius map on each call, and calls the above version. This should not
1176  * be used for repeated alls, rather create a radius map ahead of time.
1177  *
1178  * \overload
1179  *
1180  * \tparam eigenImT the eigen array type of the output and non-reference images
1181  *
1182  */
1183 template<typename eigenImT, typename eigenImT1, typename eigenImT2>
1184 void stddevImage( eigenImT & stdIm, ///< [out] the standard deviation image. This will be resized.
1185  const eigenImT1 & im, ///< [in] the image to form the standard deviation profile of, never altered.
1186  const eigenImT2 & mask, ///< [in] a 1/0 mask. 0 pixels are excluded from the std-dev calculations.
1187  typename eigenImT::Scalar minRad, ///< [in] the minimum radius to analyze
1188  typename eigenImT::Scalar maxRad, ///< [in] the maximum radius to analyze
1189  bool divide = false ///< [in] [optional] if true, the output is the input image is divided by the std-dev profile, i.e. a S/N map. default is false.
1190  )
1191 {
1192  int dim1 = im.cols();
1193  int dim2 = im.rows();
1194 
1196  rad.resize(dim1, dim2);
1197 
1198  radiusImage(rad);
1199  stddevImage(stdIm, im, rad, mask, minRad, maxRad, divide );
1200 
1201 }
1202 
1203 /// Form a standard deviation image for each imamge in a cube, and optionally divide the input by it forming a S/N map cube.
1204 /** The standard deviation profile is calculated using linear interpolation on a 1 pixel grid
1205  *
1206  *
1207  * \tparam eigencubeT is the eigen cube type of the input and output cubes.
1208  * \tparam eigenImT the eigen array type of the output and non-reference images.
1209  *
1210  */
1211 template<typename eigenCubeT, typename eigenCubeT1, typename eigenImT>
1212 void stddevImageCube( eigenCubeT & stdImc, ///< [out] the standard deviation image cube. This will be resized.
1213  const eigenCubeT1 & imc, ///< [in] the image cube to form the standard deviation profile of.
1214  const eigenImT & mask, ///< [in] a 1/0 mask. 0 pixels are excluded from the std-dev calculations.
1215  typename eigenImT::Scalar minRad, ///< [in] the minimum radius to analyze
1216  typename eigenImT::Scalar maxRad, ///< [in] the maximum radius to analyze
1217  bool divide = false ///< [in] [optional] if true, the output is the input image is divided by the std-dev profile, i.e. a S/N map. default is false.
1218  )
1219 {
1220  int dim1 = imc.cols();
1221  int dim2 = imc.rows();
1222 
1224  rad.resize(dim1, dim2);
1225 
1226  radiusImage(rad);
1227 
1228  stdImc.resize(imc.rows(), imc.cols(), imc.planes());
1229 
1230  //#pragma omp parallel for
1231  for(int i=0; i< imc.planes(); ++i)
1232  {
1234 
1235  im = imc.image(i);
1236 
1237  stddevImage(stdIm, im, rad, mask, minRad, maxRad, divide );
1238 
1239  stdImc.image(i) = stdIm;
1240 
1241  }
1242 }
1243 
1244 ///@}
1245 
1246 } //namespace improc
1247 } //namespace mx
1248 
1249 #endif //__imageFilters_hpp__
1250 
1251 
mxException for a size error
Class to manage interpolation using the GSL interpolation library.
constexpr units::realT c()
The speed of light.
Definition: constants.hpp:60
constexpr units::realT h()
Planck Constant.
Definition: constants.hpp:91
Eigen::Array< scalarT, -1, -1 > eigenImage
Definition of the eigenImage type, which is an alias for Eigen::Array.
Definition: eigenImage.hpp:44
realT dtor(realT q)
Convert from degrees to radians.
Definition: geo.hpp:132
int medianSmooth(imageTout &imOut, const imageTin &imIn, int medianFullWidth)
Smooth an image using the median in a rectangular box.
int meanSmooth(imageTout &imOut, int &xMax, int &yMax, typename imageTout::Scalar &pMax, const imageTin &imIn, int meanFullWidth, bool rejectMinMax=false)
Smooth an image using the mean in a rectangular box, optionally rejecting the highest and lowest valu...
void filterImage(imageOutT &fim, imageInT im, kernelT kernel, int maxr=0)
Filter an image with a mean kernel.
void radiusImage(eigenT &m, typename eigenT::Scalar xc, typename eigenT::Scalar yc, typename eigenT::Scalar scale=1)
Fills in the cells of an Eigen 2D Array with their radius from the center.
Definition: imageMasks.hpp:49
void radprof(vecT &rad, vecT &prof, const eigenImT1 &im, bool mean=false, double dr=1)
Calculate the the radial profile.
void radprofim(radprofT &radprof, eigenImT &im, bool subtract=false, bool mean=false)
Form a radial profile image, and optionally subtract it from the input.
void stddevImage(eigenImT &stdIm, const eigenImT1 &im, const eigenImT2 &mask, typename eigenImT::Scalar minRad, typename eigenImT::Scalar maxRad, bool divide=false)
Form a standard deviation image, and optionally divide the input by it to form a S/N map.
void stddevImageCube(eigenCubeT &stdImc, const eigenCubeT1 &imc, const eigenImT &mask, typename eigenImT::Scalar minRad, typename eigenImT::Scalar maxRad, bool divide=false)
Form a standard deviation image for each imamge in a cube, and optionally divide the input by it form...
vectorT::value_type vectorMedianInPlace(vectorT &vec)
Calculate median of a vector in-place, altering the vector.
valueT vectorVariance(const valueT *vec, size_t sz, valueT mean)
Calculate the variance of a vector relative to a supplied mean value.
vectorT::value_type vectorMedian(const vectorT &vec, vectorT *work=0)
Calculate median of a vector, leaving the vector unaltered.
void colEdgeMedSubtract(eigenImT &im, int nrows)
void rowEdgeMedSubtract(eigenImT &im, int ncols)
Declares and defines functions to work with image masks.
The mxlib c++ namespace.
Definition: mxError.hpp:107
Azimuthally variable boxcare kernel.
void setMaxWidth()
Sets the max width based on the configured az and rad widths.
arithT m_maxAz
the maximum half-width of the averging box in the azimuthal direction, in degrees....
azBoxKernel(arithT radWidth, arithT azWidth, arithT maxAz)
arithT m_radWidth
the half-width of the averaging box, in the radial direction, in pixels.
arithT m_azWidth
the half-width of the averaging box, in the azimuthal direction, in pixels.
azBoxKernel(arithT radWidth, arithT azWidth)
Symetric Gaussian smoothing kernel.