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