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