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