55TEST_CASE(
"precalcKernel caches production kernel values",
"[improc::precalcKernel]" )
57 SECTION(
"with Gaussian kernel" )
67 for(
int column = 0; column < 8; ++column )
69 for(
int row = 0; row < 8; ++row )
73 REQUIRE( ( generatedKernel == cachedKernel ).all() );
78 SECTION(
"with azBoxKernel kernel" )
82 kernelT kernel( 3, 5 );
88 for(
int column = 0; column < 8; ++column )
90 for(
int row = 0; row < 8; ++row )
94 REQUIRE( ( generatedKernel == cachedKernel ).all() );
107TEST_CASE(
"azBoxKernel is finite at centers and neighboring pixels",
"[improc::azBoxKernel]" )
111 REQUIRE( kernel.size() > 0 );
112 REQUIRE( arrayAllFinite( kernel ) );
113 REQUIRE( kernel.sum() == Approx( 1.0 ).margin( 1e-12 ) );
116 SECTION(
"exact center uses the positive x-axis and configured half-widths" )
119 kernelT kernelGenerator( 3.0, 1.0 );
123 requireFiniteNormalized( kernel );
124 REQUIRE( kernelGenerator.maxWidth() == 4 );
125 REQUIRE( kernel.rows() == 8 );
126 REQUIRE( kernel.cols() == 4 );
129 SECTION(
"an angular limit retains the output pixel" )
132 kernelT kernelGenerator( 1.0, 2.0, 30.0 );
136 requireFiniteNormalized( kernel );
137 REQUIRE( kernel.rows() % 2 == 1 );
138 REQUIRE( kernel.cols() % 2 == 1 );
139 REQUIRE( kernel( kernel.rows() / 2, kernel.cols() / 2 ) > 0 );
142 requireFiniteNormalized( kernel );
143 REQUIRE( kernel.rows() % 2 == 1 );
144 REQUIRE( kernel.cols() % 2 == 1 );
145 REQUIRE( kernel( kernel.rows() / 2, kernel.cols() / 2 ) > 0 );
148 SECTION(
"an odd kernel includes its finite center sample" )
151 kernelT kernelGenerator( 2.0, 2.0 );
159 requireFiniteNormalized( centerKernel );
160 requireFiniteNormalized( radialNeighbor );
161 requireFiniteNormalized( azimuthalNeighbor );
162 requireFiniteNormalized( halfPixelKernel );
163 REQUIRE( kernelGenerator.maxWidth() == 1 );
164 REQUIRE( centerKernel.rows() % 2 == 1 );
165 REQUIRE( centerKernel.cols() % 2 == 1 );
166 REQUIRE( centerKernel( centerKernel.rows() / 2, centerKernel.cols() / 2 ) > 0 );
167 REQUIRE( radialNeighbor( radialNeighbor.rows() / 2, radialNeighbor.cols() / 2 ) > 0 );
170 SECTION(
"radial and azimuthal neighbors rotate an anisotropic kernel" )
173 kernelT kernelGenerator( 3.0, 1.0 );
179 requireFiniteNormalized( radialKernel );
180 requireFiniteNormalized( azimuthalKernel );
181 REQUIRE( radialKernel.rows() == azimuthalKernel.cols() );
182 REQUIRE( radialKernel.cols() == azimuthalKernel.rows() );
193TEST_CASE(
"azBoxKernel validates widths coordinates and maxAz",
"[improc::azBoxKernel]" )
198 SECTION(
"zero half-widths produce an identity kernel" )
200 kernelT identityKernel( 0.0, 0.0 );
201 REQUIRE( identityKernel.maxWidth() == 0 );
203 REQUIRE( kernel.rows() == 1 );
204 REQUIRE( kernel.cols() == 1 );
205 REQUIRE( kernel( 0, 0 ) == 1.0 );
207 REQUIRE( kernel( 0, 0 ) == 1.0 );
210 SECTION(
"subpixel half-widths support a one-pixel odd kernel" )
213 subpixelKernelT subpixelKernel( 0.25, 0.25 );
215 REQUIRE( subpixelKernel.maxWidth() == 0 );
217 REQUIRE( kernel.rows() == 1 );
218 REQUIRE( kernel.cols() == 1 );
219 REQUIRE( kernel( 0, 0 ) == 1.0 );
222 SECTION(
"negative half-widths retain their absolute-value contract" )
224 kernelT positiveWidths( 2.0, 4.0 );
225 kernelT negativeWidths( -2.0, -4.0 );
230 REQUIRE( ( positiveKernel == negativeKernel ).all() );
233 SECTION(
"nonfinite widths maxAz and coordinates are rejected" )
235 const double nan = std::numeric_limits<double>::quiet_NaN();
236 const double infinity = std::numeric_limits<double>::infinity();
237 kernelT invalidRadialWidth( nan, 1.0 );
238 kernelT invalidAzimuthalWidth( 1.0, infinity );
239 kernelT invalidMaxAz( 1.0, 1.0, nan );
240 kernelT validKernel( 1.0, 1.0 );
243 REQUIRE( kernel.size() == 0 );
250 SECTION(
"maxAz limits support around the positive x-axis at image center" )
252 kernelT unlimitedKernel( 2.0, 5.0 );
253 kernelT equivalentUnlimitedKernel( 2.0, 5.0, 180.0 );
254 kernelT limitedKernel( 2.0, 5.0, 30.0 );
255 kernelT negativeLimitKernel( 2.0, 5.0, -30.0 );
259 REQUIRE( equivalentUnlimitedKernel.setKernel( 0.0, 0.0, equivalentUnlimited ) ==
mx::error_t::noerror );
263 REQUIRE( arrayAllFinite( unlimited ) );
264 REQUIRE( arrayAllFinite( limited ) );
265 REQUIRE( unlimited.sum() == Approx( 1.0 ).margin( 1e-12 ) );
266 REQUIRE( limited.sum() == Approx( 1.0 ).margin( 1e-12 ) );
267 REQUIRE( ( unlimited == equivalentUnlimited ).all() );
268 REQUIRE( ( limited == negativeLimit ).all() );
269 REQUIRE( ( limited != 0.0 ).count() < ( unlimited != 0.0 ).count() );
271 const double xCenter = 0.5 * ( limited.rows() - 1.0 );
272 const double yCenter = 0.5 * ( limited.cols() - 1.0 );
274 for(
int column = 0; column < limited.cols(); ++column )
276 for(
int row = 0; row < limited.rows(); ++row )
278 if( limited( row, column ) > 0 )
280 REQUIRE( std::fabs( std::atan2( column - yCenter, row - xCenter ) ) <= maxAngle + 1e-12 );
286 SECTION(
"finite dimension overflow is rejected" )
288 kernelT oversizedKernel( std::numeric_limits<double>::max(), 1.0 );
290 REQUIRE( oversizedKernel.maxWidth() == 0 );
292 REQUIRE( kernel.size() == 0 );
295 SECTION(
"inconsistent cached width bounds are rejected" )
297 kernelT widthKernel( 3.0, 1.0 );
298 widthKernel.m_maxWidth = 0;
301 kernelT heightKernel( 1.0, 3.0 );
302 heightKernel.m_maxWidth = 2;
306 SECTION(
"a kernel with no accepted samples is rejected" )
308 kernelT emptyKernel( 1.0, 1.0 );
309 emptyKernel.m_radWidth = -1.0;
311 REQUIRE( kernel.size() == 0 );
314 SECTION(
"maxAz is evaluated away from the image center" )
316 kernelT limitedKernel( 2.0, 5.0, 30.0 );
318 REQUIRE( arrayAllFinite( kernel ) );
319 REQUIRE( kernel.sum() == Approx( 1.0 ).margin( 1e-12 ) );
330TEST_CASE(
"precalcKernel supports odd even and non-square image centers",
"[improc::precalcKernel]" )
333 kernelT kernelGenerator( 2.0, 2.0 );
337 REQUIRE( oddKernelCache.
m_kernels.size() == 5 * 7 );
338 REQUIRE( evenKernelCache.
m_kernels.size() == 4 * 6 );
340 for(
const auto &cachedKernel : oddKernelCache.
m_kernels )
342 REQUIRE( cachedKernel.size() > 0 );
343 REQUIRE( arrayAllFinite( cachedKernel ) );
344 REQUIRE( cachedKernel.sum() == Approx( 1.0 ).margin( 1e-12 ) );
346 for(
const auto &cachedKernel : evenKernelCache.
m_kernels )
348 REQUIRE( cachedKernel.size() > 0 );
349 REQUIRE( arrayAllFinite( cachedKernel ) );
350 REQUIRE( cachedKernel.sum() == Approx( 1.0 ).margin( 1e-12 ) );
355 REQUIRE( arrayAllFinite( centerKernel ) );
357 REQUIRE( arrayAllFinite( centerKernel ) );
516TEST_CASE(
"medianSmooth honors exact odd and even full widths",
"[improc::medianSmooth]" )
518 SECTION(
"even width uses width squared samples" )
522 for(
int column = 0; column < input.cols(); ++column )
524 for(
int row = 0; row < input.rows(); ++row )
526 input( row, column ) = row + column * input.rows();
529 output.setConstant( -99.0 );
536 REQUIRE( result == 0 );
537 REQUIRE( output( 2, 2 ) == Approx( 7.5 ) );
538 REQUIRE( xMax == 2 );
539 REQUIRE( yMax == 2 );
540 REQUIRE( pMax == Approx( 7.5 ) );
541 for(
int column = 0; column < output.cols(); ++column )
543 for(
int row = 0; row < output.rows(); ++row )
545 if( row != 2 || column != 2 )
547 REQUIRE( output( row, column ) == -99.0 );
553 SECTION(
"odd width retains its centered footprint" )
557 for(
int column = 0; column < input.cols(); ++column )
559 for(
int row = 0; row < input.rows(); ++row )
561 input( row, column ) = row + column * input.rows();
564 output.setConstant( -99.0 );
567 REQUIRE( output( 1, 1 ) == Approx( 4.0 ) );
568 REQUIRE( output( 0, 0 ) == -99.0 );
569 REQUIRE( output( 2, 2 ) == -99.0 );
572 SECTION(
"unit width is an identity operation" )
575 input << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0;
580 REQUIRE( ( output == input ).all() );
621TEST_CASE(
"meanSmooth honors exact even full widths",
"[improc::meanSmooth]" )
625 for(
int column = 0; column < input.cols(); ++column )
627 for(
int row = 0; row < input.rows(); ++row )
629 input( row, column ) = row + column * input.rows();
632 output.setConstant( -99.0 );
635 REQUIRE( output( 2, 2 ) == Approx( 7.5 ) );
636 REQUIRE( output( 1, 1 ) == -99.0 );
638 output.setConstant( -99.0 );
643 REQUIRE( output( 2, 2 ) == Approx( 7.5 ) );
644 REQUIRE( xMax == 2 );
645 REQUIRE( yMax == 2 );
646 REQUIRE( pMax == Approx( 7.5 ) );
648 SECTION(
"reject extrema in an all-positive window" )
651 positiveInput << 1.0, 2.0, 3.0, 100.0;
653 positiveOutput.setConstant( -99.0 );
656 REQUIRE( positiveOutput( 1, 1 ) == Approx( 2.5 ) );
657 REQUIRE( positiveOutput( 0, 0 ) == -99.0 );
660 SECTION(
"reject extrema in an all-negative window" )
663 negativeInput << -100.0, -3.0, -2.0, -1.0;
665 negativeOutput.setConstant( -99.0 );
671 REQUIRE( negativeOutput( 1, 1 ) == Approx( -2.5 ) );
672 REQUIRE( negativeOutput( 0, 0 ) == -99.0 );
673 REQUIRE( xMax == 1 );
674 REQUIRE( yMax == 1 );
675 REQUIRE( pMax == Approx( -2.5 ) );
684TEST_CASE(
"vectorSmoothMedian honors exact even full widths",
"[math::vectorSmoothMedian]" )
686 std::vector<double> input{ 0.0, 1.0, 2.0, 3.0, 100.0 };
687 const std::vector<double> original = input;
688 std::vector<double> output;
691 REQUIRE( input == original );
692 REQUIRE( output.size() == input.size() );
693 REQUIRE( output[0] == Approx( 0.5 ) );
694 REQUIRE( output[1] == Approx( 1.0 ) );
695 REQUIRE( output[2] == Approx( 1.5 ) );
696 REQUIRE( output[3] == Approx( 2.5 ) );
697 REQUIRE( output[4] == Approx( 3.0 ) );
699 output.assign( 1, -99.0 );
701 REQUIRE( output == std::vector<double>{ -99.0 } );
709TEST_CASE(
"stddevImage subtracts the annular mean for S/N",
"[improc::stddevImage]" )
716 for(
int column = 0; column < image.cols(); ++column )
718 for(
int row = 0; row < image.rows(); ++row )
720 radius( row, column ) = column + 0.5;
721 image( row, column ) = 10.0 * column + ( row % 2 == 0 ? 2.0 : 4.0 );
729 const double mean = 92.0 / 7.0;
730 const double variance = ( 3.0 * std::pow( 12.0 - mean, 2 ) + 4.0 * std::pow( 14.0 - mean, 2 ) ) / 6.0;
731 REQUIRE( stddev( 2, 1 ) == Approx( ( 12.0 - mean ) / std::sqrt( variance ) ) );
733 SECTION(
"negative masked radii are skipped before binning" )
735 radius( 0, 0 ) = -0.5;
740 REQUIRE( std::isfinite( stddev( 2, 1 ) ) );
743 SECTION(
"a masked infinite radius finishes the bins" )
745 radius( 0, 7 ) = std::numeric_limits<double>::infinity();
750 REQUIRE( std::isfinite( stddev( 2, 1 ) ) );