mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
imageUtils.hpp
Go to the documentation of this file.
1/** \file imageUtils.hpp
2 * \author Jared R. Males
3 * \brief Header for the image processing utilities
4 * \ingroup image_processing_files
5 *
6 */
7
8//***********************************************************************//
9// Copyright 2020 Jared R. Males (jaredmales@gmail.com)
10//
11// This file is part of mxlib.
12//
13// mxlib is free software: you can redistribute it and/or modify
14// it under the terms of the GNU General Public License as published by
15// the Free Software Foundation, either version 3 of the License, or
16// (at your option) any later version.
17//
18// mxlib is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
25//***********************************************************************//
26
27#ifndef improc_imageUtils_hpp
28#define improc_imageUtils_hpp
29
30#include <cmath>
31#include <limits>
32#include <type_traits>
33
35
36#include "imageTransforms.hpp"
37
38namespace mx
39{
40namespace improc
41{
42
43/// Get the mxlib standard center coordinate of an image
44/**
45 * \returns 0.5*(rows_cols-1)
46 */
47template <typename realT = double>
48realT imCen( int rows_cols /**< [in] the number of rows or columns in the image */ )
49{
50 return 0.5 * ( 1.0 * rows_cols - 1 );
51}
52
53/// Get the mxlib standard center x-coordinate of an image
54/**
55 * \returns 0.5*(rows-1)
56 */
57template <typename realT = double, typename imT>
58realT imCenX( const imT &im /**< [in] the image to find the center of */ )
59{
60 return imCen<realT>( im.rows() );
61}
62
63/// Get the mxlib standard center y-coordinate of an image
64/**
65 * \returns 0.5*(cols-1)
66 */
67template <typename realT = double, typename imT>
68realT imCenY( const imT &im /**< [in] the image to find the center of */ )
69{
70 return imCen<realT>( im.cols() );
71}
72
73/** \ingroup image_utils
74 *@{
75 */
76
77/// Return the configured invalid image-pixel value.
78/** The default is a quiet NaN so FITS and image-display tools recognize invalid pixels. Defining
79 * `MXLIB_INVALID_NUMBER_VALUE` at compile time selects that finite value instead. CMake propagates its
80 * `MXLIB_INVALID_NUMBER_VALUE` cache setting through the installed pkg-config metadata.
81 *
82 * \returns the invalid-pixel value for `realT`
83 */
84template <typename realT>
85constexpr realT invalidNumber()
86{
87 static_assert( std::is_floating_point_v<realT>, "invalidNumber requires a floating-point type" );
88
89#ifdef MXLIB_INVALID_NUMBER_VALUE
90 return static_cast<realT>( MXLIB_INVALID_NUMBER_VALUE );
91#else
92 return std::numeric_limits<realT>::quiet_NaN();
93#endif
94}
95
96/// Check whether a value represents an invalid image pixel.
97/** Detects the configured mxlib invalid-number value as well as NaN and positive or negative infinity. The
98 * non-finite classification remains reliable under finite-math-only optimization.
99 *
100 * \returns true if value is invalid, otherwise false.
101 */
102template <typename realT>
103bool isInvalidPixel( realT value /**< [in] value to test */ )
104{
105 static_assert( std::is_floating_point_v<realT>, "isInvalidPixel requires a floating-point type" );
106
107#ifdef MXLIB_INVALID_NUMBER_VALUE
108 return value == invalidNumber<realT>() || !math::isFinite( value );
109#else
110 return !math::isFinite( value );
111#endif
112}
113
114/// Reflect pixel coordinates across the given center pixel.
115/**
116 */
117template <typename realT>
118int reflectImageCoords( int &x1, ///< [out] the reflected x coordinate
119 int &y1, ///< [out] the reflected y coordinate
120 int x0, ///< [in] the input x coordinate
121 int y0, ///< [in] the input y coordinate
122 realT xc, ///< [in] the center pixel x coordinate
123 realT yc ///< [in] the center pixel y coordinate
124)
125{
126 x1 = xc - ( x0 - xc );
127 y1 = yc - ( y0 - yc );
128
129 return 0;
130}
131
132/// @}
133
134/// Zero any NaNs in an image
135/**
136 * \overload
137 *
138 * \ingroup eigen_image_processing
139 */
140template <class imageT, typename valueT>
141void zeroNaNs( imageT &im, ///< [in.out] image which will have any NaN pixels set to zero
142 valueT val ///< [in] [optional] The value to set NaN pixels too. Default is 0.
143)
144{
145 for( int c = 0; c < im.cols(); ++c )
146 {
147 for( int r = 0; r < im.rows(); ++r )
148 {
149 if( isInvalidPixel( im( r, c ) ) )
150 {
151 im( r, c ) = val;
152 }
153 }
154 }
155}
156
157/// Zero any NaNs in an image
158/**
159 * \ingroup eigen_image_processing
160 */
161template <class imageT>
162void zeroNaNs( imageT &im /**< [in.out] image which will have any NaN pixels set to zero */ )
163{
164 typename imageT::Scalar zero = 0;
166}
167
168/// Zero any NaNs in an image cube
169/** This version fills in a mask with 1s where there were nans, 0s elsewhere.
170 *
171 * \ingroup eigen_image_processing
172 */
173template <class cubeT, class maskCubeT>
174void zeroNaNCube( cubeT &imc, /**< [in.out] cube which will have any NaN pixels set to zero */
175 maskCubeT *mask /**< [out] a 1/0 mask with 1 indicating which pixels where nan */
176)
177{
178 if( mask )
179 {
180 mask->resize( imc.rows(), imc.cols(), imc.planes() );
181 mask->setZero();
182 }
183
184 for( int p = 0; p < imc.planes(); ++p )
185 {
186 for( int c = 0; c < imc.cols(); ++c )
187 {
188 for( int r = 0; r < imc.rows(); ++r )
189 {
190 if( isInvalidPixel( imc.image( p )( r, c ) ) )
191 {
192 imc.image( p )( r, c ) = 0;
193 if( mask )
194 {
195 ( *mask ).image( p )( r, c ) = 1;
196 }
197 }
198 }
199 }
200 }
201}
202
203/// Zero any NaNs in an image cube
204/**
205 * \ingroup eigen_image_processing
206 */
207template <class cubeT>
208void zeroNaNCube( cubeT &imc /**< [in.out] cube which will have any NaN pixels set to zero */ )
209{
210 return zeroNaNCube<cubeT, cubeT>( imc, nullptr );
211}
212
213/// Calculate the mean value of an image
214/**
215 *
216 * \returns the mean of the input image
217 *
218 * \ingroup eigen_image_processing
219 */
220template <class calcT, class imageT>
221calcT imageMean( imageT &im /**< [in] the image of which to calculate the mean*/ )
222{
223 return static_cast<calcT>( im.sum() ) / ( im.rows() * im.cols() );
224}
225
226/// Calculate the mean value of an image over a mask
227/**
228 * \returns the mean of the input image in the masked region
229 *
230 * \ingroup eigen_image_processing
231 */
232template <class calcT, class imageT, class maskT>
233calcT imageMean( imageT &im, /**< [in] the image of which to calculate the mean*/
234 const maskT &mask /**< [in] a 1/0 mask where 1 defines the good pixels */ )
235{
236 return static_cast<calcT>( ( im * mask ).sum() ) / ( mask.sum() );
237}
238
239/// Calculate the variance of an image given its mean
240/**
241 *
242 * \returns the variance of the input image
243 *
244 * \ingroup eigen_image_processing
245 */
246template <typename calcT, class imageT>
247calcT imageVariance( imageT &im /**< [in] the image of which to calculate the variance*/,
248 calcT mn /**< [in] the mean value of the image w.r.t. which to calcualate the variance */
249)
250{
251 return ( im.template cast<calcT>() - mn ).square().sum() / ( im.rows() * im.cols() );
252}
253
254/// Calculate the variance of an image given its mean
255/**
256 *
257 * \returns the variance of the input image
258 *
259 * \ingroup eigen_image_processing
260 */
261template <typename calcT, class imageT, class maskT>
262calcT imageVariance( imageT &im /**< [in] the image of which to calculate the variance*/,
263 calcT mn, /**< [in] the mean value of the image w.r.t. which to calcualate the variance */
264 const maskT &mask /**< [in] a 1/0 mask where 1 defines the good pixels */
265)
266{
267 return ( im.template cast<calcT>() * mask - mn ).square().sum() / ( mask.sum() );
268}
269
270/// Calculate the median of an Eigen-like array.
271/** Calculates the median of the entire array, allowing for some pixels to be ignored using a mask.
272 * Working memory can be retained between calls.
273 *
274 * \tparam imageT is an Eigen-like type
275 * \tparam maskT is an Eigen-like type
276 *
277 * \returns the median of the unmasked pixels of mat, using \ref mx::math::vectorMedianInPlace().
278 *
279 * \ingroup eigen_image_processing
280 *
281 */
282template <typename imageT, typename maskT = imageT>
283typename imageT::Scalar
284imageMedian( const imageT &mat, /**< [in] the image to take the median of*/
285 const maskT *mask, /**< [in] if non-0, a 1/0 mask where 0 pixels are ignored.*/
286 std::vector<typename imageT::Scalar> *work = 0 /**< [in] [optional] working memory
287 can be retained
288 and re-passed. Is resized.*/
289)
290{
291 typename imageT::Scalar med;
292
293 bool localWork = false;
294 if( work == 0 )
295 {
296 work = new std::vector<typename imageT::Scalar>;
297 localWork = true;
298 }
299
300 int sz = mat.size();
301
302 if( mask )
303 {
304 sz = mask->sum();
305 }
306
307 work->resize( sz );
308
309 if( mask )
310 {
311 int ii = 0;
312 for( int i = 0; i < mat.rows(); ++i )
313 {
314 for( int j = 0; j < mat.cols(); ++j )
315 {
316 if( ( *mask )( i, j ) == 0 )
317 {
318 continue;
319 }
320 ( *work )[ii] = mat( i, j );
321 ++ii;
322 }
323 }
324 }
325 else
326 {
327 int ii = 0;
328 for( int i = 0; i < mat.rows(); ++i )
329 {
330 for( int j = 0; j < mat.cols(); ++j )
331 {
332 ( *work )[ii] = mat( i, j );
333 ++ii;
334 }
335 }
336 }
337
338 med = math::vectorMedianInPlace( *work );
339
340 if( localWork )
341 {
342 delete work;
343 }
344
345 return med;
346}
347
348/// Calculate the median of an Eigen-like array.
349/** Calculates the median of the entire array.
350 * Working memory can be retained between calls.
351 *
352 * \tparam imageT is an Eigen-like type
353 *
354 * \returns the median of the unmasked pixels of mat, using \ref mx::math::vectorMedianInPlace().
355 *
356 * \ingroup eigen_image_processing
357 *
358 */
359template <typename imageT>
360typename imageT::Scalar imageMedian( const imageT &mat, /**< [in] the image to take the median of*/
361 std::vector<typename imageT::Scalar> *work = 0 /**< [in] [optional] working memory
362 can be retained and re-passed.*/
363)
364{
365 return imageMedian( mat, static_cast<Eigen::Array<typename imageT::Scalar, -1, -1> *>( nullptr ), work );
366}
367
368/// Calculate the center of light of an image
369/** Note that the sum of the image should be > 0.
370 *
371 * \ingroup eigen_image_processing
372 */
373template <typename imageT>
374int imageCenterOfLight( typename imageT::Scalar &x, ///< [out] the x coordinate of the center of light [pixels]
375 typename imageT::Scalar &y, ///< [out] the y coordinate of hte center of light [pixels]
376 const imageT &im ///< [in] the image to centroid
377)
378{
379 x = 0;
380 y = 0;
381
382 typename imageT::Scalar sum = im.sum();
383
384 if( sum == 0 )
385 {
386 x = 0.5 * ( im.rows() - 1.0 );
387 y = 0.5 * ( im.cols() - 1.0 );
388 return 0;
389 }
390
391 for( int j = 0; j < im.cols(); ++j )
392 {
393 for( int i = 0; i < im.rows(); ++i )
394 {
395 x += ( i + 1 ) * im( i, j );
396 y += ( j + 1 ) * im( i, j );
397 }
398 }
399
400 x = x / sum - 1;
401 y = y / sum - 1;
402
403 return 0;
404}
405
406/// Find the maximum in an image at sub-pixel resolution by interpolation
407/** Uses imageMagnify() to expand the image to the desired scale. Because of the
408 * scaling used in imageMagnify, the desired scale may not be exact. As a result
409 * the actual scale is returned in scale_x and scale_y.
410 *
411 * \ingroup eigen_image_processing
412 */
413template <typename floatT, typename imageT, typename magImageT, typename transformT>
414int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, in pixels of the input image
415 floatT &y, ///< [out] the y-position of the maximum, in pixels of the input image
416 floatT &scale_x, ///< [in.out] the desired scale or resolution, in pixels < 1, in the x direction.
417 ///< On output contains the actual scale calculated.
418 floatT &scale_y, ///< [in.out] the desired scale or resolution, in pixels < 1, in the y direction.
419 ///< On output contains the actual scale calculated.
420 magImageT &magIm, ///< [in] the magnified image. This is used as working memory, will be resized.
421 const imageT &im, ///< [in] the image to find the maximum of
422 transformT trans ///< [in] the transform to use for interpolation
423)
424{
425 floatT magSize_x = ceil( ( im.rows() - 1.0 ) / scale_x ) + 1;
426 floatT magSize_y = ceil( ( im.cols() - 1.0 ) / scale_y ) + 1;
427
428 floatT mag_x = ( (floatT)magSize_x - 1.0 ) / ( (floatT)im.rows() - 1.0 );
429 floatT mag_y = ( (floatT)magSize_y - 1.0 ) / ( (floatT)im.cols() - 1.0 );
430
431 scale_x = 1.0 / mag_x;
432 scale_y = 1.0 / mag_y;
433
434 magIm.resize( magSize_x, magSize_y );
435
436 imageMagnify( magIm, im, trans );
437
438 int ix, iy;
439 magIm.maxCoeff( &ix, &iy );
440 x = ix * scale_x;
441 y = iy * scale_y;
442
443 return 0;
444}
445
446/// Find the maximum in an image at sub-pixel resolution by cubic convolution interpolation
447/** Uses imageMagnify() to expand the image to the desired scale. Because of the
448 * scaling used in imageMagnify, the desired scale may not be exact. As a result
449 * the actual scale is returned in scale_x and scale_y.
450 *
451 * \ingroup eigen_image_processing
452 */
453template <typename floatT, typename imageT, typename magImageT>
454int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, in pixels of the input image
455 floatT &y, ///< [out] the y-position of the maximum, in pixels of the input image
456 floatT &scale_x, ///< [in.out] the desired scale or resolution, in pixels < 1, in the x direction.
457 ///< On output contains the actual scale calculated.
458 floatT &scale_y, ///< [in.out] the desired scale or resolution, in pixels < 1, in the y direction.
459 ///< On output contains the actual scale calculated.
460 magImageT &magIm, ///< [in] the magnified image. This is used as working memory, will be resized.
461 const imageT &im ///< [in] the image to find the maximum of
462)
463{
464 return imageMaxInterp( x, y, scale_x, scale_y, magIm, im, cubicConvolTransform<typename imageT::Scalar>() );
465}
466
467/// Combine two images, each with their own mask defining good pixels.
468/** The combined image is made up of the pixels in im1 where mask1 is 1, and the pixels of im2 where mask2 is 1.
469 * If a pixel in both mask1 and mask2 has a value of 1, that pixel in the combo is the average of im1 and im2.
470 * All other pixels are set to 0 in the combined image.
471 *
472 * Separate template types are used for each argument to allow references, etc.
473 *
474 * \tparam imageT the eigen-like array type of the combined image
475 * \tparam imageT1 the eigen-like array type of image 1
476 * \tparam imageT2 the eigen-like array type of mask 1
477 * \tparam imageT3 the eigen-like array type of image 2
478 * \tparam imageT4 the eigen-like array type of mask 2
479 *
480 * \ingroup eigen_image_processing
481 */
482template <typename imageT, typename imageT1, typename imageT2, typename imageT3, typename imageT4>
483void combine2ImagesMasked( imageT &combo, ///< [out] the combined image. will be resized.
484 const imageT1 &im1, ///< [in] the first image
485 const imageT2 &mask1, ///< [in] the mask for the first image
486 const imageT3 &im2, ///< [in] the second image
487 const imageT4 &mask2 ///< [in] the mask for the second image
488)
489{
490 combo.resize( im1.rows(), im2.cols() );
491
492 for( int c = 0; c < combo.cols(); ++c )
493 {
494 for( int r = 0; r < combo.rows(); ++r )
495 {
496 if( mask1( r, c ) == 1 && mask2( r, c ) == 0 )
497 combo( r, c ) = im1( r, c );
498 else if( mask2( r, c ) == 1 & mask1( r, c ) == 0 )
499 combo( r, c ) = im2( r, c );
500 else if( mask1( r, c ) == 1 && mask2( r, c ) == 1 )
501 combo( r, c ) = 0.5 * ( im1( r, c ) + im2( r, c ) );
502 else
503 combo( r, c ) = 0;
504 }
505 }
506}
507
508/// Remove rows and columns
509/**
510 * \ingroup eigen_image_processing
511 */
512template <typename eigenT, typename eigenTin>
513void removeRowsAndCols( eigenT &out, const eigenTin &in, int st, int w )
514{
515 out.resize( in.rows() - w, in.cols() - w );
516
517 out.topLeftCorner( st, st ) = in.topLeftCorner( st, st );
518
519 out.bottomLeftCorner( in.rows() - ( st + w ), st ) = in.bottomLeftCorner( in.rows() - ( st + w ), st );
520
521 out.topRightCorner( st, in.cols() - ( st + w ) ) = in.topRightCorner( st, in.cols() - ( st + w ) );
522
523 out.bottomRightCorner( in.rows() - ( st + w ), in.cols() - ( st + w ) ) =
524 in.bottomRightCorner( in.rows() - ( st + w ), in.cols() - ( st + w ) );
525}
526
527/// Remove rows
528/**
529 * \ingroup eigen_image_processing
530 */
531template <typename eigenT, typename eigenTin>
532void removeRows( eigenT &out, const eigenTin &in, int st, int w )
533{
534 out.resize( in.rows() - w, in.cols() );
535
536 out.topLeftCorner( st, in.cols() ) = in.topLeftCorner( st, in.cols() );
537
538 out.bottomLeftCorner( in.rows() - ( st + w ), in.cols() ) =
539 in.bottomLeftCorner( in.rows() - ( st + w ), in.cols() );
540}
541
542/// Remove columns
543/**
544 * \ingroup eigen_image_processing
545 */
546template <typename eigenT, typename eigenTin>
547void removeCols( eigenT &out, const eigenTin &in, int st, int w )
548{
549 out.resize( in.rows(), in.cols() - w );
550
551 out.topLeftCorner( in.rows(), st ) = in.topLeftCorner( in.rows(), st );
552
553 out.topRightCorner( in.rows(), in.cols() - ( st + w ) ) = in.topRightCorner( in.rows(), in.cols() - ( st + w ) );
554}
555
556/** \ingroup image_utils
557 *@{
558 */
559
560/// Copy one image to another, with no transformation
561/** This is merely memcpy
562 *
563 * \returns dest
564 */
565void *imcpy( void *dest, ///< [out] the address of the first pixel in the destination image
566 void *src, ///< [in] the address of the first pixel in the source image
567 size_t width, ///< [in] the width in pixels of size szof
568 size_t height, ///< [in] the height in pixels of size szof
569 size_t szof ///< [in] the size in bytes of a one pixel
570);
571
572/// Copy one image to another, flipping up-down
573/** This is a reversed row-by-row memcpy
574 *
575 * \returns dest
576 */
577void *imcpy_flipUD( void *dest, ///< [out] the address of the first pixel in the destination image
578 void *src, ///< [in] the address of the first pixel in the source image
579 size_t width, ///< [in] the width in pixels of size szof
580 size_t height, ///< [in] the height in pixels of size szof
581 size_t szof ///< [in] the size in bytes of a one pixel
582);
583
584/// Copy one image to another, flipping left-right
585/**
586 *
587 * \returns dest
588 */
589void *imcpy_flipLR( void *dest, ///< [out] the address of the first pixel in the destination image
590 void *src, ///< [in] the address of the first pixel in the source image
591 size_t width, ///< [in] the width in pixels of size szof
592 size_t height, ///< [in] the height in pixels of size szof
593 size_t szof ///< [in] the size in bytes of a one pixel
594);
595
596/// Copy one image to another, flipping up-down and left-right
597/**
598 *
599 * \returns dest
600 */
601void *imcpy_flipUDLR( void *dest, ///< [out] the address of the first pixel in the destination image
602 void *src, ///< [in] the address of the first pixel in the source image
603 size_t width, ///< [in] the width in pixels of size szof
604 size_t height, ///< [in] the height in pixels of size szof
605 size_t szof ///< [in] the size in bytes of a one pixel
606);
607
608} // namespace improc
609} // namespace mx
610
611#endif // improc_imageUtils_hpp
Floating-point classification utilities that remain reliable under fast-math optimization.
void removeCols(eigenT &out, const eigenTin &in, int st, int w)
Remove columns.
void removeRows(eigenT &out, const eigenTin &in, int st, int w)
Remove rows.
void removeRowsAndCols(eigenT &out, const eigenTin &in, int st, int w)
Remove rows and columns.
int imageMaxInterp(floatT &x, floatT &y, floatT &scale_x, floatT &scale_y, magImageT &magIm, const imageT &im, transformT trans)
Find the maximum in an image at sub-pixel resolution by interpolation.
imageT::Scalar imageMedian(const imageT &mat, const maskT *mask, std::vector< typename imageT::Scalar > *work=0)
Calculate the median of an Eigen-like array.
calcT imageVariance(imageT &im, calcT mn)
Calculate the variance of an image given its mean.
int imageCenterOfLight(typename imageT::Scalar &x, typename imageT::Scalar &y, const imageT &im)
Calculate the center of light of an image.
void combine2ImagesMasked(imageT &combo, const imageT1 &im1, const imageT2 &mask1, const imageT3 &im2, const imageT4 &mask2)
Combine two images, each with their own mask defining good pixels.
calcT imageMean(imageT &im)
Calculate the mean value of an image.
void zeroNaNs(imageT &im, valueT val)
Zero any NaNs in an image.
void zeroNaNCube(cubeT &imc, maskCubeT *mask)
Zero any NaNs in an image cube.
bool isFinite(realT value)
Test whether a floating-point value is finite, including under finite-math-only optimization.
void imageMagnify(arrOutT &transim, const arrInT &im, transformT trans)
Magnify an image.
void * imcpy(void *dest, void *src, size_t width, size_t height, size_t szof)
Copy one image to another, with no transformation.
constexpr realT invalidNumber()
Return the configured invalid image-pixel value.
bool isInvalidPixel(realT value)
Check whether a value represents an invalid image pixel.
void * imcpy_flipLR(void *dest, void *src, size_t width, size_t height, size_t szof)
Copy one image to another, flipping left-right.
void * imcpy_flipUDLR(void *dest, void *src, size_t width, size_t height, size_t szof)
Copy one image to another, flipping up-down and left-right.
void * imcpy_flipUD(void *dest, void *src, size_t width, size_t height, size_t szof)
Copy one image to another, flipping up-down.
int reflectImageCoords(int &x1, int &y1, int x0, int y0, realT xc, realT yc)
Reflect pixel coordinates across the given center pixel.
vectorT::value_type vectorMedianInPlace(vectorT &vec)
Calculate median of a vector in-place, altering the vector.
Image interpolation and transformation.
realT imCen(int rows_cols)
Get the mxlib standard center coordinate of an image.
realT imCenX(const imT &im)
Get the mxlib standard center x-coordinate of an image.
realT imCenY(const imT &im)
Get the mxlib standard center y-coordinate of an image.
The mxlib c++ namespace.
Definition mxlib.hpp:37
Transformation by cubic convolution interpolation.