mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
imageUtils_test.cpp
Go to the documentation of this file.
1/** \file imageUtils_test.cpp
2 * \brief Tests of image-processing utilities.
3 */
4#include "../../catch2/catch.hpp"
5
6#include <Eigen/Dense>
7
8#include <limits>
9#include <vector>
10
11#define MX_NO_ERROR_REPORTS
12
16
17/// Verify image invalid-pixel generation and classification for floating-point values.
18/** Exercises the default NaN contract or a configured finite override, plus nonfinite classification. */
19/**
20 * \ingroup imageUtils_unit_tests
21 */
22TEST_CASE( "Image invalid-pixel detection handles sentinel and nonfinite values", "[improc::isInvalidPixel]" )
23{
24 REQUIRE_FALSE( mx::improc::isInvalidPixel( 0.0F ) );
25 REQUIRE( mx::improc::isInvalidPixel( std::numeric_limits<float>::quiet_NaN() ) );
26 REQUIRE( mx::improc::isInvalidPixel( std::numeric_limits<double>::quiet_NaN() ) );
27 REQUIRE( mx::improc::isInvalidPixel( std::numeric_limits<float>::infinity() ) );
28 REQUIRE( mx::improc::isInvalidPixel( -std::numeric_limits<float>::infinity() ) );
31
32#ifdef MXLIB_INVALID_NUMBER_VALUE
33 REQUIRE( mx::improc::invalidNumber<float>() == static_cast<float>( MXLIB_INVALID_NUMBER_VALUE ) );
34 REQUIRE( mx::improc::invalidNumber<double>() == static_cast<double>( MXLIB_INVALID_NUMBER_VALUE ) );
35#else
38#endif
39}
40
41/** centroiding Gaussians with center of light
42 *
43 * Verify center of light calculation
44 *
45 */
46/**
47 * \ingroup imageUtils_unit_tests
48 */
49TEST_CASE( "Verify center of light calculation", "[improc::imageCenterOfLight]" )
50{
51 GIVEN( "a Gaussian" )
52 {
53 WHEN( "geometric center" )
54 {
56 im.resize( 64, 64 );
57
58 mx::math::func::gaussian2D<double>( im.data(), im.rows(), im.cols(), 0., 1.0, 31.5, 31.5, 2 );
59
60 double x, y;
62
63 REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 31.5, 1e-8 ) );
64 REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 31.5, 1e-8 ) );
65 }
66 WHEN( "geometric quarter" )
67 {
69 im.resize( 64, 64 );
70
71 mx::math::func::gaussian2D<double>( im.data(), im.rows(), im.cols(), 0., 1.0, 15.5, 15.5, 2 );
72
73 double x, y;
75
76 REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) );
77 REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) );
78 }
79 }
80}
81
82/** \brief Verifies masked and unmasked image medians with internal and caller-owned workspaces.
83 *
84 * \ingroup imageUtils_unit_tests
85 */
86TEST_CASE( "imageMedian selects valid image samples", "[improc::imageMedian]" )
87{
89 image << 1.0, 9.0, 3.0, 7.0, 5.0, 11.0;
90
91 REQUIRE( mx::improc::imageMedian( image ) == Approx( 6.0 ) );
92
93 std::vector<double> work;
94 REQUIRE( mx::improc::imageMedian( image, &work ) == Approx( 6.0 ) );
95 REQUIRE( work.size() == static_cast<size_t>( image.size() ) );
96
97 Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic> mask( 2, 3 );
98 mask << 1, 0, 0, 0, 1, 1;
99
100 REQUIRE( mx::improc::imageMedian( image, &mask, &work ) == Approx( 5.0 ) );
101 REQUIRE( work.size() == 3 );
102 REQUIRE( mx::improc::imageMedian( image, &mask ) == Approx( 5.0 ) );
103}
104
105/// Verify zeroNaNCube replaces invalid cube pixels and records their locations.
106/** Exercises both mx::improc::zeroNaNCube overloads, including mask allocation and finite pixels. */
107/**
108 * \ingroup imageUtils_unit_tests
109 */
110TEST_CASE( "zeroNaNCube replaces invalid pixels and reports a mask", "[improc::zeroNaNCube]" )
111{
112 mx::improc::eigenCube<float> cube( 2, 3, 2 );
113 cube.cube().setOnes();
114 cube.image( 0 )( 0, 1 ) = std::numeric_limits<float>::quiet_NaN();
115 cube.image( 1 )( 1, 2 ) = std::numeric_limits<float>::infinity();
116
118 mx::improc::zeroNaNCube( cube, &mask );
119
120 REQUIRE( cube.image( 0 )( 0, 1 ) == 0.0F );
121 REQUIRE( cube.image( 1 )( 1, 2 ) == 0.0F );
122 REQUIRE( cube.image( 0 )( 1, 1 ) == 1.0F );
123 REQUIRE( mask.rows() == cube.rows() );
124 REQUIRE( mask.cols() == cube.cols() );
125 REQUIRE( mask.planes() == cube.planes() );
126 REQUIRE( mask.image( 0 )( 0, 1 ) == 1.0F );
127 REQUIRE( mask.image( 1 )( 1, 2 ) == 1.0F );
128 REQUIRE( mask.image( 0 )( 1, 1 ) == 0.0F );
129
131 REQUIRE( cube.image( 0 )( 1, 1 ) == 1.0F );
132}
An image cube with an Eigen-like API.
Definition eigenCube.hpp:33
Eigen::Map< Eigen::Array< dataT, Eigen::Dynamic, Eigen::Dynamic > > cube()
Returns a 2D Eigen::Eigen::Map pointed at the entire cube.
Eigen::Map< Eigen::Array< dataT, Eigen::Dynamic, Eigen::Dynamic > > image(Index n)
Returns a 2D Eigen::Eigen::Map pointed at the specified image.
An image cube with an Eigen API.
Declarations for utilities related to the Gaussian function.
Eigen::Array< scalarT, -1, -1 > eigenImage
Definition of the eigenImage type, which is an alias for Eigen::Array.
imageT::Scalar imageMedian(const imageT &mat, const maskT *mask, std::vector< typename imageT::Scalar > *work=0)
Calculate the median of an Eigen-like array.
int imageCenterOfLight(typename imageT::Scalar &x, typename imageT::Scalar &y, const imageT &im)
Calculate the center of light of an image.
void zeroNaNCube(cubeT &imc, maskCubeT *mask)
Zero any NaNs in an image cube.
realT gaussian2D(const realT x, const realT y, const realT G0, const realT G, const realT x0, const realT y0, const realT sigma)
Find value at position (x,y) of the 2D arbitrarily-centered symmetric Gaussian.
Definition gaussian.hpp:126
bool isNan(realT value)
Test whether a floating-point value is NaN, including under finite-math-only optimization.
TEST_CASE("Image invalid-pixel detection handles sentinel and nonfinite values", "[improc::isInvalidPixel]")
Verify image invalid-pixel generation and classification for floating-point values.
constexpr realT invalidNumber()
Return the configured invalid image-pixel value.
bool isInvalidPixel(realT value)
Check whether a value represents an invalid image pixel.
Header for the image processing utilities.