From 802cd03c64cc1c1ee40e2f46dadfa272fb27c4ee Mon Sep 17 00:00:00 2001 From: Jared Males Date: Wed, 10 Sep 2025 10:43:23 -0700 Subject: [PATCH 01/21] fixed option size bug --- source/app/clOptions.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/app/clOptions.cpp b/source/app/clOptions.cpp index e9d2faa49..6feb00dd4 100644 --- a/source/app/clOptions.cpp +++ b/source/app/clOptions.cpp @@ -102,10 +102,10 @@ void clOptions::parse( int argc, char **argv, std::vector *nonOptio argv += ( argc > 0 ); // skip program name argv[0] if present // If not already done, we push the unknown catcher and the termination descriptor. - if( descriptions.back().index != 0 ) + if( descriptions.size() == 0 || descriptions.back().shortopt != 0 ) { - descriptions.push_back( - { nOpts, 0, "", "", option::Arg::None, "" } ); // This is inserted to catch unknown options + // This is inserted to catch unknown options + descriptions.push_back( { nOpts, 0, "", "", option::Arg::None, "" } ); descriptions.push_back( { 0, 0, 0, 0, 0, 0 } ); } From 8c2af8ad391e14a29513e990a0d37520a83065e8 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Wed, 10 Sep 2025 10:45:45 -0700 Subject: [PATCH 02/21] updated doc of clOptions --- include/app/clOptions.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/app/clOptions.hpp b/include/app/clOptions.hpp index 3b212ee18..66b4e4c86 100644 --- a/include/app/clOptions.hpp +++ b/include/app/clOptions.hpp @@ -83,10 +83,13 @@ struct clOptions ); /// Parse the command line + /** + * \todo test for descriptions of size 0 and with/without ending sentinel + */ void parse( int argc, ///< [in] From main(argc, argv), the number of command line arguments char **argv, ///< in] From main(argc, argv), the command line arguments - std::vector *nonOptions = - 0 ///< [out] [optional] the elements in argv which are not option or option-arguments. + std::vector *nonOptions = 0 /**< [out] [optional] the elements in argv + which are not option or option-arguments. */ ); /// Get the value of the option, if present. From f5c59e5d48905a2a9a6052c163a7d41c724e0daf Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 23 Dec 2025 21:07:51 -0700 Subject: [PATCH 03/21] fixed missing data(); moved imageMedian to utils; doc cleanup --- include/improc/eigenImage.hpp | 81 ---------------- include/improc/imageUtils.hpp | 147 +++++++++++++++++++++++++++++- include/sigproc/signalWindows.hpp | 2 +- 3 files changed, 143 insertions(+), 87 deletions(-) diff --git a/include/improc/eigenImage.hpp b/include/improc/eigenImage.hpp index f988bd36e..0faadbd7b 100644 --- a/include/improc/eigenImage.hpp +++ b/include/improc/eigenImage.hpp @@ -105,87 +105,6 @@ struct eigenArrPlanes } }; -/// Calculate the median of an Eigen-like array. -/** Calculates the median of the entire array, allowing for some pixels to be ignored using a mask. - * Working memory can be retained between calls. - * - * \tparam imageT is an Eigen-like type - * \tparam maskT is an Eigen-like type - * - * \returns the median of the unmasked pixels of mat, using \ref vectorMedianInPlace(). - * - * \ingroup eigen_image_processing - * - */ -template -typename imageT::Scalar -imageMedian( const imageT &mat, ///< [in] the image to take the median of - const maskT *mask, ///< [in] if non-0, a 1/0 mask where 0 pixels are ignored. - std::vector *work = - 0 ///< [in] [optional] working memory can be retained and re-passed. Is resized. -) -{ - typename imageT::Scalar med; - - bool localWork = false; - if( work == 0 ) - { - work = new std::vector; - localWork = true; - } - - int sz = mat.size(); - - if( mask ) - { - sz = mask->sum(); - } - - work->resize( sz ); - - int ii = 0; - for( int i = 0; i < mat.rows(); ++i ) - { - for( int j = 0; j < mat.cols(); ++j ) - { - if( mask ) - { - if( ( *mask )( i, j ) == 0 ) - continue; - } - - ( *work )[ii] = mat( i, j ); - ++ii; - } - } - - med = math::vectorMedianInPlace( *work ); - - if( localWork ) - delete work; - - return med; -} - -/// Calculate the median of an Eigen-like array. -/** Calculates the median of the entire array. - * Working memory can be retained between calls. - * - * \tparam imageT is an Eigen-like type - * - * \returns the median of the unmasked pixels of mat, using \ref vectorMedianInPlace(). - * - * \ingroup eigen_image_processing - * - */ -template -typename imageT::Scalar imageMedian( - const imageT &mat, ///< [in] the image to take the median of - std::vector *work = 0 ///< [in] [optional] working memory can be retained and re-passed. -) -{ - return imageMedian( mat, (Eigen::Array *)0, work ); -} } // namespace improc } // namespace mx diff --git a/include/improc/imageUtils.hpp b/include/improc/imageUtils.hpp index 0506321ed..1ee8c610d 100644 --- a/include/improc/imageUtils.hpp +++ b/include/improc/imageUtils.hpp @@ -78,6 +78,8 @@ constexpr T invalidNumber() } /// Check if the number is nan, using several different methods +/** + */ inline bool IsNan( float value ) { return ( ( ( ( *(uint32_t *)&value ) & 0x7fffffff ) > 0x7f800000 ) || ( value == invalidNumber() ) || @@ -102,9 +104,13 @@ int reflectImageCoords( int &x1, ///< [out] the reflected x coordinate return 0; } +/// @} + /// Zero any NaNs in an image /** * \overload + * + * \ingroup eigen_image_processing */ template void zeroNaNs( imageT &im, ///< [in.out] image which will have any NaN pixels set to zero @@ -124,6 +130,9 @@ void zeroNaNs( imageT &im, ///< [in.out] image which will have any NaN pixels se } /// Zero any NaNs in an image +/** + * \ingroup eigen_image_processing + */ template void zeroNaNs( imageT &im /**< [in.out] image which will have any NaN pixels set to zero */ ) { @@ -134,6 +143,7 @@ void zeroNaNs( imageT &im /**< [in.out] image which will have any NaN pixels set /// Zero any NaNs in an image cube /** This version fills in a mask with 1s where there were nans, 0s elsewhere. * + * \ingroup eigen_image_processing */ template void zeroNaNCube( cubeT &imc, /**< [in.out] cube which will have any NaN pixels set to zero */ @@ -166,6 +176,9 @@ void zeroNaNCube( cubeT &imc, /**< [in.out] cube which will have any NaN pix } /// Zero any NaNs in an image cube +/** + * \ingroup eigen_image_processing + */ template void zeroNaNCube( cubeT &imc /**< [in.out] cube which will have any NaN pixels set to zero */ ) { @@ -176,6 +189,8 @@ void zeroNaNCube( cubeT &imc /**< [in.out] cube which will have any NaN pixels s /** * * \returns the mean of the input image + * + * \ingroup eigen_image_processing */ template calcT imageMean( imageT &im /**< [in] the image of which to calculate the mean*/ ) @@ -185,8 +200,9 @@ calcT imageMean( imageT &im /**< [in] the image of which to calculate the mean*/ /// Calculate the mean value of an image over a mask /** - * * \returns the mean of the input image in the masked region + * + * \ingroup eigen_image_processing */ template calcT imageMean( imageT &im, /**< [in] the image of which to calculate the mean*/ @@ -199,6 +215,8 @@ calcT imageMean( imageT &im, /**< [in] the image of which to calculate the mean* /** * * \returns the variance of the input image + * + * \ingroup eigen_image_processing */ template calcT imageVariance( imageT &im /**< [in] the image of which to calculate the variance*/, @@ -212,6 +230,8 @@ calcT imageVariance( imageT &im /**< [in] the image of which to calculate the va /** * * \returns the variance of the input image + * + * \ingroup eigen_image_processing */ template calcT imageVariance( imageT &im /**< [in] the image of which to calculate the variance*/, @@ -222,9 +242,108 @@ calcT imageVariance( imageT &im /**< [in] the image of which to calculate the va return ( im.template cast() * mask - mn ).square().sum() / ( mask.sum() ); } +/// Calculate the median of an Eigen-like array. +/** Calculates the median of the entire array, allowing for some pixels to be ignored using a mask. + * Working memory can be retained between calls. + * + * \tparam imageT is an Eigen-like type + * \tparam maskT is an Eigen-like type + * + * \returns the median of the unmasked pixels of mat, using \ref vectorMedianInPlace(). + * + * \ingroup eigen_image_processing + * + */ +template +typename imageT::Scalar +imageMedian( const imageT &mat, /**< [in] the image to take the median of*/ + const maskT *mask, /**< [in] if non-0, a 1/0 mask where 0 pixels are ignored.*/ + std::vector *work = 0 /**< [in] [optional] working memory + can be retained + and re-passed. Is resized.*/ +) +{ + typename imageT::Scalar med; + + bool localWork = false; + if( work == 0 ) + { + work = new std::vector; + localWork = true; + } + + int sz = mat.size(); + + if( mask ) + { + sz = mask->sum(); + } + + work->resize( sz ); + + if( mask ) + { + int ii = 0; + for( int i = 0; i < mat.rows(); ++i ) + { + for( int j = 0; j < mat.cols(); ++j ) + { + if( ( *mask )( i, j ) == 0 ) + { + continue; + } + ( *work )[ii] = mat( i, j ); + ++ii; + } + } + } + else + { + int ii = 0; + for( int i = 0; i < mat.rows(); ++i ) + { + for( int j = 0; j < mat.cols(); ++j ) + { + ( *work )[ii] = mat( i, j ); + ++ii; + } + } + } + + med = math::vectorMedianInPlace( *work ); + + if( localWork ) + { + delete work; + } + + return med; +} + +/// Calculate the median of an Eigen-like array. +/** Calculates the median of the entire array. + * Working memory can be retained between calls. + * + * \tparam imageT is an Eigen-like type + * + * \returns the median of the unmasked pixels of mat, using \ref vectorMedianInPlace(). + * + * \ingroup eigen_image_processing + * + */ +template +typename imageT::Scalar imageMedian( const imageT &mat, /**< [in] the image to take the median of*/ + std::vector *work = 0 /**< [in] [optional] working memory can + be retained and re-passed.*/ +) +{ + return imageMedian( mat, reinterpret_cast *>(nullptr), work ); +} + /// Calculate the center of light of an image /** Note that the sum of the image should be > 0. * + * \ingroup eigen_image_processing */ template int imageCenterOfLight( typename imageT::Scalar &x, ///< [out] the x coordinate of the center of light [pixels] @@ -264,6 +383,7 @@ int imageCenterOfLight( typename imageT::Scalar &x, ///< [out] the x coordinate * scaling used in imageMagnify, the desired scale may not be exact. As a result * the actual scale is returned in scale_x and scale_y. * + * \ingroup eigen_image_processing */ template int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, in pixels of the input image @@ -303,6 +423,7 @@ int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, * scaling used in imageMagnify, the desired scale may not be exact. As a result * the actual scale is returned in scale_x and scale_y. * + * \ingroup eigen_image_processing */ template int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, in pixels of the input image @@ -330,6 +451,8 @@ int imageMaxInterp( floatT &x, ///< [out] the x-position of the maximum, * \tparam imageT2 the eigen-like array type of mask 1 * \tparam imageT3 the eigen-like array type of image 2 * \tparam imageT4 the eigen-like array type of mask 2 + * + * \ingroup eigen_image_processing */ template void combine2ImagesMasked( imageT &combo, ///< [out] the combined image. will be resized. @@ -357,10 +480,13 @@ void combine2ImagesMasked( imageT &combo, ///< [out] the combined image. } } +/// Remove rows and columns +/** + * \ingroup eigen_image_processing + */ template void removeRowsAndCols( eigenT &out, const eigenTin &in, int st, int w ) { - out.resize( in.rows() - w, in.cols() - w ); out.topLeftCorner( st, st ) = in.topLeftCorner( st, st ); @@ -373,10 +499,13 @@ void removeRowsAndCols( eigenT &out, const eigenTin &in, int st, int w ) in.bottomRightCorner( in.rows() - ( st + w ), in.cols() - ( st + w ) ); } +/// Remove rows +/** + * \ingroup eigen_image_processing + */ template void removeRows( eigenT &out, const eigenTin &in, int st, int w ) { - out.resize( in.rows() - w, in.cols() ); out.topLeftCorner( st, in.cols() ) = in.topLeftCorner( st, in.cols() ); @@ -385,10 +514,13 @@ void removeRows( eigenT &out, const eigenTin &in, int st, int w ) in.bottomLeftCorner( in.rows() - ( st + w ), in.cols() ); } +/// Remove columns +/** + * \ingroup eigen_image_processing + */ template void removeCols( eigenT &out, const eigenTin &in, int st, int w ) { - out.resize( in.rows(), in.cols() - w ); out.topLeftCorner( in.rows(), st ) = in.topLeftCorner( in.rows(), st ); @@ -396,6 +528,11 @@ void removeCols( eigenT &out, const eigenTin &in, int st, int w ) out.topRightCorner( in.rows(), in.cols() - ( st + w ) ) = in.topRightCorner( in.rows(), in.cols() - ( st + w ) ); } + +/** \ingroup image_utils + *@{ + */ + /// Copy one image to another, with no transformation /** This is merely memcpy * @@ -444,7 +581,7 @@ void *imcpy_flipUDLR( void *dest, ///< [out] the address of the first pixel i size_t szof ///< [in] the size in bytes of a one pixel ); -///@} + } // namespace improc } // namespace mx diff --git a/include/sigproc/signalWindows.hpp b/include/sigproc/signalWindows.hpp index 002076a6e..6d38457bd 100644 --- a/include/sigproc/signalWindows.hpp +++ b/include/sigproc/signalWindows.hpp @@ -525,7 +525,7 @@ void tukey2dAnnulus( arrT &filt, ///< [in,out] a pre-allocated typename arrT::Scalar yc ///< [in] the desired y center of the window. ) { - return tukey2dAnnulus( filt, filt.rows(), filt.cols(), D, eps, alpha, xc, yc ); + return tukey2dAnnulus( filt.data(), filt.rows(), filt.cols(), D, eps, alpha, xc, yc ); } /** \brief Create a 2-D Tukey window on a rectangle From 71b8d8a5cb294cf6d6b51ef9f47ee6984c97aa0d Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 23 Dec 2025 21:16:07 -0700 Subject: [PATCH 04/21] fixed cast --- include/improc/imageUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/improc/imageUtils.hpp b/include/improc/imageUtils.hpp index 1ee8c610d..bda8217ff 100644 --- a/include/improc/imageUtils.hpp +++ b/include/improc/imageUtils.hpp @@ -337,7 +337,7 @@ typename imageT::Scalar imageMedian( const imageT &mat, /**< [in] the image to t be retained and re-passed.*/ ) { - return imageMedian( mat, reinterpret_cast *>(nullptr), work ); + return imageMedian( mat, static_cast *>(nullptr), work ); } /// Calculate the center of light of an image From d8fb391c4778d08b0e2c507c0b91351539bb92e5 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Fri, 2 Jan 2026 11:40:17 -0700 Subject: [PATCH 05/21] added repo name,url, path to gengithead; formatted and colored warning in gengithead --- gengithead.sh | 38 +++++++++++++++++++++++++------------- source/Makefile | 2 +- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/gengithead.sh b/gengithead.sh index 72aec56a2..f448f4b1c 100644 --- a/gengithead.sh +++ b/gengithead.sh @@ -26,7 +26,7 @@ # Usage: gengithead.sh directory output_path prefix # # directory = directory of git repo, optional default is './' -# output_path = optional ouput path, default './git_version.h' +# output_path = optional output path, default './git_version.h' # prefix = optional prefix for #define variable names, default is GIT # # note: the arguments are positional, so to change output_path you must @@ -40,18 +40,25 @@ centerName(){ textsize=${#1} width=42 span=$(((width + textsize) / 2 )) - espan=$((span - textsize)) + espan=$((span - textsize+2)) if [ $((espan % 2)) -eq 1 ]; then espan=$((espan - 1)); fi - printf " #pragma message (\"*%${span}s%${espan}s\")\n" "$1" "*" + printf " \"*\033[33m%${span}s\033[39m%${espan}s\"%s\n" "$1" "*\n" "\\" } #defaults for args GITPATH=${1:-'./'} -HEADPATH=${2:-'./git_version.h'} -PREFIX=${3:-'GIT'} +REPO_NAME=$(basename $(git --exec-path=$GITPATH rev-parse --show-toplevel)) +REPO_PATH=$(realpath $GITPATH) + +#default PREFIX is the repo name +PREFIX=${3:-$REPO_NAME} + +#default output path is PREFIX_git_version.h +HEADPATH=${2:-"./"$PREFIX"_git_version.h"} -GIT_HEADER="$HEADPATH" +GIT_HEADER="$HEADPATH" +GIT_URL=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH remote get-url origin) GIT_BRANCH=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH rev-parse --abbrev-ref HEAD) GIT_VERSION=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH log -1 --format=%H) @@ -60,12 +67,15 @@ git --git-dir=$GITPATH/.git --work-tree=$GITPATH diff-index --quiet HEAD -- GIT_MODIFIED=$? set -e -REPO_NAME=$(basename $(git --exec-path=$GITPATH rev-parse --show-toplevel)) -echo "#ifndef $PREFIX""_VERSION_H" > $GIT_HEADER -echo "#define $PREFIX""_VERSION_H" >> $GIT_HEADER + +echo "#ifndef $PREFIX""_GIT_VERSION_H" > $GIT_HEADER +echo "#define $PREFIX""_GIT_VERSION_H" >> $GIT_HEADER echo "" >> $GIT_HEADER +echo "#define $PREFIX""_REPO \"$REPO_NAME\"" >> $GIT_HEADER +echo "#define $PREFIX""_URL \"$GIT_URL\"" >> $GIT_HEADER echo "#define $PREFIX""_BRANCH \"$GIT_BRANCH\"" >> $GIT_HEADER +echo "#define $PREFIX""_SRCPATH \"$REPO_PATH\"" >> $GIT_HEADER echo "#define $PREFIX""_CURRENT_SHA1 \"$GIT_VERSION\"" >> $GIT_HEADER echo "#define $PREFIX""_REPO_MODIFIED $GIT_MODIFIED" >> $GIT_HEADER echo "" >> $GIT_HEADER @@ -74,13 +84,15 @@ if [ $GIT_MODIFIED = 1 ]; then echo "#if $PREFIX""_REPO_MODIFIED == 1" >> $GIT_HEADER echo " #ifndef GITHEAD_NOWARNING" >> $GIT_HEADER echo " #define GITHEAD_NOWARNING" >> $GIT_HEADER -echo " #pragma message (\"******************************************\")" >> $GIT_HEADER -echo " #pragma message (\"* *\")" >> $GIT_HEADER +echo " #pragma message (\"\n\"\\" >> $GIT_HEADER +echo " \"******************************************\n\"\\" >> $GIT_HEADER +echo " \"* *\n\"\\" >> $GIT_HEADER centerName "WARNING: repository modified" >> $GIT_HEADER centerName "changes not committed for" >> $GIT_HEADER centerName $REPO_NAME >> $GIT_HEADER -echo " #pragma message (\"* *\")" >> $GIT_HEADER -echo " #pragma message (\"******************************************\")" >> $GIT_HEADER +echo " \"* *\n\"\\" >> $GIT_HEADER +echo " \"******************************************\n\"\\" >> $GIT_HEADER +echo " )" >> $GIT_HEADER echo " #endif" >> $GIT_HEADER echo "#endif" >> $GIT_HEADER fi diff --git a/source/Makefile b/source/Makefile index 1155678c3..480520b08 100644 --- a/source/Makefile +++ b/source/Makefile @@ -27,7 +27,7 @@ OBJS = \ improc/imageUtils.o \ ipc/processInterface.o \ ipc/sharedMemSegment.o \ - math/gslInterpolator.cpp \ + math/gslInterpolator.o \ math/templateBLAS.o \ math/templateLapack.o \ math/fit/templateLevmar.o \ From 7e3882092a81bd261f000a9aa30b7f19d9679ae2 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Fri, 2 Jan 2026 12:51:54 -0700 Subject: [PATCH 06/21] git tracking now includes untracked files --- gengithead.sh | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/gengithead.sh b/gengithead.sh index f448f4b1c..ad4af4261 100644 --- a/gengithead.sh +++ b/gengithead.sh @@ -55,19 +55,28 @@ PREFIX=${3:-$REPO_NAME} #default output path is PREFIX_git_version.h HEADPATH=${2:-"./"$PREFIX"_git_version.h"} - - GIT_HEADER="$HEADPATH" + +# Get git status GIT_URL=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH remote get-url origin) GIT_BRANCH=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH rev-parse --abbrev-ref HEAD) GIT_VERSION=$(git --git-dir=$GITPATH/.git --work-tree=$GITPATH log -1 --format=%H) +# Check if repo is modified set +e git --git-dir=$GITPATH/.git --work-tree=$GITPATH diff-index --quiet HEAD -- GIT_MODIFIED=$? set -e +# Check if untracked files exist +set +e +GIT_UNTRACKED_STR=$(git status | grep Untracked) +set -e +GIT_UNTRACKED=0 +if [ ${#GIT_UNTRACKED_STR} -gt 0 ]; then + GIT_UNTRACKED=1 +fi echo "#ifndef $PREFIX""_GIT_VERSION_H" > $GIT_HEADER echo "#define $PREFIX""_GIT_VERSION_H" >> $GIT_HEADER @@ -78,17 +87,18 @@ echo "#define $PREFIX""_BRANCH \"$GIT_BRANCH\"" >> $GIT_HEADER echo "#define $PREFIX""_SRCPATH \"$REPO_PATH\"" >> $GIT_HEADER echo "#define $PREFIX""_CURRENT_SHA1 \"$GIT_VERSION\"" >> $GIT_HEADER echo "#define $PREFIX""_REPO_MODIFIED $GIT_MODIFIED" >> $GIT_HEADER -echo "" >> $GIT_HEADER -echo "" >> $GIT_HEADER +echo "#define $PREFIX""_REPO_UNTRACKED $GIT_UNTRACKED" >> $GIT_HEADER + if [ $GIT_MODIFIED = 1 ]; then +echo "" >> $GIT_HEADER echo "#if $PREFIX""_REPO_MODIFIED == 1" >> $GIT_HEADER -echo " #ifndef GITHEAD_NOWARNING" >> $GIT_HEADER -echo " #define GITHEAD_NOWARNING" >> $GIT_HEADER +echo " #ifndef "$PREFIX"_GITHEAD_NOWARNING" >> $GIT_HEADER +echo " #define "$PREFIX"_GITHEAD_NOWARNING" >> $GIT_HEADER echo " #pragma message (\"\n\"\\" >> $GIT_HEADER echo " \"******************************************\n\"\\" >> $GIT_HEADER echo " \"* *\n\"\\" >> $GIT_HEADER centerName "WARNING: repository modified" >> $GIT_HEADER -centerName "changes not committed for" >> $GIT_HEADER +centerName "changes not committed in" >> $GIT_HEADER centerName $REPO_NAME >> $GIT_HEADER echo " \"* *\n\"\\" >> $GIT_HEADER echo " \"******************************************\n\"\\" >> $GIT_HEADER @@ -96,6 +106,23 @@ echo " )" >> $GIT_HEADER echo " #endif" >> $GIT_HEADER echo "#endif" >> $GIT_HEADER fi +if [ $GIT_UNTRACKED = 1 ]; then echo "" >> $GIT_HEADER +echo "#if $PREFIX""_REPO_UNTRACKED == 1" >> $GIT_HEADER +echo " #ifndef "$PREFIX"_GITHEAD_NO_UNTRACKED_WARNING" >> $GIT_HEADER +echo " #define "$PREFIX"_GITHEAD_NO_UNTRACKED_WARNING" >> $GIT_HEADER +echo " #pragma message (\"\n\"\\" >> $GIT_HEADER +echo " \"******************************************\n\"\\" >> $GIT_HEADER +echo " \"* *\n\"\\" >> $GIT_HEADER +centerName "WARNING: repository untracked" >> $GIT_HEADER +centerName "untracked files exist in" >> $GIT_HEADER +centerName $REPO_NAME >> $GIT_HEADER +echo " \"* *\n\"\\" >> $GIT_HEADER +echo " \"******************************************\n\"\\" >> $GIT_HEADER +echo " )" >> $GIT_HEADER +echo " #endif" >> $GIT_HEADER +echo "#endif" >> $GIT_HEADER +fi + echo "" >> $GIT_HEADER echo "#endif" >> $GIT_HEADER From e3c1884ca46e951fb9232d38db599efe00cbcbd5 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Fri, 2 Jan 2026 12:52:38 -0700 Subject: [PATCH 07/21] added notes on conda install --- setup/installing_conda.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 setup/installing_conda.md diff --git a/setup/installing_conda.md b/setup/installing_conda.md new file mode 100644 index 000000000..be36b1ed6 --- /dev/null +++ b/setup/installing_conda.md @@ -0,0 +1,19 @@ +Note on installing conda on Ubuntu 24: + +mkdir -p ~/miniconda3 +wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh +bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3 +rm ~/miniconda3/miniconda.sh +source ~/miniconda3/bin/activate +conda init --all +logout +login +conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main +conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r +conda create -y -n mxlib_env +conda activate mxlib_env +bash ./setup/conda_setup_Linux.sh + +Need to install make, gcc, g++, pkg-config cmake + +cmake -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX .. From efb6c71ed8928441491cfd81c429f3000df63cd7 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 6 Jan 2026 10:21:25 -0700 Subject: [PATCH 08/21] added blas-devel to conda packages to get lapack.pc again --- setup/conda_env_setup.bash | 2 +- setup/installing_conda.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/conda_env_setup.bash b/setup/conda_env_setup.bash index 3d44c212c..7c546962d 100755 --- a/setup/conda_env_setup.bash +++ b/setup/conda_env_setup.bash @@ -1,7 +1,7 @@ #!/usr/bin/env bash function install_common_packages() { - conda install --strict-channel-priority -c conda-forge -y gcc gxx make pkg-config cmake cfitsio eigen boost openblas lapack gsl fftw + conda install --strict-channel-priority -c conda-forge -y gcc gxx make pkg-config cmake cfitsio eigen boost openblas lapack blas-devel gsl fftw #In case we switch back to mkl: #conda install --strict-channel-priority -c conda-forge -y pkg-config cmake cfitsio cppzmq eigen boost mkl mkl-include gsl fftw #conda env config vars set MKLROOT="$CONDA_PREFIX" diff --git a/setup/installing_conda.md b/setup/installing_conda.md index be36b1ed6..ca1f3b403 100644 --- a/setup/installing_conda.md +++ b/setup/installing_conda.md @@ -12,7 +12,7 @@ conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/ma conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r conda create -y -n mxlib_env conda activate mxlib_env -bash ./setup/conda_setup_Linux.sh +bash ./setup/conda_env_setup_Linux.bash Need to install make, gcc, g++, pkg-config cmake From 35862d7ccb36eb8c0d18cd8622873c492bfed36f Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 13 Jan 2026 21:53:28 -0700 Subject: [PATCH 09/21] got turbAtmo working again; updated a bunch of docs --- include/ao/analysis/aoSystem.hpp | 32 ++-- include/ao/sim/turbAtmosphere.hpp | 230 ++++++++++++++--------------- include/ao/sim/turbLayer.hpp | 88 ++++++----- include/ao/sim/turbSubHarmonic.hpp | 98 ++++++------ include/math/histogramUniform.hpp | 70 ++++++--- include/sigproc/basisUtils2D.hpp | 23 +-- include/sigproc/zernike.hpp | 133 +++++++++-------- 7 files changed, 360 insertions(+), 314 deletions(-) diff --git a/include/ao/analysis/aoSystem.hpp b/include/ao/analysis/aoSystem.hpp index b620280a0..714c28a46 100644 --- a/include/ao/analysis/aoSystem.hpp +++ b/include/ao/analysis/aoSystem.hpp @@ -86,8 +86,8 @@ class aoSystem wfs *m_wfsBeta{ nullptr }; ///< The WFS beta_p class. bool m_ownWfsBeta{ false }; ///< Flag indicating if the WFS beta_p pointer is owned by this instance. - realT m_opticalGain{ - 1 }; ///< The optical gain of the WFS, 0-1. Treated as a sensitivity reduction in measurement noise. Default 1. + realT m_opticalGain{ 1 }; /**< The optical gain of the WFS, 0-1. Treated as a sensitivity + reduction in measurement noise. Default 1.*/ protected: realT m_lam_wfs{ 0 }; ///< WFS wavelength [m] @@ -100,23 +100,23 @@ class aoSystem bool m_bin_npix{ true }; ///< Flag controlling whether or not to bin WFS pixels according to the actuator spacing. - int m_bin_opt{ - 0 }; ///< The optimum binning factor. If WFS modes are used, this is the mode index (0 to N-1). If not, it is - ///< 1 minus the pixel binning factor. It is always 1 minus the actuator binning factor. + int m_bin_opt{ 0 }; /**< The optimum binning factor. If WFS modes are used, this is the mode + index (0 to N-1). If not, it is 1 minus the pixel binning factor. + It is always 1 minus the actuator binning factor. */ - realT m_tauWFS{ 0 }; ///< Actual WFS exposure time [sec] + realT m_tauWFS{ 0 }; ///< Actual WFS exposure time [sec] - realT m_deltaTau{ 0 }; ///< Loop latency [sec] + realT m_deltaTau{ 0 }; ///< Loop latency [sec] - bool m_optTau{ true }; ///< Flag controlling whether optimum integration time is calculated (true) enforcing - ///< m_minTauWFS, or if m_tauWFS is used (false). Default: true. + bool m_optTau{ true }; ///< Flag controlling whether optimum integration time is calculated (true) enforcing + ///< m_minTauWFS, or if m_tauWFS is used (false). Default: true. - realT m_lam_sci{ 0 }; ///< Science wavelength [m] + realT m_lam_sci{ 0 }; ///< Science wavelength [m] - realT m_zeta{ 0 }; ///< Zenith angle [radians] - realT m_secZeta{ 1 }; ///< Secant of the Zenith angle (calculated) + realT m_zeta{ 0 }; ///< Zenith angle [radians] + realT m_secZeta{ 1 }; ///< Secant of the Zenith angle (calculated) - int m_fit_mn_max{ 100 }; ///< Maximum spatial frequency index to use for fitting error calculation. + int m_fit_mn_max{ 100 }; ///< Maximum spatial frequency index to use for fitting error calculation. bool m_circularLimit{ false }; ///< Flag to indicate that the spatial frequency limit is circular, not square. @@ -2038,13 +2038,13 @@ realT aoSystem::optimumTauWFS( { if( m_D == 0 ) { - internal::mxlib_error_report(error_t::paramnotset, "Diameter (D) not set." ); + internal::mxlib_error_report( error_t::paramnotset, "Diameter (D) not set." ); return -1; } if( m_F0 == 0 ) { - internal::mxlib_error_report(error_t::paramnotset, "0-mag photon flux (F0) not set." ); + internal::mxlib_error_report( error_t::paramnotset, "0-mag photon flux (F0) not set." ); return -1; } @@ -3096,7 +3096,7 @@ void aoSystem::loadConfig( app::appConfigurator &confi } else { - throw(mx::exception(error_t::invalidarg, "unknown WFS " + wfsStr + " specified")); + throw( mx::exception( error_t::invalidarg, "unknown WFS " + wfsStr + " specified" ) ); } } diff --git a/include/ao/sim/turbAtmosphere.hpp b/include/ao/sim/turbAtmosphere.hpp index ec349ae47..728931f77 100644 --- a/include/ao/sim/turbAtmosphere.hpp +++ b/include/ao/sim/turbAtmosphere.hpp @@ -77,12 +77,14 @@ namespace sim * * \ingroup mxAOSim */ -template -struct turbAtmosphere : public base::changeable> +template +struct turbAtmosphere : public base::changeable> { public: typedef _aoSystemT aoSystemT; + typedef _verboseT verboseT; + typedef typename aoSystemT::realT realT; typedef Eigen::Array imageT; @@ -90,28 +92,29 @@ struct turbAtmosphere : public base::changeable> /** \name Configuration Data * @{ */ - uint32_t m_wfSz{ 0 }; ///< Size of the wavefront in pixels. + uint32_t m_wfSz{ 0 }; ///< Size of the wavefront in pixels. - uint32_t m_buffSz{ 0 }; ///< Buffer to apply around wavefront for interpolation. + uint32_t m_buffSz{ 0 }; ///< Buffer to apply around wavefront for interpolation. - aoSystemT *m_aosys{ nullptr }; ///< The AO system object containing all system parameters. See \ref aoSystem. + aoSystemT *m_aosys{ nullptr }; ///< The AO system object containing all system parameters. See \ref aoSystem. - uint32_t m_shLevel{ 0 }; /**< Number of subharmonic levels. 0 means no subharmonics. Generally only - * 1 level is needed to obtain good Tip/Tilt results. See \ref turbSubHarmonic. - */ + uint32_t m_shLevel{ 0 }; /**< Number of subharmonic levels. 0 means no subharmonics. Generally only + * 1 level is needed to obtain good Tip/Tilt results. See \ref turbSubHarmonic. + */ bool m_outerSubHarmonics{ true }; /**< Whether or not the outer subharmonics are included. * See \ref m_turbSubHarmonic. */ + bool m_shPreCalc{ true }; /**< Whether or not to pre-calculate the subharmonic modes. This is a * trade of speed vs. memory use. See \ref turbSubHarmonic. */ - bool m_retain{ false }; /**< Whether or not to retain working memory after screen generation. One - * would set to true if many screens will be calculated in monte carlo fashion. - * For a standard case of one set of screens being shifted by wind velocity, - * this should be false to minimize memory use. - */ + bool m_retain{ false }; /**< Whether or not to retain working memory after screen generation. One + * would set to true if many screens will be calculated in monte carlo fashion. + * For a standard case of one set of screens being shifted by wind velocity, + * this should be false to minimize memory use. + */ bool m_forceGen{ true }; /**< Force generation of new screens if true. Note that this class does not presently * do any checks of saved screens to verify they match the current configuration. @@ -124,7 +127,7 @@ struct turbAtmosphere : public base::changeable> * to \ref m_forceGen` == true`. */ - imageT *m_pupil{ 0 }; ///< A pointer to the pupil mask. + imageT *m_pupil{ 0 }; ///< A pointer to the pupil mask. realT m_timeStep{ 0 }; ///< Length of each iteration, in seconds. @@ -133,13 +136,13 @@ struct turbAtmosphere : public base::changeable> /** \name Internal State * @{ */ - std::vector> m_layers; ///< Vector of turbulent layers. + std::vector> m_layers; ///< Vector of turbulent layers. std::vector> m_subHarms; ///< Sub-harmonic layers - size_t m_frames{ 0 }; ///< Length of the turbulence sequence. + size_t m_frames{ 0 }; ///< Length of the turbulence sequence. - int m_nWf{ 0 }; ///< Number of iterations which have occurred. + int m_nWf{ 0 }; ///< Number of iterations which have occurred. math::normDistT m_normVar; ///< Normal random deviate generator. This seeded in the constructor. @@ -154,11 +157,11 @@ struct turbAtmosphere : public base::changeable> /// Setup the overall atmosphere. /** */ - void setup( - uint32_t wfSz, ///< [in] The size of the wavefront in pixels. - uint32_t buffSz, ///< [in] The size of the interpolation buffer to use. - aoSystemT *aosys, ///< [in] Pointer to an AO System. See \ref m_aosys. - uint32_t shLevel ///< [in] number of subharmonic levels to use. 0 turns off subharmonics. See \ref m_shLevel. + void setup( uint32_t wfSz, ///< [in] The size of the wavefront in pixels. + uint32_t buffSz, ///< [in] The size of the interpolation buffer to use. + aoSystemT *aosys, ///< [in] Pointer to an AO System. See \ref m_aosys. + uint32_t shLevel /**< [in] number of subharmonic levels to use. 0 turns off subharmonics. + See \ref m_shLevel.*/ ); /// Set the wavefront size @@ -338,14 +341,14 @@ struct turbAtmosphere : public base::changeable> void nextWF( wavefront &wf ); }; -template -turbAtmosphere::turbAtmosphere() +template +turbAtmosphere::turbAtmosphere() { m_normVar.seed(); } -template -void turbAtmosphere::setup( uint32_t ws, uint32_t bs, aoSystemT *aos, uint32_t shl ) +template +void turbAtmosphere::setup( uint32_t ws, uint32_t bs, aoSystemT *aos, uint32_t shl ) { wfSz( ws ); buffSz( bs ); @@ -353,8 +356,8 @@ void turbAtmosphere::setup( uint32_t ws, uint32_t bs, aoSystemT *aos, shLevel( shl ); } -template -void turbAtmosphere::wfSz( uint32_t ws ) +template +void turbAtmosphere::wfSz( uint32_t ws ) { if( ws != m_wfSz ) { @@ -363,14 +366,14 @@ void turbAtmosphere::wfSz( uint32_t ws ) } } -template -uint32_t turbAtmosphere::wfSz() +template +uint32_t turbAtmosphere::wfSz() { return m_wfSz; } -template -void turbAtmosphere::buffSz( uint32_t bs ) +template +void turbAtmosphere::buffSz( uint32_t bs ) { if( bs != m_buffSz ) { @@ -379,14 +382,14 @@ void turbAtmosphere::buffSz( uint32_t bs ) } } -template -uint32_t turbAtmosphere::buffSz() +template +uint32_t turbAtmosphere::buffSz() { return m_buffSz; } -template -void turbAtmosphere::aosys( aoSystemT *aos ) +template +void turbAtmosphere::aosys( aoSystemT *aos ) { if( aos != m_aosys ) { @@ -395,14 +398,14 @@ void turbAtmosphere::aosys( aoSystemT *aos ) } } -template -aoSystemT *turbAtmosphere::aosys() +template +aoSystemT *turbAtmosphere::aosys() { return m_aosys; } -template -void turbAtmosphere::shLevel( uint32_t shl ) +template +void turbAtmosphere::shLevel( uint32_t shl ) { if( shl != m_shLevel ) { @@ -411,14 +414,14 @@ void turbAtmosphere::shLevel( uint32_t shl ) } } -template -uint32_t turbAtmosphere::shLevel() +template +uint32_t turbAtmosphere::shLevel() { return m_shLevel; } -template -void turbAtmosphere::outerSubHarmonics( bool osh ) +template +void turbAtmosphere::outerSubHarmonics( bool osh ) { if( osh != m_outerSubHarmonics ) { @@ -427,14 +430,14 @@ void turbAtmosphere::outerSubHarmonics( bool osh ) } } -template -bool turbAtmosphere::outerSubHarmonics() +template +bool turbAtmosphere::outerSubHarmonics() { return m_outerSubHarmonics; } -template -void turbAtmosphere::shPreCalc( bool shp ) +template +void turbAtmosphere::shPreCalc( bool shp ) { if( shp != m_shPreCalc ) { @@ -443,14 +446,14 @@ void turbAtmosphere::shPreCalc( bool shp ) } } -template -bool turbAtmosphere::shPreCalc() +template +bool turbAtmosphere::shPreCalc() { return m_shPreCalc; } -template -void turbAtmosphere::retain( bool rtn ) +template +void turbAtmosphere::retain( bool rtn ) { if( rtn != m_retain ) { @@ -459,14 +462,14 @@ void turbAtmosphere::retain( bool rtn ) } } -template -bool turbAtmosphere::retain() +template +bool turbAtmosphere::retain() { return m_retain; } -template -void turbAtmosphere::forceGen( bool fg ) +template +void turbAtmosphere::forceGen( bool fg ) { if( fg != m_forceGen ) { @@ -476,14 +479,14 @@ void turbAtmosphere::forceGen( bool fg ) m_forceGen = fg; } -template -bool turbAtmosphere::forceGen() +template +bool turbAtmosphere::forceGen() { return m_forceGen; } -template -void turbAtmosphere::dataDir( const std::string &dd ) +template +void turbAtmosphere::dataDir( const std::string &dd ) { if( dd != m_dataDir ) { @@ -493,28 +496,27 @@ void turbAtmosphere::dataDir( const std::string &dd ) m_dataDir = dd; } -template -std::string turbAtmosphere::dataDir() +template +std::string turbAtmosphere::dataDir() { return m_dataDir; } -template -void turbAtmosphere::setLayers( const std::vector &scrnSz ) +template +void turbAtmosphere::setLayers( const std::vector &scrnSz ) { if( m_aosys == nullptr ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbAtmosphere::setLayers", "ao system is not set (m_aosys is nullptr)" ); + throw mx::exception( error_t::paramnotset, "ao system is not set (m_aosys is nullptr)" ); } size_t nLayers = scrnSz.size(); if( nLayers != m_aosys->atm.n_layers() ) { - mxThrowException( err::invalidarg, - "mx::AO::sim::turbAtmosphere::setLayers", - "Size of scrnSz vector does not match atmosphere." ); + throw mx::exception( error_t::invalidarg, + + "Size of scrnSz vector does not match atmosphere." ); } if( nLayers != m_layers.size() ) @@ -549,13 +551,14 @@ void turbAtmosphere::setLayers( const std::vector &scrnSz ) } } -template -void turbAtmosphere::setLayers( const size_t scrnSz ) +template +void turbAtmosphere::setLayers( const size_t scrnSz ) { if( m_aosys == nullptr ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbAtmosphere::setLayers", "atmosphere is not set (m_atm is nullptr)" ); + throw mx::exception( error_t::paramnotset, + + "atmosphere is not set (m_atm is nullptr)" ); } size_t n = m_aosys->atm.n_layers(); @@ -563,37 +566,37 @@ void turbAtmosphere::setLayers( const size_t scrnSz ) setLayers( std::vector( n, scrnSz ) ); } -template -uint32_t turbAtmosphere::nLayers() +template +uint32_t turbAtmosphere::nLayers() { return m_layers.size(); } -template -turbLayer &turbAtmosphere::layer( uint32_t n ) +template +turbLayer &turbAtmosphere::layer( uint32_t n ) { if( n >= m_layers.size() ) { - mxThrowException( err::invalidarg, "mx::AO::sim::turbAtmosphere::layer", "n too large for number of layers." ); + throw mx::exception( error_t::invalidarg, "n too large for number of layers." ); } return m_layers[n]; } -template -math::normDistT &turbAtmosphere::normVar() +template +math::normDistT &turbAtmosphere::normVar() { return m_normVar; } -template -uint32_t turbAtmosphere::scrnLengthPixels() +template +uint32_t turbAtmosphere::scrnLengthPixels() { return 0; } -template -uint32_t turbAtmosphere::scrnLengthPixels( uint32_t n ) +template +uint32_t turbAtmosphere::scrnLengthPixels( uint32_t n ) { return 0; @@ -603,32 +606,32 @@ uint32_t turbAtmosphere::scrnLengthPixels( uint32_t n ) */ } -template -realT turbAtmosphere::scrnLength() +template +turbAtmosphere::realT turbAtmosphere::scrnLength() { return 0; } -template -realT turbAtmosphere::scrnLength( uint32_t n ) +template +turbAtmosphere::realT turbAtmosphere::scrnLength( uint32_t n ) { return 0; } -template -uint32_t turbAtmosphere::maxShift( realT dt ) +template +uint32_t turbAtmosphere::maxShift( realT dt ) { return 0; } -template -uint32_t turbAtmosphere::maxShift( uint32_t n, realT dt ) +template +uint32_t turbAtmosphere::maxShift( uint32_t n, realT dt ) { return 0; } -template -void turbAtmosphere::genLayers() +template +void turbAtmosphere::genLayers() { if( m_dataDir != "" && !m_forceGen ) { @@ -655,17 +658,17 @@ void turbAtmosphere::genLayers() { if( m_layers[i].m_phase.rows() != m_layers[i].scrnSz() || m_layers[i].m_phase.cols() != m_layers[i].scrnSz() ) { - mxThrowException( err::sizeerr, "mx::AO::sim::turbAtmosphere::genLayers", "layer phase not allocated." ); + throw mx::exception( error_t::sizeerr, "layer phase not allocated." ); } if( m_layers[i].m_freq.rows() != m_layers[i].scrnSz() || m_layers[i].m_freq.cols() != m_layers[i].scrnSz() ) { - mxThrowException( err::sizeerr, "mx::AO::sim::turbAtmosphere::genLayers", "layer freq not allocated." ); + throw mx::exception( error_t::sizeerr, "layer freq not allocated." ); } if( m_layers[i].m_psd.rows() != m_layers[i].scrnSz() || m_layers[i].m_psd.cols() != m_layers[i].scrnSz() ) { - mxThrowException( err::sizeerr, "mx::AO::sim::turbAtmosphere::genLayers", "layer psd not allocated." ); + throw mx::exception( error_t::sizeerr, "layer psd not allocated." ); } for( size_t jj = 0; jj < m_layers[i].m_scrnSz; ++jj ) @@ -719,14 +722,12 @@ void turbAtmosphere::genLayers() this->setChangePoint(); } -template -int turbAtmosphere::shift( improc::milkImage &milkPhase, realT dt ) +template +int turbAtmosphere::shift( improc::milkImage &milkPhase, realT dt ) { if( this->isChanged() ) { - mxThrowException( err::invalidconfig, - "mx::AO::sim::turbAtmosphere::shift", - "configuration has changed but genLayers has not been run." ); + throw mx::exception( error_t::invalidconfig, "configuration has changed but genLayers has not been run." ); } improc::eigenMap phase( milkPhase ); @@ -738,7 +739,8 @@ int turbAtmosphere::shift( improc::milkImage &milkPhase, realT // Don't use OMP if no multiple layers b/c it seems to make it use multiple threads in some awful way if( m_layers.size() > 1 ) { -#pragma omp parallel for + // clang-format off + #pragma omp parallel for // clang-format on for( size_t j = 0; j < m_layers.size(); ++j ) { m_layers[j].shift( dt ); @@ -764,40 +766,38 @@ int turbAtmosphere::shift( improc::milkImage &milkPhase, realT return 0; } -template -int turbAtmosphere::frames( int f ) +template +int turbAtmosphere::frames( int f ) { m_frames = f; return 0; } -template -size_t turbAtmosphere::frames() +template +size_t turbAtmosphere::frames() { return m_frames; } -template -int turbAtmosphere::wfPS( realT ps ) +template +int turbAtmosphere::wfPS( realT ps ) { static_cast( ps ); return 0; } -template -void turbAtmosphere::nextWF( wavefront &wf ) +template +void turbAtmosphere::nextWF( wavefront &wf ) { if( !m_aosys ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbAtmosphere::nextWF", "the AO system pointer is not set" ); + throw mx::exception( error_t::paramnotset, "the AO system pointer is not set" ); } if( !m_pupil ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbAtmosphere::nextWF", "the m_pupil pointer is not set" ); + throw mx::exception( error_t::paramnotset, "the m_pupil pointer is not set" ); } shift( wf.phase, m_nWf * m_timeStep ); diff --git a/include/ao/sim/turbLayer.hpp b/include/ao/sim/turbLayer.hpp index afe8e323b..89baa69d2 100644 --- a/include/ao/sim/turbLayer.hpp +++ b/include/ao/sim/turbLayer.hpp @@ -42,7 +42,7 @@ namespace AO namespace sim { -template +template struct turbAtmosphere; /// Simulation of a single turbulent layer @@ -51,14 +51,16 @@ struct turbAtmosphere; * * \ingroup mxAOSim */ -template +template struct turbLayer { typedef _aoSystemT aoSystemT; + typedef _verboseT verboseT; + typedef typename aoSystemT::realT realT; typedef Eigen::Array imageT; - turbAtmosphere *m_parent{ nullptr }; + turbAtmosphere *m_parent{ nullptr }; uint32_t m_layerNo; @@ -83,7 +85,7 @@ struct turbLayer mx::math::uniDistT uniVar; ///< Uniform deviate, used in shiftRandom. - void setLayer( turbAtmosphere *parent, int layerNo, uint32_t scrnSz ); + void setLayer( turbAtmosphere *parent, int layerNo, uint32_t scrnSz ); uint32_t layerNo(); @@ -115,12 +117,14 @@ struct turbLayer }; // template -// turbLayer::turbLayer() +// turbLayer::turbLayer() // { // } -template -void turbLayer::setLayer( turbAtmosphere *parent, int layerNo, uint32_t scrnSz ) +template +void turbLayer::setLayer( turbAtmosphere *parent, + int layerNo, + uint32_t scrnSz ) { m_parent = parent; m_layerNo = layerNo; @@ -129,15 +133,12 @@ void turbLayer::setLayer( turbAtmosphere *parent, int laye if( m_parent == nullptr ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbLayer::setLayer", "parent is not set (m_parent is nullptr)" ); + throw mx::exception( error_t::paramnotset, "parent is not set (m_parent is nullptr)" ); } if( m_parent->aosys() == nullptr ) { - mxThrowException( err::paramnotset, - "mx::AO::sim::turbLayer::setLayer", - "parent ao system is not set (m_parent->aosys() is nullptr)" ); + throw mx::exception( error_t::paramnotset, "parent is not set (m_parent->aosys() is nullptr)" ); } realT vwind = m_parent->aosys()->atm.layer_v_wind( m_layerNo ); @@ -151,32 +152,29 @@ void turbLayer::setLayer( turbAtmosphere *parent, int laye alloc(); } -template -uint32_t turbLayer::layerNo() +template +uint32_t turbLayer::layerNo() { return m_layerNo; } -template -uint32_t turbLayer::scrnSz() +template +uint32_t turbLayer::scrnSz() { return m_scrnSz; } -template -void turbLayer::alloc() +template +void turbLayer::alloc() { if( m_parent == nullptr ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbLayer::alloc", "parent is not set (m_parent is nullptr)" ); + throw mx::exception( error_t::paramnotset, "parent is not set (m_parent is nullptr)" ); } if( m_parent->aosys() == nullptr ) { - mxThrowException( err::paramnotset, - "mx::AO::sim::turbLayer::alloc", - "parent ao system is not set (m_parent->aosys() is nullptr)" ); + throw mx::exception( error_t::paramnotset, "parent is not set (m_parent->aosys() is nullptr)" ); } m_phase.resize( m_scrnSz, m_scrnSz ); @@ -213,11 +211,16 @@ void turbLayer::alloc() realT L02; if( L0 > 0 ) + { L02 = 1.0 / ( L0 * L0 ); + } else + { L02 = 0; + } -#pragma omp parallel for + // clang-format off + #pragma omp parallel for // clang-format on for( size_t jj = 0; jj < m_scrnSz; ++jj ) { for( size_t ii = 0; ii < m_scrnSz; ++ii ) @@ -231,7 +234,9 @@ void turbLayer::alloc() { p = beta / pow( pow( m_freq( ii, jj ), 2 ) + L02, sqrt_alpha ); if( l0 > 0 ) + { p *= exp( -1 * pow( m_freq( ii, jj ) * l0, 2 ) ); + } } realT Ppiston = 0; @@ -272,15 +277,15 @@ void turbLayer::alloc() } } -template -void turbLayer::genDealloc() +template +void turbLayer::genDealloc() { m_freq.resize( 0, 0 ); m_psd.resize( 0, 0 ); } -template -void turbLayer::shift( realT dt ) +template +void turbLayer::shift( realT dt ) { int wdx, wdy; realT ddx, ddy; @@ -297,28 +302,35 @@ void turbLayer::shift( realT dt ) wdx %= m_scrnSz; wdy %= m_scrnSz; - // Check for a new whole-pixel shift - if( wdx != m_last_wdx || wdy != m_last_wdy ) + if( dt == 0 ) { - // Need a whole pixel shift (this also extracts the m_wfSz + m_buffSz subarray) - improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); + m_shiftPhase = m_phase; } + else + { + // Check for a new whole-pixel shift + if( wdx != m_last_wdx || wdy != m_last_wdy ) + { + // Need a whole pixel shift (this also extracts the m_wfSz + m_buffSz subarray) + improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); + } - // Do the sub-pixel shift - improc::imageShift( m_shiftPhase, m_shiftPhaseWP, ddx, ddy, improc::cubicConvolTransform( -0.5 ) ); + // Do the sub-pixel shift + improc::imageShift( m_shiftPhase, m_shiftPhaseWP, ddx, ddy, improc::cubicConvolTransform( -0.5 ) ); + } m_last_wdx = wdx; m_last_wdy = wdy; } -template -void turbLayer::initRandom() +template +void turbLayer::initRandom() { uniVar.seed(); } -template -void turbLayer::shiftRandom( bool nofract ) +template +void turbLayer::shiftRandom( bool nofract ) { int wdx, wdy; realT ddx, ddy; diff --git a/include/ao/sim/turbSubHarmonic.hpp b/include/ao/sim/turbSubHarmonic.hpp index aaf400b18..953eca385 100644 --- a/include/ao/sim/turbSubHarmonic.hpp +++ b/include/ao/sim/turbSubHarmonic.hpp @@ -54,12 +54,14 @@ namespace sim * * \ingroup mxAOSim */ -template +template class turbSubHarmonic : public base::changeable> { public: typedef _turbAtmosphereT turbAtmosphereT; + typedef _verboseT verboseT; + typedef typename turbAtmosphereT::realT realT; protected: @@ -69,11 +71,11 @@ class turbSubHarmonic : public base::changeable m_noise; ///< Vector of Gaussian deviates prepared for each screen generation. + std::vector m_noise; ///< Vector of Gaussian deviates prepared for each screen generation. - std::vector m_m; ///< m-coordinate fractional spatial frequency indices of the subharmonics - std::vector m_n; ///< n-coordinate fractional spatial frequency indices of the subharmonics + std::vector m_m; ///< m-coordinate fractional spatial frequency indices of the subharmonics + std::vector m_n; ///< n-coordinate fractional spatial frequency indices of the subharmonics - std::vector m_sqrtPSD; // the square-root of the PSD at each point + std::vector m_sqrtPSD; // the square-root of the PSD at each point improc::eigenCube m_modes; ///< the pre-calculated modes - ///@} + ///@} public: /** \name Construction @@ -160,13 +162,13 @@ class turbSubHarmonic : public base::changeable -turbSubHarmonic::turbSubHarmonic() +template +turbSubHarmonic::turbSubHarmonic() { } -template -void turbSubHarmonic::turbAtmo( turbAtmosphereT *turbatm ) +template +void turbSubHarmonic::turbAtmo( turbAtmosphereT *turbatm ) { if( turbatm != m_turbAtmo ) { @@ -175,14 +177,14 @@ void turbSubHarmonic::turbAtmo( turbAtmosphereT *turbatm ) } } -template -turbAtmosphereT *turbSubHarmonic::turbAtmo() +template +turbAtmosphereT *turbSubHarmonic::turbAtmo() { return m_turbAtmo; } -template -void turbSubHarmonic::level( uint32_t ml ) +template +void turbSubHarmonic::level( uint32_t ml ) { if( ml != m_level ) { @@ -191,14 +193,14 @@ void turbSubHarmonic::level( uint32_t ml ) } } -template -uint32_t turbSubHarmonic::level() +template +uint32_t turbSubHarmonic::level() { return m_level; } -template -void turbSubHarmonic::outerSubHarmonics( bool osh ) +template +void turbSubHarmonic::outerSubHarmonics( bool osh ) { if( osh != m_outerSubHarmonics ) { @@ -207,14 +209,14 @@ void turbSubHarmonic::outerSubHarmonics( bool osh ) } } -template -bool turbSubHarmonic::outerSubHarmonics() +template +bool turbSubHarmonic::outerSubHarmonics() { return m_outerSubHarmonics; } -template -void turbSubHarmonic::preCalc( bool pc ) +template +void turbSubHarmonic::preCalc( bool pc ) { if( pc != m_preCalc ) { @@ -223,36 +225,31 @@ void turbSubHarmonic::preCalc( bool pc ) } } -template -bool turbSubHarmonic::preCalc() +template +bool turbSubHarmonic::preCalc() { return m_preCalc; } -template -void turbSubHarmonic::initGrid( uint32_t layerNo ) +template +void turbSubHarmonic::initGrid( uint32_t layerNo ) { int N; if( m_turbAtmo == nullptr ) { - mxThrowException( err::paramnotset, - "mx::AO::sim::turbSubHarmonic::initGrid", - "atmosphere is not set (m_turbAtmo is nullptr)" ); + throw mx::exception( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" ); } if( m_turbAtmo->aosys() == nullptr ) { - mxThrowException( err::paramnotset, - "mx::AO::sim::turbSubHarmonic::initGrid", - "ao system is not set (m_turbAtmo->m_aosys is nullptr)" ); + throw mx::exception( error_t::paramnotset, "ao system is not set (m_turbAtmo->m_aosys is nullptr)" ); } if( m_turbAtmo->nLayers() <= layerNo ) { - mxThrowException( err::invalidconfig, - "mx::AO::sim::turbSubHarmonic::initGrid", - "atmosphere is not setup (m_turbAtmo->m_layers size is <= layerNo)" ); + throw mx::exception( error_t::invalidconfig, + "atmosphere is not setup (m_turbAtmo->m_layers size is <= layerNo)" ); } if( m_level == 0 ) @@ -386,8 +383,8 @@ void turbSubHarmonic::initGrid( uint32_t layerNo ) this->setChangePoint(); } -template -void turbSubHarmonic::screen( improc::eigenImage &scrn ) +template +void turbSubHarmonic::screen( improc::eigenImage &scrn ) { if( m_level == 0 ) { @@ -396,20 +393,17 @@ void turbSubHarmonic::screen( improc::eigenImage &scrn ) if( m_turbAtmo == nullptr ) { - mxThrowException( - err::paramnotset, "mx::AO::sim::turbSubHarmonic::screen", "atmosphere is not set (m_turbAtmo is nullptr)" ); + throw mx::exception( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" ); } if( this->isChanged() ) { - mxThrowException( err::invalidconfig, - "mx::AO::sim::turbSubHarmonic::screen", - "configuration has changed but not re-initialized" ); + throw mx::exception( error_t::invalidconfig, "configuration has changed but not re-initialized" ); } if( scrn.rows() != m_scrnSz || scrn.cols() != m_scrnSz ) { - mxThrowException( err::sizeerr, "mx::AO::sim::turbSubHarmonic::screen", "input screen is not the right size" ); + throw mx::exception( error_t::sizeerr, "input screen is not the right size" ); } // Check that we're allocated @@ -417,16 +411,14 @@ void turbSubHarmonic::screen( improc::eigenImage &scrn ) { if( m_modes.rows() != scrn.rows() || m_modes.cols() != scrn.cols() || m_modes.planes() != m_noise.size() ) { - mxThrowException( - err::sizeerr, "mx::AO::sim::turbSubHarmonic::screen", "modes cube wrong size, call initGrid()." ); + throw mx::exception( error_t::sizeerr, "modes cube wrong size, call initGrid()." ); } } else { if( m_noise.size() != m_m.size() || m_noise.size() != m_n.size() || m_sqrtPSD.size() != m_noise.size() ) { - mxThrowException( - err::sizeerr, "mx::AO::sim::turbSubHarmonic::screen", "vectors not allocated, call initGrid()." ); + throw mx::exception( error_t::sizeerr, "vectors not allocated, call initGrid()." ); } } @@ -463,8 +455,8 @@ void turbSubHarmonic::screen( improc::eigenImage &scrn ) } } -template -void turbSubHarmonic::deinit() +template +void turbSubHarmonic::deinit() { m_m.clear(); m_n.clear(); diff --git a/include/math/histogramUniform.hpp b/include/math/histogramUniform.hpp index 5f2f8df2a..811511fdf 100644 --- a/include/math/histogramUniform.hpp +++ b/include/math/histogramUniform.hpp @@ -42,12 +42,14 @@ namespace math template class histogramUniform { - public: - realT m_min{ 0 }; ///< The mininum bin location - realT m_max{ 0 }; ///< The maximum bin location - realT m_width{ 0 }; ///< The bin width + protected: + realT m_min{ 0 }; ///< The mininum bin location + realT m_max{ 0 }; ///< The maximum bin location + realT m_width{ 0 }; ///< The bin width + + std::vector m_freqs; ///< The frequencies, one for each bin. - std::vector _freqs; ///< The frequencies, one for each bin. + public: /// Default c'tor, does not allocate. /** Must call setup before use */ @@ -55,9 +57,9 @@ class histogramUniform { } - /// Setup the histogram, performing allocations. - histogramUniform( realT mn, ///< [in] the new minimum bin location - realT mx, ///< [in] the new maximum bin location + /// C'tor to setup the histogram, performing allocations. + histogramUniform( realT mn, ///< [in] the minimum bin location + realT mx, ///< [in] the maximum bin location realT w ///< [in] the bin width ) : m_min( mn ), m_max( mx ), m_width( w ) @@ -65,6 +67,26 @@ class histogramUniform reset(); } + /// C'tor to setup the histogram, performing allocations, and accumulate from a vector of values. + /** + * Optionally normalizes the histogram + */ + histogramUniform( realT mn, ///< [in] the minimum bin location + realT mx, ///< [in] the maximum bin location + realT w, ///< [in] the bin width + const std::vector &vals, ///< [in] The vector of values to accumulate + bool normalize = false ///< [in] [opt] whether or not to normalize after accumulation + ) + : m_min( mn ), m_max( mx ), m_width( w ) + { + reset(); + accum( vals ); + if( normalize ) + { + this->normalize(); + } + } + /// Setup the histogram, performing allocations. void setup( realT mn, ///< [in] the new minimum bin location realT mx, ///< [in] the new maximum bin location @@ -81,7 +103,7 @@ class histogramUniform /// Resize and 0 the frequency vector. Assumes m_min, m_max, and m_width are set. void reset() { - _freqs.resize( ( m_max - m_min ) / m_width + 1, 0 ); + m_freqs.resize( ( m_max - m_min ) / m_width + 1, 0 ); } /// Accumulate a value in the appropriate bin. @@ -89,30 +111,36 @@ class histogramUniform { int i = ( val - m_min ) / m_width; if( i < 0 ) + { i = 0; - if( i >= _freqs.size() ) - i = _freqs.size() - 1; + } + if( i >= m_freqs.size() ) + { + i = m_freqs.size() - 1; + } - ++_freqs[i]; + ++m_freqs[i]; } /// Accumulate a vector of values. void accum( const std::vector &vals /**< [in] The vector of values to accumulate */ ) { for( int i = 0; i < vals.size(); ++i ) + { accum( vals[i] ); + } } /// Get the frequency in the i-th bin. realT freq( int i /**< [in] the bin number */ ) { - return _freqs[i]; ///\returns the current value of _freqs[i]. + return m_freqs[i]; ///\returns the current value of m_freqs[i]. } /// Get the number of bins int bins() { - return _freqs.size(); ///\returns the size of the frequency vector. + return m_freqs.size(); ///\returns the size of the frequency vector. } /// Get the value of the left-edge of the i-th bin. @@ -137,20 +165,20 @@ class histogramUniform /** This normalizes the histogram so that it is a probability distribution, such that the sum * \f$ \sum_i P_i \Delta x = 1 \f$ where \f$ \Delta x \f$ is the bin width. */ - void normalize( - int excludeTop = - 0 /**< [in] [optional] specifies a number of bins at the top of the range to exclude from the sum */ ) + void normalize( int excludeTop = 0 /**< [in] [optional] specifies a number of bins at the + top of the range to exclude from the sum */ + ) { realT sum = 0; - for( int i = 0; i < _freqs.size() - excludeTop; ++i ) + for( int i = 0; i < m_freqs.size() - excludeTop; ++i ) { - sum += _freqs[i]; + sum += m_freqs[i]; } - for( int i = 0; i < _freqs.size(); ++i ) + for( int i = 0; i < m_freqs.size(); ++i ) { - _freqs[i] /= ( sum * m_width ); + m_freqs[i] /= ( sum * m_width ); } } }; diff --git a/include/sigproc/basisUtils2D.hpp b/include/sigproc/basisUtils2D.hpp index ca11fd492..3c613a723 100644 --- a/include/sigproc/basisUtils2D.hpp +++ b/include/sigproc/basisUtils2D.hpp @@ -147,15 +147,20 @@ int basisNormalize( improc::eigenCube &modes, ///< [in.out] the basis to * \ingroup signal_processing */ template -int basisAmplitudes( - std::vector &s, ///< [out] the amplitudes of each mode fit to the image (will be resized). - improc::eigenImage &im, ///< [in.out] the image to fit. Is subtracted in place if desired. - improc::eigenCube &modes, ///< [in] the modes to fit. - improc::eigenImage &mask, ///< [in] the 1/0 mask which defines the domain of the fit. - bool subtract = false, ///< [in] [optional] if true then the modes are subtracted as they are fit to the image - int meanIgnore = 0, ///< [in] [optional] if 1 then the mean, or if 2 the median, value is subtracted before fitting. - ///< If subtract is false, this value is added back after the subtraction. - int N = -1 ///< [in] [optional] the number of modes to actually fit. If N < 0 then all modes are fit. +int basisAmplitudes( std::vector &s, /**< [out] the amplitudes of each mode fit to + the image (will be resized).*/ + improc::eigenImage &im, /**< [in.out] the image to fit. Is subtracted in + place if desired.*/ + improc::eigenCube &modes, ///< [in] the modes to fit. + improc::eigenImage &mask, ///< [in] the 1/0 mask which defines the domain of the fit. + bool subtract = false, /**< [in] [opt] if true then the modes are + subtracted as they are fit to the image */ + int meanIgnore = 0, /**< [in] [opt] if 1 then the mean, or if 2 the median, + value is subtracted before fitting. If subtract + is false, this value is added + back after the subtraction. */ + int N = -1 /**< [in] [opt] the number of modes to actually fit. + If N < 0 then all modes are fit. */ ) { if( N < 0 ) diff --git a/include/sigproc/zernike.hpp b/include/sigproc/zernike.hpp index d058ed5fb..6b8d49df4 100644 --- a/include/sigproc/zernike.hpp +++ b/include/sigproc/zernike.hpp @@ -41,7 +41,6 @@ #include "../math/func/sign.hpp" #include "../math/constants.hpp" - namespace mx { namespace sigproc @@ -109,7 +108,7 @@ int zernikeRCoeffs( if( n < m ) { - internal::mxlib_error_report(error_t::invalidarg, "n cannot be less than m in the Zernike polynomials" ); + internal::mxlib_error_report( error_t::invalidarg, "n cannot be less than m in the Zernike polynomials" ); return -1; } @@ -155,11 +154,11 @@ extern template int zernikeRCoeffs<__float128>( std::vector<__float128> &c, int * \tparam calcRealT is a real floating type used for internal calcs, should be at least double. */ template -realT zernikeR( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$. - int n, ///< [in] the radial index of the Zernike polynomial. - int m, ///< [in] the azimuthal index of the Zernike polynomial. - std::vector - &c ///< [in] contains the radial polynomial coeeficients, and must be of length \f$ 0.5(n-m)+1\f$. +realT zernikeR( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$. + int n, ///< [in] the radial index of the Zernike polynomial. + int m, ///< [in] the azimuthal index of the Zernike polynomial. + std::vector &c /**< [in] contains the radial polynomial coeeficients, + and must be of length \f$ 0.5(n-m)+1\f$. */ ) { m = abs( m ); @@ -173,7 +172,7 @@ realT zernikeR( realT rho, ///< [in] the radial coordinate, \f$ 0 \le \rho \le 1 if( c.size() != 0.5 * ( n - m ) + 1 ) { - internal::mxlib_error_report(error_t::invalidarg, "c vector has incorrect length for n and m." ); + internal::mxlib_error_report( error_t::invalidarg, "c vector has incorrect length for n and m." ); return -9999; } @@ -239,12 +238,6 @@ extern template __float128 zernikeR<__float128, __float128>( __float128 rho, int /// Calculate the value of a Zernike radial polynomial at a given radius and angle. /** - * \param[in] rho is the radial coordinate, \f$ 0 \le \rho \le 1 \f$. - * \param[in] phi is the azimuthal angle (in radians) - * \param[in] n is the radial index of the Zernike polynomial. - * \param[in] m is the azimuthal index of the Zernike polynomial. - * \param[in] c is contains the radial polynomial coeeficients, and must be of length \f$ 0.5(n-m)+1\f$. - * * \retval -9999 indicates a possible error * \retval R the value of the Zernike radial polynomial otherwise * @@ -252,7 +245,13 @@ extern template __float128 zernikeR<__float128, __float128>( __float128 rho, int * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template -realT zernike( realT rho, realT phi, int n, int m, std::vector &c ) +realT zernike( realT rho, /**< [in] the radial coordinate, \f$ 0 \le \rho \le 1 \f$.*/ + realT phi, /**< [in] the azimuthal angle (in radians)*/ + int n, /**< [in] the radial index of the Zernike polynomial.*/ + int m, /**< [in] the azimuthal index of the Zernike polynomial.*/ + std::vector &c /**< [in] contains the radial polynomial coeeficients, and + must be of length \f$ 0.5(n-m)+1\f$.*/ +) { realT azt; @@ -359,23 +358,22 @@ extern template __float128 zernike<__float128, __float128>( __float128 rho, __fl /// Fill in an Eigen-like array with a Zernike polynomial /** Sets any pixel which is at rad \<= r \< rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil - * - * \param[out] arr is the allocated array with an Eigen-like interface. The rows() and cols() members are used to size - * the polynomial. \param[in] n is the radial index of the polynomial \param[in] m is the azimuthal index of the - * polynomial \param[in] xcen is the x coordinate of the desired center of the polynomial, in pixels \param[in] ycen is - * the y coordinate of the desired center of the polynomial, in pixels \param[in] rad [optional] is the desired radius. - * If rad \<= 0, then the maximum radius based on dimensions of m is used. * * \tparam realT is a real floating type * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template -int zernike( arrayT &arr, - int n, - int m, - typename arrayT::Scalar xcen, - typename arrayT::Scalar ycen, - typename arrayT::Scalar rad = -1 ) +int zernike( arrayT &arr, /**< [out] allocated array with an Eigen-like interface. The rows() + and cols() members are used to size the polynomial. */ + int n, /**< [in] the radial index of the polynomial*/ + int m, /**< [in] the azimuthal index of the polynomial*/ + typename arrayT::Scalar xcen, /**< [in] the x coordinate of the desired center of the polynomial, + in pixels*/ + typename arrayT::Scalar ycen, /**< [in] the y coordinate of the desired center of the polynomial, + in pixels*/ + typename arrayT::Scalar rad = -1 /**< [in] the desired radius. If rad \<= 0, then the maximum radius + based on dimensions of m is used.*/ +) { typedef typename arrayT::Scalar realT; realT x; @@ -426,18 +424,20 @@ int zernike( arrayT &arr, /// Fill in an Eigen-like array with a Zernike polynomial /** Sets any pixel which is at rad \<= r \<= rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil * - * \param[out] arr is the allocated array with an Eigen-like interface. The rows() and cols() members are used to size - * the polynomial. \param[in] j is the Noll index of the polynomial \param[in] xcen is the x coordinate of the desired - * center of the polynomial, in pixels \param[in] ycen is the y coordinate of the desired center of the polynomial, in - * pixels \param[in] rad [optional] is the desired radius. If rad \<= 0, then the maximum radius based on dimensions of - * m is used. * * \tparam realT is a real floating type * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template int zernike( - arrayT &arr, int j, typename arrayT::Scalar xcen, typename arrayT::Scalar ycen, typename arrayT::Scalar rad = -1 ) + arrayT &arr, /**< [out] is the allocated array with an Eigen-like interface. The rows() and + cols() members are used to size the polynomial.*/ + int j, /**< [in] is the Noll index of the polynomial*/ + typename arrayT::Scalar xcen, /**< [in] is the x coordinate of the desired center of the polynomial, in pixels */ + typename arrayT::Scalar ycen, /**< [in] is the y coordinate of the desired center of the polynomial, in pixels*/ + typename arrayT::Scalar rad = -1 /**< [in] is the desired radius. If rad \<= 0, then the maximum radius + based on dimensions of m is used.*/ +) { typedef typename arrayT::Scalar realT; @@ -453,15 +453,18 @@ int zernike( /** The geometric center of the array, 0.5*(arr.rows()-1), 0.5*(arr.cols()-1), is used as the center. * Sets any pixel which is at rad \<= r \< rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil * - * \param[out] arr is the allocated array with an Eigen-like interface. The rows() and cols() members are used to size - * the polynomial. \param[in] j is the Noll index of the polynomial \param[in] rad [optional] is the desired radius. If - * rad \<= 0, then the maximum radius based on dimensions of m is used. - * * \tparam realT is a real floating type * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template -int zernike( arrayT &arr, int n, int m, typename arrayT::Scalar rad = -1 ) +int zernike( arrayT &arr, /**< [out] allocated array with an Eigen-like interface. + The rows() and cols() members are used to size + the polynomial.*/ + int n, /**< [in] the radial index of the polynomial*/ + int m, /**< [in] the azimuthal index of the polynomial*/ + typename arrayT::Scalar rad = -1 /**< [in] [opt] the desired radius. If rad \<= 0, then the + maximum radius based on dimensions of m is used.*/ +) { typename arrayT::Scalar xcen = 0.5 * ( arr.rows() - 1.0 ); typename arrayT::Scalar ycen = 0.5 * ( arr.cols() - 1.0 ); @@ -473,15 +476,17 @@ int zernike( arrayT &arr, int n, int m, typename arrayT::Scalar rad = -1 ) /** The geometric center of the array, 0.5*(arr.rows()-1), 0.5*(arr.cols()-1), is used as the center. * Sets any pixel which is at rad \<= r \< rad+0.5 pixels to rho = 1, to be consistent with mx::circularPupil * - * \param[out] arr is the allocated array with an Eigen-like interface. The rows() and cols() members are used to size - * the polynomial. \param[in] j is the Noll index of the polynomial \param[in] rad [optional] is the desired radius. If - * rad \<= 0, then the maximum radius based on dimensions of m is used. - * * \tparam arrayT is an Eigen-like array of real floating type * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template -int zernike( arrayT &arr, int j, typename arrayT::Scalar rad = -1 ) +int zernike( arrayT &arr, /**< [out] the allocated array with an Eigen-like interface. + The rows() and cols() members are used to size + the polynomial.*/ + int j, /**< [in] the Noll index of the polynomial*/ + typename arrayT::Scalar rad = -1 /**< [in] [opt] the desired radius. If rad \<= 0, then the maximum + radius based on dimensions of m is used.*/ +) { typename arrayT::Scalar xcen = 0.5 * ( arr.rows() - 1.0 ); typename arrayT::Scalar ycen = 0.5 * ( arr.cols() - 1.0 ); @@ -499,11 +504,12 @@ int zernike( arrayT &arr, int j, typename arrayT::Scalar rad = -1 ) * \tparam calcRealT is a real floating type used for internal calculations, should be at least double */ template -int zernikeBasis( - cubeT &cube, ///< [in.out] the pre-allocated cube which will be filled with the Zernike basis - typename cubeT::Scalar rad = - -1, ///< [in] [optional] the radius of the aperture. If -1 then the full image size is used. - int minj = 2 ///< [in] [optional] the minimum j value to include. The default is j=2, which skips piston (j=1). +int zernikeBasis( cubeT &cube, /**< [in/out] the pre-allocated cube which will be filled + with the Zernike basis*/ + typename cubeT::Scalar rad = -1, /**< [in] [opt] the radius of the aperture. If -1 then the full + image size is used.*/ + int minj = 2 /**< [in] [opt] the minimum j value to include. The default is j=2, + which skips piston (j=1).*/ ) { typedef typename cubeT::imageT arrayT; @@ -540,8 +546,7 @@ int zernikeBasis( */ template std::complex zernikeQ( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the - * \cite noll_1976 convention of cycles-per-radius. - */ + \cite noll_1976 convention of cycles-per-radius.*/ realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency int n, ///< [in] the Zernike polynomial n int m ///< [in] the Zernike polynomial m @@ -593,8 +598,8 @@ std::complex zernikeQ( realT k, /**< [in] the radial coordinate of norm * \tparam realT is the floating point type used for arithmetic */ template -realT zernikeQNorm( realT k, ///< [in] the radial coordinate of normalized spatial frequency. This is in the \cite - ///< noll_1976 convention of cycles-per-radius. +realT zernikeQNorm( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the + \cite noll_1976 convention of cycles-per-radius.*/ realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency int n, ///< [in] the Zernike polynomial n int m ///< [in] the Zernike polynomial m @@ -607,9 +612,13 @@ realT zernikeQNorm( realT k, ///< [in] the radial coordinate of normalized spa if( k < 0.00001 ) { if( n == 0 ) + { B = 1.0; + } else + { B = 0.0; + } } else { @@ -649,8 +658,8 @@ extern template __float128 zernikeQNorm<__float128>( __float128 k, __float128 ph * */ template -realT zernikeQNorm( realT k, ///< [in] the radial coordinate of normalized spatial frequency. This is in the \cite - ///< noll_1976 convention of cycles-per-radius. +realT zernikeQNorm( realT k, /**< [in] the radial coordinate of normalized spatial frequency. This is in the + \cite noll_1976 convention of cycles-per-radius.*/ realT phi, ///< [in] the azimuthal coordinate of normalized spatial frequency int j ///< [in] the Zernike polynomial index j (Noll convention) ) @@ -673,23 +682,23 @@ realT zernikeQNorm( realT k, ///< [in] the radial coordinate of normalized spa * \tparam arrayT is the Eigen-like array type. Arithmetic will be done in arrayT::Scalar. */ template -int zernikeQNorm( - arrayT &arr, ///< [out] the allocated array. The rows() and cols() members are used to size the transform. - arrayT &k, ///< [in] the normalized spatial frequency magnitude at each pixel. This is in the \cite noll_1976 - ///< convention of cycles-per-radius. - arrayT &phi, ///< [in] the spatial frequency angle at each pixel - int j ///< [in] the polynomial index in the Noll convention \cite noll_1976 +int zernikeQNorm( arrayT &arr, /**< [out] the allocated array. The rows() and cols() members are used to size + the transform.*/ + arrayT &k, /**< [in] the normalized spatial frequency magnitude at each pixel. This is in the \cite + noll_1976 convention of cycles-per-radius.*/ + arrayT &phi, ///< [in] the spatial frequency angle at each pixel + int j ///< [in] the polynomial index in the Noll convention \cite noll_1976 ) { if( arr.rows() != k.rows() || arr.cols() != k.cols() ) { - internal::mxlib_error_report(error_t::invalidarg, "output array and input k are not the same size" ); + internal::mxlib_error_report( error_t::invalidarg, "output array and input k are not the same size" ); return -1; } if( arr.rows() != phi.rows() || arr.cols() != phi.cols() ) { - internal::mxlib_error_report(error_t::invalidarg, "output array and input phi are not the same size" ); + internal::mxlib_error_report( error_t::invalidarg, "output array and input phi are not the same size" ); return -1; } From b205b8f349a22f3f7f88b1d222ca91b2510ee7db Mon Sep 17 00:00:00 2001 From: Jared Males Date: Wed, 14 Jan 2026 12:35:05 -0700 Subject: [PATCH 10/21] got fourCovar working again --- include/ao/analysis/fourierCovariance.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ao/analysis/fourierCovariance.hpp b/include/ao/analysis/fourierCovariance.hpp index 9d76b5b0d..21af561c4 100644 --- a/include/ao/analysis/fourierCovariance.hpp +++ b/include/ao/analysis/fourierCovariance.hpp @@ -646,7 +646,7 @@ void calcKLCoeffs( const std::string &outFile, const std::string &cvFile ) double t0 = sys::get_curr_time(); - int info = math::eigenSYEVR( evecs, evals, cv, 0, -1, 'U', &mem ); + int info = math::eigenSYEVR( evecs, evals, cv, 0, -1, 'U', &mem ); double t1 = sys::get_curr_time(); From e2579dc4fac6a999d907b92c79755b2adb0196d7 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 17 Feb 2026 17:00:19 -0700 Subject: [PATCH 11/21] fixed stoT deprecated warning text --- include/improc/imageFilters.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/include/improc/imageFilters.hpp b/include/improc/imageFilters.hpp index 697158014..7109c2c0f 100644 --- a/include/improc/imageFilters.hpp +++ b/include/improc/imageFilters.hpp @@ -8,7 +8,6 @@ #ifndef __imageFilters_hpp__ #define __imageFilters_hpp__ - #include #include @@ -541,8 +540,8 @@ template void medianFilterImage( imageOutT &fim, /**< [out] Contains the filtered image, will be resized*/ imageInT im, /**< [in] the image to be filtered*/ const kernelT &kernel, /**< [in] a fully configured kernel object*/ - int maxr = 0, /**< [in] [opt] the maximum radius from the image center to apply the kernel. - pixels outside this radius are set to 0.*/ + int maxr = 0, /**< [in] [opt] the maximum radius from the image center to apply + the kernel. Psixels outside this radius are set to 0.*/ int maxrproc = 1 ) { fim.resize( im.rows(), im.cols() ); @@ -852,7 +851,7 @@ int meanSmooth( imageTout &imOut, ///< [out] the smoothed image. Not re-allocate /// Smooth an image using the median in a rectangular box. Also Determines the location and value of the highest pixel /// in the smoothed image. -/** Calculates the median value in a rectangular box of imIn, of size medianFullSidth X medianFullWidth and stores it in +/** Calculates the median value in a rectangular box of imIn, of size medianFullWidth X medianFullWidth and stores it in * the corresonding center pixel of imOut. Does not smooth the 0.5*medianFullwidth rows and columns of the input image, * and the values of these pixels are not changed in imOut (i.e. you should 0 them before the call). * From e7b140a8b1527b2461da3433804ca5ffa1b4d1f3 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Tue, 17 Feb 2026 17:10:48 -0700 Subject: [PATCH 12/21] fixed stoT deprecated warning text --- include/ioutils/stringUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ioutils/stringUtils.hpp b/include/ioutils/stringUtils.hpp index 0bccf85eb..6408fc2db 100644 --- a/include/ioutils/stringUtils.hpp +++ b/include/ioutils/stringUtils.hpp @@ -221,7 +221,7 @@ typeT stoT( const std::string &str, /**< [in] the string to convert*/ * \deprecated */ template -[[deprecated( "Use mx::stoT instead" )]] +[[deprecated( "Use mx::ioutils::stoT instead" )]] typeT convertFromString( const std::string &str, /**< [in] the string to convert*/ error_t *errc = nullptr /**< [out] [optional] mxlib error code */ ) { From aff68038601b832669efb14a7510501a60189593 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 19 Feb 2026 09:30:31 -0700 Subject: [PATCH 13/21] Integrate tests into CMake and clean up test suite --- CMakeLists.txt | 16 + README.md | 43 +++ tests/CMakeLists.txt | 90 ++++++ tests/include/ao/sim/pyramidSensor_test.cpp | 113 +++---- tests/include/improc/imageTransforms_test.cpp | 26 +- tests/include/improc/imageUtils_test.cpp | 8 +- .../improc/imageXCorrDiscrete_test.cpp | 13 +- tests/include/improc/imageXCorrFFT_test.cpp | 278 ++++++++++-------- tests/include/ioutils/stringUtils_test.cpp | 6 +- tests/include/math/ft/fftT_test.cpp | 75 ++--- tests/include/math/ft/fftTcuda_test.cpp | 132 ++++----- tests/include/math/func/moffat_test.cpp | 6 +- tests/include/math/templateBLAS_test.cpp | 8 +- tests/include/sigproc/psdFilter_test.cpp | 12 +- tests/include/sigproc/psdUtils_test.cpp | 83 +++--- tests/include/sigproc/signalWindows_test.cpp | 12 +- tests/include/sys/timeUtils_test.cpp | 12 +- .../include/wfp/fraunhoferPropagator_test.cpp | 32 +- 18 files changed, 567 insertions(+), 398 deletions(-) create mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 08136e67a..db3792476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ option(MXLIB_USE_OPENMP "Whether or not to use OpenMP in mxlib and other librari option(MXLIB_USE_CUDA "Whether or not to use CUDA library for mxlib" ON) option(MXLIB_USE_ISIO "Whether or not to use the ImageStreamIO library for mxlib" ON) +option(MXLIB_BUILD_TESTS "Whether or not to build mxlib tests" OFF) set(MXLIB_USE_FFT_FROM "fftw" CACHE STRING "Which library to use for the FFT interface in mxlib") @@ -785,4 +786,19 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc install( PROGRAMS gengithead.sh DESTINATION bin) +############################################ +# Tests +############################################ + +if(MXLIB_BUILD_TESTS) + include(CTest) + add_subdirectory(tests) +else() + add_subdirectory(tests EXCLUDE_FROM_ALL) +endif() + +add_custom_target(tests + DEPENDS mxlibTest +) + #dump_cmake_variables(".") diff --git a/README.md b/README.md index a9c142876..68d92721f 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,46 @@ The documentation is located here: https://jaredmales.github.io/mxlib-doc/ See the [User's Guide](https://jaredmales.github.io/mxlib-doc/modules.html) for [installation instructions](https://jaredmales.github.io/mxlib-doc/group__installation.html) +## CMake Tests + +Build tests on demand (always available, even if `MXLIB_BUILD_TESTS=OFF`): + +```bash +cmake -S . -B _build +cmake --build _build --target tests -j +``` + +Configure with tests enabled: + +```bash +cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON +``` + +With `MXLIB_BUILD_TESTS=ON`, test executables are part of the default build. +With `MXLIB_BUILD_TESTS=OFF`, they are skipped by default and built only via `tests`/`mxlibTest` targets. + +Build the aggregate test executable: + +```bash +cmake --build _build --target mxlibTest -j +``` + +Run tests: + +```bash +ctest --test-dir _build --output-on-failure +``` + +Run the aggregate test executable directly: + +```bash +cmake --build _build --target mxlibTestRun +``` + +Build and run a single test source (Makefile.one equivalent): + +```bash +cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON -DMXLIB_ONE_TEST=include/math/geo_test.cpp +cmake --build _build --target mxlibTestOne -j +cmake --build _build --target mxlibTestOneRun +``` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..63d51c6f5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,90 @@ +################################################################# +# cmake configuration for mxlib tests +################################################################# + +set(MXLIB_TEST_MAIN_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/testMain.cpp) + +set(MXLIB_TEST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/include/ao/analysis/aoAtmosphere_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ao/analysis/aoSystem_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ao/analysis/aoPSDs_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/astro/astroDynamics_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ioutils/fileUtils_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ioutils/fits/fitsHeaderCard_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/ioutils/fits/fitsFile_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/math/geo_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/math/func/moffat_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/math/templateBLAS_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/math/templateLapack_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/math/randomT_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/sigproc/psdUtils_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/sigproc/psdFilter_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/sigproc/zernike_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/sigproc/signalWindows_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageTransforms_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageUtils_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/sys/timeUtils_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageXCorrFFT_test.cpp +) + +if(MXLIB_USE_CUDA) + list(APPEND MXLIB_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/math/cuda/templateCublas_test.cpp) +endif() + +add_executable(mxlibTest ${MXLIB_TEST_MAIN_SOURCE} ${MXLIB_TEST_SOURCES}) +target_link_libraries(mxlibTest PRIVATE mxlib-shared) +if(NOT MXLIB_BUILD_TESTS) + set_target_properties(mxlibTest PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +endif() + +add_custom_target(mxlibTestRun + COMMAND $ + DEPENDS mxlibTest + USES_TERMINAL +) + +if(BUILD_TESTING) + add_test(NAME mxlibTest COMMAND mxlibTest) +endif() + +set(MXLIB_ONE_TEST + "" + CACHE STRING + "Single test source to build, relative to tests/ (e.g. include/math/geo_test.cpp), similar to tests/Makefile.one" +) + +if(MXLIB_ONE_TEST) + if(IS_ABSOLUTE "${MXLIB_ONE_TEST}") + set(MXLIB_ONE_TEST_SOURCE "${MXLIB_ONE_TEST}") + else() + set(MXLIB_ONE_TEST_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/${MXLIB_ONE_TEST}") + endif() + + if(MXLIB_ONE_TEST_SOURCE MATCHES "\\.o$") + string(REGEX REPLACE "\\.o$" ".cpp" MXLIB_ONE_TEST_SOURCE "${MXLIB_ONE_TEST_SOURCE}") + elseif(MXLIB_ONE_TEST_SOURCE MATCHES "\\.hpp$") + string(REGEX REPLACE "\\.hpp$" ".cpp" MXLIB_ONE_TEST_SOURCE "${MXLIB_ONE_TEST_SOURCE}") + elseif(NOT MXLIB_ONE_TEST_SOURCE MATCHES "\\.cpp$") + string(APPEND MXLIB_ONE_TEST_SOURCE ".cpp") + endif() + + if(NOT EXISTS "${MXLIB_ONE_TEST_SOURCE}") + message(FATAL_ERROR "MXLIB_ONE_TEST source does not exist: ${MXLIB_ONE_TEST_SOURCE}") + endif() + + add_executable(mxlibTestOne ${MXLIB_TEST_MAIN_SOURCE} ${MXLIB_ONE_TEST_SOURCE}) + target_link_libraries(mxlibTestOne PRIVATE mxlib-shared) + if(NOT MXLIB_BUILD_TESTS) + set_target_properties(mxlibTestOne PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) + endif() + + add_custom_target(mxlibTestOneRun + COMMAND $ + DEPENDS mxlibTestOne + USES_TERMINAL + ) + + if(BUILD_TESTING) + add_test(NAME mxlibTestOne COMMAND mxlibTestOne) + endif() +endif() diff --git a/tests/include/ao/sim/pyramidSensor_test.cpp b/tests/include/ao/sim/pyramidSensor_test.cpp index c713c1592..7477a62cf 100644 --- a/tests/include/ao/sim/pyramidSensor_test.cpp +++ b/tests/include/ao/sim/pyramidSensor_test.cpp @@ -4,25 +4,18 @@ #define MX_NO_ERROR_REPORTS -//#define DEBUG - - -#include "../../../../include/ioutils/fits/fitsFile.hpp" -using namespace mx::fits; +// #define DEBUG #include "../../../../include/ao/sim/pyramidSensor.hpp" #include "../../../../include/ao/sim/ccdDetector.hpp" using namespace mx::AO::sim; - #include "../../../../include/improc/eigenImage.hpp" #include "../../../../include/improc/imageMasks.hpp" -//#include -//#include +// #include +// #include using namespace mx::improc; - - /// Simulate a pyramid sensor on CPU /** * \ingroup ao_sim_pyramidSensor_tests @@ -37,60 +30,53 @@ TEST_CASE( "Simulate a pyramid sensor on CPU", "[ao::sim]" ) pyramidSensor> pwfs; eigenImage pupil, fm; - pupil.resize(pupSz, pupSz); - pupil.setConstant(0); - maskCircle(pupil, 0.5 * (1.0 * pupSz - 1), 1); + pupil.resize( pupSz, pupSz ); + pupil.setConstant( 0 ); + maskCircle( pupil, 0.5 * ( 1.0 * pupSz - 1 ), 1 ); wavefront wf; realT nphperpix = 1e10 / pupil.sum(); - wf.setAmplitude(pupil * sqrt(nphperpix)); + wf.setAmplitude( pupil * sqrt( nphperpix ) ); - fm.resize(pupSz, pupSz); + fm.resize( pupSz, pupSz ); - pwfs.D(6.5); - pwfs.pupilSz(pupSz); - pwfs.wfSz(wfSz); - pwfs.pupilSep(1 + 4.1 / 56.); + pwfs.D( 6.5 ); + pwfs.pupilSz( pupSz ); + pwfs.wfSz( wfSz ); + pwfs.pupilSep( 1 + 4.1 / 56. ); - pwfs.perStep(0.5); - pwfs.modRadius(3); + pwfs.perStep( 0.5 ); + pwfs.modRadius( 3 ); - pwfs.detSize(120, 120); - pwfs.detector.noNoise(true); - pwfs.detector.expTime(1); + pwfs.detSize( 120, 120 ); + pwfs.detector.noNoise( true ); + pwfs.detector.expTime( 1 ); - pwfs.lambda(850e-9); + pwfs.lambda( 850e-9 ); - wf.setPhase(fm * 0); + wf.setPhase( fm * 0 ); - pwfs.senseWavefrontCal(wf); + pwfs.senseWavefrontCal( wf ); size_t N = 10; double t0 = mx::sys::get_curr_time(); - for(size_t n = 0; n < N; ++n) + for( size_t n = 0; n < N; ++n ) { - pwfs.senseWavefrontCal(wf); + pwfs.senseWavefrontCal( wf ); } double t1 = mx::sys::get_curr_time(); - std::cerr << "\nCPU: " << 1.0*N / (t1-t0) << " fps\n\n"; + std::cerr << "\nCPU: " << 1.0 * N / ( t1 - t0 ) << " fps\n\n"; eigenImage ref = pwfs.detectorImage.image(); - fitsFile ff; - ff.write("ref.fits", ref); - ff.write("tip.fits", pwfs.detectorImage.tipImage()); - realT s = ref.sum(); std::cout << s << '\n'; - REQUIRE(s > 9.9e9); - + REQUIRE( s > 9.9e9 ); } - - /// Simulate a pyramid sensor on GPU /** * \ingroup ao_sim_pyramidSensor_tests @@ -102,61 +88,54 @@ TEST_CASE( "Simulate a pyramid sensor on GPU", "[ao::sim]" ) uint32_t pupSz = 56.0; uint32_t wfSz = 256.0; - omp_set_num_threads(4); + omp_set_num_threads( 4 ); pyramidSensor, 1> pwfs; eigenImage pupil, fm; - pupil.resize(pupSz, pupSz); - pupil.setConstant(0); - maskCircle(pupil, 0.5 * (1.0 * pupSz - 1), 1); + pupil.resize( pupSz, pupSz ); + pupil.setConstant( 0 ); + maskCircle( pupil, 0.5 * ( 1.0 * pupSz - 1 ), 1 ); wavefront wf; realT nphperpix = 1e10 / pupil.sum(); - wf.setAmplitude(pupil * sqrt(nphperpix)); + wf.setAmplitude( pupil * sqrt( nphperpix ) ); - fm.resize(pupSz, pupSz); + fm.resize( pupSz, pupSz ); - pwfs.D(6.5); - pwfs.pupilSz(pupSz); - pwfs.wfSz(wfSz); - pwfs.pupilSep(1 + 4.1 / 56.); + pwfs.D( 6.5 ); + pwfs.pupilSz( pupSz ); + pwfs.wfSz( wfSz ); + pwfs.pupilSep( 1 + 4.1 / 56. ); - pwfs.perStep(0.5); - pwfs.modRadius(3); + pwfs.perStep( 0.5 ); + pwfs.modRadius( 3 ); - pwfs.detSize(120, 120); - pwfs.detector.noNoise(true); - pwfs.detector.expTime(1); + pwfs.detSize( 120, 120 ); + pwfs.detector.noNoise( true ); + pwfs.detector.expTime( 1 ); - pwfs.lambda(850e-9); + pwfs.lambda( 850e-9 ); - wf.setPhase(fm * 0); + wf.setPhase( fm * 0 ); - pwfs.senseWavefrontCal(wf); + pwfs.senseWavefrontCal( wf ); size_t N = 10; double t0 = mx::sys::get_curr_time(); - for(size_t n = 0; n < N; ++n) + for( size_t n = 0; n < N; ++n ) { - pwfs.senseWavefrontCal(wf); + pwfs.senseWavefrontCal( wf ); } double t1 = mx::sys::get_curr_time(); - std::cerr << "\nGPU: " << 1.0*N / (t1-t0) << " fps\n\n"; + std::cerr << "\nGPU: " << 1.0 * N / ( t1 - t0 ) << " fps\n\n"; eigenImage ref = pwfs.detectorImage.image(); - fitsFile ff; - ff.write("refgpu.fits", ref); - ff.write("tipgpu.fits", pwfs.detectorImage.tipImage()); - realT s = ref.sum(); std::cout << s << '\n'; - REQUIRE(s > 9.9e9); - + REQUIRE( s > 9.9e9 ); } - - diff --git a/tests/include/improc/imageTransforms_test.cpp b/tests/include/improc/imageTransforms_test.cpp index 5e3638e1f..f8f103f2e 100644 --- a/tests/include/improc/imageTransforms_test.cpp +++ b/tests/include/improc/imageTransforms_test.cpp @@ -43,33 +43,35 @@ SCENARIO( "Verify direction and accuracy of various image shifts", "[improc::ima mx::improc::imageShift( shift, im, -0.5, -0.5, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 127.0 ) < 1e-4 ); // should be much better than this, but this is a test - REQUIRE( fabs( fit.y0() - 127.0 ) < 1e-4 ); + REQUIRE_THAT( + fit.x0(), + Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); // should be much better than this, but this is a test + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); mx::improc::imageShift( shift, im, +0.5, +0.5, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 128.0 ) < 1e-4 ); - REQUIRE( fabs( fit.y0() - 128.0 ) < 1e-4 ); + REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); mx::improc::imageShift( shift, im, +1.0, +1.0, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 128.5 ) < 1e-4 ); - REQUIRE( fabs( fit.y0() - 128.5 ) < 1e-4 ); + REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.5, 1e-4 ) ); + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.5, 1e-4 ) ); mx::improc::imageShift( shift, im, +0.5, -0.5, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 128.0 ) < 1e-4 ); - REQUIRE( fabs( fit.y0() - 127.0 ) < 1e-4 ); + REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); mx::improc::imageShift( shift, im, -0.3, +0.7, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 127.2 ) < 1e-3 ); // non-0.5 pixel shifts are harder - REQUIRE( fabs( fit.y0() - 128.2 ) < 1e-3 ); + REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 127.2, 1e-3 ) ); // non-0.5 pixel shifts are harder + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.2, 1e-3 ) ); mx::improc::imageShift( shift, im, 1.3, -0.7, mx::improc::cubicConvolTransform() ); fit.fit(); - REQUIRE( fabs( fit.x0() - 128.8 ) < 1e-3 ); - REQUIRE( fabs( fit.y0() - 126.8 ) < 1e-3 ); + REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.8, 1e-3 ) ); + REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 126.8, 1e-3 ) ); } } } diff --git a/tests/include/improc/imageUtils_test.cpp b/tests/include/improc/imageUtils_test.cpp index 455a43bac..134f35f02 100644 --- a/tests/include/improc/imageUtils_test.cpp +++ b/tests/include/improc/imageUtils_test.cpp @@ -31,8 +31,8 @@ SCENARIO( "Verify center of light calculation", "[improc::imageCenterOfLight]" ) double x, y; mx::improc::imageCenterOfLight( x, y, im ); - REQUIRE( fabs( x - 31.5 ) < 1e-8 ); - REQUIRE( fabs( y - 31.5 ) < 1e-8 ); + REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 31.5, 1e-8 ) ); + REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 31.5, 1e-8 ) ); } WHEN( "geometric quarter" ) { @@ -44,8 +44,8 @@ SCENARIO( "Verify center of light calculation", "[improc::imageCenterOfLight]" ) double x, y; mx::improc::imageCenterOfLight( x, y, im ); - REQUIRE( fabs( x - 15.5 ) < 1e-8 ); - REQUIRE( fabs( y - 15.5 ) < 1e-8 ); + REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) ); + REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) ); } } } diff --git a/tests/include/improc/imageXCorrDiscrete_test.cpp b/tests/include/improc/imageXCorrDiscrete_test.cpp index 877499a81..5b3534cf0 100644 --- a/tests/include/improc/imageXCorrDiscrete_test.cpp +++ b/tests/include/improc/imageXCorrDiscrete_test.cpp @@ -10,7 +10,6 @@ #include "../../../include/math/func/gaussian.hpp" #include "../../../include/improc/imageXCorrDiscrete.hpp" #include "../../../include/improc/eigenCube.hpp" -#include "../../../include/ioutils/fits/fitsFile.hpp" /** Scenario: centroiding Gaussians with center of light * @@ -40,14 +39,10 @@ SCENARIO( "Verify X-Corr with center of light calculation", "[improc::imageXCorr xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.m_refIm ); - ff.write( "ccIm.fits", xcf.m_ccIm ); - std::cerr << x << " " << y << "\n"; - REQUIRE( fabs( x - 2 ) < 1e-8 ); - REQUIRE( fabs( y - 2 ) < 1e-8 ); + REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 2, 1e-8 ) ); + REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 2, 1e-8 ) ); } WHEN( "geometric quarter" ) { @@ -59,8 +54,8 @@ SCENARIO( "Verify X-Corr with center of light calculation", "[improc::imageXCorr double x, y; mx::improc::imageCenterOfLight( x, y, im ); - REQUIRE( fabs( x - 15.5 ) < 1e-8 ); - REQUIRE( fabs( y - 15.5 ) < 1e-8 ); + REQUIRE_THAT( x, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) ); + REQUIRE_THAT( y, Catch::Matchers::WithinAbs( 15.5, 1e-8 ) ); } } } diff --git a/tests/include/improc/imageXCorrFFT_test.cpp b/tests/include/improc/imageXCorrFFT_test.cpp index 98463fcf6..9850e500c 100644 --- a/tests/include/improc/imageXCorrFFT_test.cpp +++ b/tests/include/improc/imageXCorrFFT_test.cpp @@ -10,7 +10,6 @@ #include "../../../include/math/func/gaussian.hpp" #include "../../../include/improc/imageXCorrFFT.hpp" #include "../../../include/improc/eigenCube.hpp" -#include "../../../include/ioutils/fits/fitsFile.hpp" /** Scenario: centroiding Gaussians with center of light * @@ -37,24 +36,27 @@ SCENARIO( "Image cross-correlation with FFT using center of light", "[improc::im double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); xcf.maxLag( 20 ); xcf.refIm( refIm ); - xcf( x, y, im2 ); - - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+4,+4)" ) { @@ -71,24 +73,29 @@ SCENARIO( "Image cross-correlation with FFT using center of light", "[improc::im double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); + xcf.maxLag( 20 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); - xcf.maxLag( 20 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal even sizes, shift=(-3.6,+2.25)" ) { @@ -105,24 +112,28 @@ SCENARIO( "Image cross-correlation with FFT using center of light", "[improc::im double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); + xcf.maxLag( 20 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); - xcf.maxLag( 20 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+1.3,-0.6)" ) { @@ -139,24 +150,28 @@ SCENARIO( "Image cross-correlation with FFT using center of light", "[improc::im double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); + xcf.maxLag( 20 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::centerOfLight ); - xcf.maxLag( 20 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } } } @@ -186,24 +201,28 @@ SCENARIO( "Image cross-correlation with FFT using magnification peak finding", " double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; - - mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.peakMethod( mx::improc::xcorrPeakMethod::interpPeak ); xcf.maxLag( 5 ); - xcf.refIm( refIm ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); + + xcf.refIm( refIm ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+4,+4)" ) { @@ -220,10 +239,16 @@ SCENARIO( "Image cross-correlation with FFT using magnification peak finding", " double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); @@ -232,14 +257,11 @@ SCENARIO( "Image cross-correlation with FFT using magnification peak finding", " xcf.maxLag( 5 ); xcf.refIm( refIm ); - xcf( x, y, im2 ); - - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal even sizes, shift=(-3.6,+2.3)" ) { @@ -256,24 +278,28 @@ SCENARIO( "Image cross-correlation with FFT using magnification peak finding", " double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::interpPeak ); + xcf.tol( 0.1 ); mx::improc::eigenImage refIm = im0; xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::interpPeak ); - xcf.tol( 0.1 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+1.3,-0.6)" ) { @@ -290,24 +316,28 @@ SCENARIO( "Image cross-correlation with FFT using magnification peak finding", " double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::interpPeak ); + xcf.maxLag( 5 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::interpPeak ); - xcf.maxLag( 5 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } } } @@ -331,24 +361,27 @@ SCENARIO( "Image cross-correlation with FFT using Gaussian peak fit", "[improc:: double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; mx::improc::eigenImage refIm = im0; xcf.peakMethod( mx::improc::xcorrPeakMethod::gaussFit ); xcf.maxLag( 32 ); xcf.refIm( refIm ); - xcf( x, y, im2 ); - - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+4,+4)" ) { @@ -365,24 +398,28 @@ SCENARIO( "Image cross-correlation with FFT using Gaussian peak fit", "[improc:: double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::gaussFit ); + xcf.maxLag( 32 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::gaussFit ); - xcf.maxLag( 32 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal even sizes, shift=(-3.6,+2.25)" ) { @@ -399,10 +436,16 @@ SCENARIO( "Image cross-correlation with FFT using Gaussian peak fit", "[improc:: double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); @@ -410,14 +453,11 @@ SCENARIO( "Image cross-correlation with FFT using Gaussian peak fit", "[improc:: xcf.maxLag( 32 ); xcf.refIm( refIm ); - xcf( x, y, im2 ); - - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } WHEN( "ref at geometric center, equal odd sizes, shift=(+1.3,-0.6)" ) { @@ -434,24 +474,28 @@ SCENARIO( "Image cross-correlation with FFT using Gaussian peak fit", "[improc:: double ycen = 0.5 * ( im0.cols() - 1 ); mx::math::func::gaussian2D( im0.data(), im0.rows(), im0.cols(), 0., 1.0, xcen, ycen, 2 ); - mx::math::func::gaussian2D( - im2.data(), im2.rows(), im2.cols(), 0., 1.0, xcen + xshift, ycen + yshift, 2 ); - - double x, y; + mx::math::func::gaussian2D( im2.data(), + im2.rows(), + im2.cols(), + 0., + 1.0, + xcen + xshift, + ycen + yshift, + 2 ); + + double x, y, peak; mx::improc::imageXCorrFFT> xcf; + xcf.peakMethod( mx::improc::xcorrPeakMethod::gaussFit ); + xcf.maxLag( 32 ); mx::improc::eigenImage refIm = im0; //.block(10,10, im0.rows()-11, im0.cols()-11); xcf.refIm( refIm ); - xcf.peakMethod( mx::improc::xcorrPeakMethod::gaussFit ); - xcf.maxLag( 32 ); - xcf( x, y, im2 ); - mx::fits::fitsFile ff; - ff.write( "refIm.fits", xcf.refIm() ); - ff.write( "ccIm.fits", xcf.ccIm() ); + xcf( x, y, peak, im2 ); REQUIRE( x == Approx( xshift ) ); REQUIRE( y == Approx( yshift ) ); + REQUIRE( peak > 0 ); } } } diff --git a/tests/include/ioutils/stringUtils_test.cpp b/tests/include/ioutils/stringUtils_test.cpp index 2a5c7c4fc..de19c03de 100644 --- a/tests/include/ioutils/stringUtils_test.cpp +++ b/tests/include/ioutils/stringUtils_test.cpp @@ -583,16 +583,14 @@ TEST_CASE( "Converting strings to numbers", "[ioutils::stringUtils]" ) SECTION( "string valid, positive, no error check" ) { long double val = stoT( "22.2567" ); - REQUIRE( fabs( val - static_cast( 22.2567 ) ) < - fabs( 1e-9 * static_cast( 22.2567 ) ) ); + REQUIRE_THAT( static_cast( val ), Catch::Matchers::WithinRel( 22.2567, 1e-9 ) ); } SECTION( "string valid, negative, w/ error check" ) { mx::error_t errc; long double val = stoT( "-2300000.897987", &errc ); - REQUIRE( fabs( val - static_cast( -2300000.897987 ) ) < - fabs( 1e-9 * static_cast( -2300000.897987 ) ) ); + REQUIRE_THAT( static_cast( val ), Catch::Matchers::WithinRel( -2300000.897987, 1e-9 ) ); REQUIRE( errc == mx::error_t::noerror ); } diff --git a/tests/include/math/ft/fftT_test.cpp b/tests/include/math/ft/fftT_test.cpp index d53fcb64a..250bcebdb 100644 --- a/tests/include/math/ft/fftT_test.cpp +++ b/tests/include/math/ft/fftT_test.cpp @@ -32,7 +32,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, forward, default constructed, eigen interface" ) @@ -50,7 +50,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, forward, plan constructor" ) @@ -66,7 +66,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, backward" ) @@ -82,7 +82,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "in-place, forward" ) @@ -98,13 +98,13 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = in.abs2().sum() / in.rows(); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = ( in - incheck ).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + // Make sure it isn't ident + REQUIRE( rmsdiff > 0 ); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "in-place, backward" ) @@ -120,15 +120,15 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) fft( in, in ); - float sout = in.abs2().sum()/in.rows(); + float sout = in.abs2().sum() / in.rows(); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = ( in - incheck ).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + // Make sure it isn't ident + REQUIRE( rmsdiff > 0 ); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } } @@ -151,11 +151,11 @@ TEST_CASE( "2D c2c FFT with FFTW, float", "[math::ft]" ) fft( out.data(), in.data() ); - float sin = in.abs2().sum() * (out.rows()*out.cols()) ; + float sin = in.abs2().sum() * ( out.rows() * out.cols() ); float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "out-of-place, forward, default constructed, eigen interface" ) @@ -170,26 +170,27 @@ TEST_CASE( "2D c2c FFT with FFTW, float", "[math::ft]" ) fft( out, in ); float sin = in.abs2().sum(); - float sout = out.abs2().sum() / (out.rows()*out.cols()); + float sout = out.abs2().sum() / ( out.rows() * out.cols() ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "out-of-place, forward, plan constructor" ) { mx::math::ft::fftT, std::complex, 2> fft( 128, 128 ); - mx::improc::eigenImage> in( 128, 128 ), out( 128, 128 );; + mx::improc::eigenImage> in( 128, 128 ), out( 128, 128 ); + ; in.setRandom(); fft( out, in ); float sin = in.abs2().sum(); - float sout = out.abs2().sum() / (in.rows()*out.cols()); + float sout = out.abs2().sum() / ( in.rows() * out.cols() ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "out-of-place, backward" ) @@ -201,16 +202,19 @@ TEST_CASE( "2D c2c FFT with FFTW, float", "[math::ft]" ) fft( in, out ); - float sin = in.abs2().sum() / (in.rows()*in.cols()); + float sin = in.abs2().sum() / ( in.rows() * in.cols() ); float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "in-place, forward" ) { - mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::forward, true ); + mx::math::ft::fftT, std::complex, 2> fft( 128, + 128, + mx::math::ft::dir::forward, + true ); mx::improc::eigenImage> in( 128, 128 ); in.setRandom(); @@ -219,20 +223,23 @@ TEST_CASE( "2D c2c FFT with FFTW, float", "[math::ft]" ) fft( in, in ); - float sout = in.abs2().sum() / (in.rows()*in.cols()); + float sout = in.abs2().sum() / ( in.rows() * in.cols() ); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = ( in - incheck ).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + // Make sure it isn't ident + REQUIRE( rmsdiff > 0 ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "in-place, backward" ) { - mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::backward, true ); + mx::math::ft::fftT, std::complex, 2> fft( 128, + 128, + mx::math::ft::dir::backward, + true ); mx::improc::eigenImage> in( 128, 128 ); in.setRandom(); @@ -243,15 +250,15 @@ TEST_CASE( "2D c2c FFT with FFTW, float", "[math::ft]" ) fft( in, in ); - float sout = in.abs2().sum()/(in.rows()*in.cols()); + float sout = in.abs2().sum() / ( in.rows() * in.cols() ); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = ( in - incheck ).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + // Make sure it isn't ident + REQUIRE( rmsdiff > 0 ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } } #ifdef HASQUAD diff --git a/tests/include/math/ft/fftTcuda_test.cpp b/tests/include/math/ft/fftTcuda_test.cpp index 9b627a389..de6f3f2da 100644 --- a/tests/include/math/ft/fftTcuda_test.cpp +++ b/tests/include/math/ft/fftTcuda_test.cpp @@ -6,7 +6,6 @@ #include - #include "../../../../include/math/ft/fftT.hpp" #include "../../../../include/improc/eigenImage.hpp" @@ -35,7 +34,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, forward, default constructed, eigen interface" ) @@ -53,7 +52,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, forward, plan constructor" ) @@ -69,7 +68,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum() / out.rows(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "out-of-place, backward" ) @@ -85,7 +84,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "in-place, forward" ) @@ -107,7 +106,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) REQUIRE(rmsdiff > 0); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } SECTION( "in-place, backward" ) @@ -131,7 +130,7 @@ TEST_CASE( "1D c2c FFT with FFTW, float", "[math::ft]" ) REQUIRE(rmsdiff > 0); // Test by Parsevals - REQUIRE( fabs( sin - sout ) < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, 1e-3 ) ); } } #endif @@ -155,20 +154,20 @@ TEST_CASE( "2D c2c FFT with cuFFT, float", "[math::ft]" ) out.setZero(); mx::cuda::cudaPtr> devIn, devOut; - devIn.upload(in.data(), in.rows(), in.cols()); - devOut.resize(out.rows(), out.cols()); + devIn.upload( in.data(), in.rows(), in.cols() ); + devOut.resize( out.rows(), out.cols() ); cufftResult rv = fft( devOut.data(), devIn.data() ); - REQUIRE(rv == CUFFT_SUCCESS); + REQUIRE( rv == CUFFT_SUCCESS ); - devOut.download(out.data()); + devOut.download( out.data() ); - float sin = in.abs2().sum() * (out.rows()*out.cols()) ; + float sin = in.abs2().sum() * ( out.rows() * out.cols() ); float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "out-of-place, forward, default constructed, eigen interface" ) @@ -182,48 +181,47 @@ TEST_CASE( "2D c2c FFT with cuFFT, float", "[math::ft]" ) out.setZero(); mx::cuda::cudaPtr> devIn, devOut; - devIn.upload(in.data(), in.rows(), in.cols()); - devOut.resize(out.rows(), out.cols()); + devIn.upload( in.data(), in.rows(), in.cols() ); + devOut.resize( out.rows(), out.cols() ); cufftResult rv = fft( devOut, devIn ); - REQUIRE(rv == CUFFT_SUCCESS); - - devOut.download(out.data()); + REQUIRE( rv == CUFFT_SUCCESS ); + devOut.download( out.data() ); float sin = in.abs2().sum(); - float sout = out.abs2().sum() / (out.rows()*out.cols()); + float sout = out.abs2().sum() / ( out.rows() * out.cols() ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } SECTION( "out-of-place, forward, plan constructor" ) { mx::math::ft::fftT, std::complex, 2, 1> fft( 128, 128 ); - mx::improc::eigenImage> in( 128, 128 ), out( 128, 128 );; + mx::improc::eigenImage> in( 128, 128 ), out( 128, 128 ); + ; out.setZero(); mx::cuda::cudaPtr> devIn, devOut; - devIn.upload(in.data(), in.rows(), in.cols()); - devOut.resize(out.rows(), out.cols()); + devIn.upload( in.data(), in.rows(), in.cols() ); + devOut.resize( out.rows(), out.cols() ); cufftResult rv = fft( devOut, devIn ); - REQUIRE(rv == CUFFT_SUCCESS); + REQUIRE( rv == CUFFT_SUCCESS ); - devOut.download(out.data()); + devOut.download( out.data() ); float sin = in.abs2().sum(); - float sout = out.abs2().sum() / (in.rows()*out.cols()); + float sout = out.abs2().sum() / ( in.rows() * out.cols() ); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } - SECTION( "out-of-place, backward" ) { mx::math::ft::fftT, std::complex, 2, 1> fft( 128, 128, mx::math::ft::dir::backward ); @@ -233,68 +231,70 @@ TEST_CASE( "2D c2c FFT with cuFFT, float", "[math::ft]" ) in.setZero(); mx::cuda::cudaPtr> devIn, devOut; - devIn.upload(in.data(), in.rows(), in.cols()); - devOut.resize(out.rows(), out.cols()); + devIn.upload( in.data(), in.rows(), in.cols() ); + devOut.resize( out.rows(), out.cols() ); cufftResult rv = fft( devOut, devIn ); - REQUIRE(rv == CUFFT_SUCCESS); + REQUIRE( rv == CUFFT_SUCCESS ); - devOut.download(out.data()); + devOut.download( out.data() ); - float sin = in.abs2().sum() / (in.rows()*in.cols()); + float sin = in.abs2().sum() / ( in.rows() * in.cols() ); float sout = out.abs2().sum(); // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, ( sin ) * ( 1e-3 ) ) ); } -/* - SECTION( "in-place, forward" ) - { - mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::forward, true ); + /* + SECTION( "in-place, forward" ) + { + mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::forward, + true ); - mx::improc::eigenImage> in( 128, 128 ); - in.setRandom(); - mx::improc::eigenImage> incheck = in; - float sin = in.abs2().sum(); + mx::improc::eigenImage> in( 128, 128 ); + in.setRandom(); + mx::improc::eigenImage> incheck = in; + float sin = in.abs2().sum(); - fft( in, in ); + fft( in, in ); - float sout = in.abs2().sum() / (in.rows()*in.cols()); + float sout = in.abs2().sum() / (in.rows()*in.cols()); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = (in-incheck).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + //Make sure it isn't ident + REQUIRE(rmsdiff > 0); - // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); - } + // Test by Parsevals + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, (sin) * (1e-3) ) ); + } - SECTION( "in-place, backward" ) - { - mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::backward, true ); + SECTION( "in-place, backward" ) + { + mx::math::ft::fftT, std::complex, 2> fft( 128, 128, mx::math::ft::dir::backward, + true ); - mx::improc::eigenImage> in( 128, 128 ); - in.setRandom(); + mx::improc::eigenImage> in( 128, 128 ); + in.setRandom(); - mx::improc::eigenImage> incheck = in; + mx::improc::eigenImage> incheck = in; - float sin = in.abs2().sum(); + float sin = in.abs2().sum(); - fft( in, in ); + fft( in, in ); - float sout = in.abs2().sum()/(in.rows()*in.cols()); + float sout = in.abs2().sum()/(in.rows()*in.cols()); - float rmsdiff = (in-incheck).abs2().sum(); + float rmsdiff = (in-incheck).abs2().sum(); - //Make sure it isn't ident - REQUIRE(rmsdiff > 0); + //Make sure it isn't ident + REQUIRE(rmsdiff > 0); - // Test by Parsevals - REQUIRE( fabs( sin - sout )/sin < 1e-3 ); - } - */ + // Test by Parsevals + REQUIRE_THAT( sin, Catch::Matchers::WithinAbs( sout, (sin) * (1e-3) ) ); + } + */ } #ifdef HASQUAD diff --git a/tests/include/math/func/moffat_test.cpp b/tests/include/math/func/moffat_test.cpp index 9923dd903..29a8d4f77 100644 --- a/tests/include/math/func/moffat_test.cpp +++ b/tests/include/math/func/moffat_test.cpp @@ -20,7 +20,7 @@ SCENARIO( "compiling 1D Moffat function", "[math::func::moffat]" ) WHEN( "compiling" ) { double mv = mx::math::func::moffat( 0., 0., 1., 0., 1., 1. ); - REQUIRE( mv == 1.0 ); + REQUIRE_THAT( mv, Catch::Matchers::WithinAbs( 1.0, 1e-12 ) ); } } } @@ -37,7 +37,7 @@ SCENARIO( "compiling 2D Moffat function", "[math::func::moffat]" ) WHEN( "compiling" ) { double mv = mx::math::func::moffat2D( 0., 0., 0., 1., 0., 0., 1., 1. ); - REQUIRE( mv == 1.0 ); + REQUIRE_THAT( mv, Catch::Matchers::WithinAbs( 1.0, 1e-12 ) ); } } } @@ -54,7 +54,7 @@ SCENARIO( "compiling Moffat FWHM", "[math::func::moffat]" ) WHEN( "compiling" ) { double mv = mx::math::func::moffatFWHM( 1., 1. ); - REQUIRE( mv == 2.0 ); + REQUIRE_THAT( mv, Catch::Matchers::WithinAbs( 2.0, 1e-12 ) ); } } } diff --git a/tests/include/math/templateBLAS_test.cpp b/tests/include/math/templateBLAS_test.cpp index 0f5d0918b..bd0dcf7f4 100644 --- a/tests/include/math/templateBLAS_test.cpp +++ b/tests/include/math/templateBLAS_test.cpp @@ -14,8 +14,8 @@ SCENARIO( "testing scal", "[templateBLAS]" ) float x[] = { 1., 2. }; int incX = 1; mx::math::scal( N, alpha, x, incX ); - REQUIRE( x[0] == 2.0 ); - REQUIRE( x[1] == 4.0 ); + REQUIRE_THAT( x[0], Catch::Matchers::WithinAbs( 2.0, 1e-6 ) ); + REQUIRE_THAT( x[1], Catch::Matchers::WithinAbs( 4.0, 1e-6 ) ); } WHEN( "precision is double" ) @@ -25,8 +25,8 @@ SCENARIO( "testing scal", "[templateBLAS]" ) double x[] = { 1., 2. }; int incX = 1; mx::math::scal( N, alpha, x, incX ); - REQUIRE( x[0] == 2.0 ); - REQUIRE( x[1] == 4.0 ); + REQUIRE_THAT( x[0], Catch::Matchers::WithinAbs( 2.0, 1e-12 ) ); + REQUIRE_THAT( x[1], Catch::Matchers::WithinAbs( 4.0, 1e-12 ) ); } } } diff --git a/tests/include/sigproc/psdFilter_test.cpp b/tests/include/sigproc/psdFilter_test.cpp index 40da34b0a..be0a90a8e 100644 --- a/tests/include/sigproc/psdFilter_test.cpp +++ b/tests/include/sigproc/psdFilter_test.cpp @@ -262,7 +262,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / 10000 ); - REQUIRE( fabs( avgRms - 1.0 ) < 0.02 ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); } WHEN( "alpha=-1.5, df arbitrary, var = 2.2" ) { @@ -307,7 +307,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / 10000 ); - REQUIRE( fabs( avgRms - sqrt( 2.2 ) ) < 0.02 * sqrt( 2.2 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), 0.02 * sqrt( 2.2 ) ) ); } } GIVEN( "a rank 2 psd" ) @@ -366,7 +366,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / 10000 ); - REQUIRE( fabs( avgRms - 1.0 ) < 0.02 ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); } WHEN( "alpha=-1.5, dk arb, var=2.2" ) { @@ -422,7 +422,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / 10000 ); - REQUIRE( fabs( avgRms - sqrt( 2.2 ) ) < 0.02 * sqrt( 2.2 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), 0.02 * sqrt( 2.2 ) ) ); } } GIVEN( "a rank 3 psd" ) @@ -516,7 +516,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / 10000 ); - REQUIRE( fabs( avgRms - 1.0 ) < 0.02 ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); } WHEN( "k-alpha=-3.5, f-alph=-1.5, dk arb, df arb, var=2" ) { @@ -607,7 +607,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / 10000 ); - REQUIRE( fabs( avgRms - sqrt( 2.0 ) ) < 0.02 * sqrt( 2 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.0 ), 0.02 * sqrt( 2 ) ) ); } } } diff --git a/tests/include/sigproc/psdUtils_test.cpp b/tests/include/sigproc/psdUtils_test.cpp index 154faf53c..15d80ef2c 100644 --- a/tests/include/sigproc/psdUtils_test.cpp +++ b/tests/include/sigproc/psdUtils_test.cpp @@ -108,7 +108,8 @@ SCENARIO( "augmenting a 1 sided PSD", "[sigproc::psdUtils]" ) mx::sigproc::normPSD( psd, f, 1.0 ); // Now have a 1/f^2 PSD with total 1-sided variance of 1.0. - REQUIRE( fabs( mx::sigproc::psdVar( f, psd, 1.0 ) - 1.0 ) < 1e-10 ); // handles epsilon + REQUIRE_THAT( mx::sigproc::psdVar( f, psd, 1.0 ), + Catch::Matchers::WithinAbs( 1.0, 1e-10 ) ); // handles epsilon // proceed to augment: std::vector f2s, psd2s; @@ -128,17 +129,17 @@ SCENARIO( "augmenting a 1 sided PSD", "[sigproc::psdUtils]" ) REQUIRE( f2s[7] == -1 ); // Should now have 1.0 in bin 0, 0.5 in all other bins. - REQUIRE( psd2s[0] == psd[0] ); - REQUIRE( psd2s[1] == 0.5 * psd[1] ); - REQUIRE( psd2s[2] == 0.5 * psd[2] ); - REQUIRE( psd2s[3] == 0.5 * psd[3] ); - REQUIRE( psd2s[4] == psd[4] ); - REQUIRE( psd2s[5] == 0.5 * psd[3] ); - REQUIRE( psd2s[6] == 0.5 * psd[2] ); - REQUIRE( psd2s[7] == 0.5 * psd[1] ); + REQUIRE_THAT( psd2s[0], Catch::Matchers::WithinAbs( psd[0], 1e-12 ) ); + REQUIRE_THAT( psd2s[1], Catch::Matchers::WithinAbs( 0.5 * psd[1], 1e-12 ) ); + REQUIRE_THAT( psd2s[2], Catch::Matchers::WithinAbs( 0.5 * psd[2], 1e-12 ) ); + REQUIRE_THAT( psd2s[3], Catch::Matchers::WithinAbs( 0.5 * psd[3], 1e-12 ) ); + REQUIRE_THAT( psd2s[4], Catch::Matchers::WithinAbs( psd[4], 1e-12 ) ); + REQUIRE_THAT( psd2s[5], Catch::Matchers::WithinAbs( 0.5 * psd[3], 1e-12 ) ); + REQUIRE_THAT( psd2s[6], Catch::Matchers::WithinAbs( 0.5 * psd[2], 1e-12 ) ); + REQUIRE_THAT( psd2s[7], Catch::Matchers::WithinAbs( 0.5 * psd[1], 1e-12 ) ); // handle machine precision - REQUIRE( fabs( mx::sigproc::psdVar( f2s, psd2s, 1.0 ) - 1.0 ) < 1e-10 ); + REQUIRE_THAT( mx::sigproc::psdVar( f2s, psd2s, 1.0 ), Catch::Matchers::WithinAbs( 1.0, 1e-10 ) ); } } } @@ -157,16 +158,16 @@ SCENARIO( "creating a 1D frequency grid", "[sigproc::psdUtils]" ) REQUIRE( mx::sigproc::frequencyGrid( f, 1.0 ) == 0 ); - REQUIRE( fabs( f[0] - 0 ) < 1e-10 ); - REQUIRE( fabs( f[1] - 0.1 ) < 1e-10 ); - REQUIRE( fabs( f[2] - 0.2 ) < 1e-10 ); - REQUIRE( fabs( f[3] - 0.3 ) < 1e-10 ); - REQUIRE( fabs( f[4] - 0.4 ) < 1e-10 ); - REQUIRE( fabs( f[5] - 0.5 ) < 1e-10 ); - REQUIRE( fabs( f[6] - -0.4 ) < 1e-10 ); - REQUIRE( fabs( f[7] - -0.3 ) < 1e-10 ); - REQUIRE( fabs( f[8] - -0.2 ) < 1e-10 ); - REQUIRE( fabs( f[9] - -0.1 ) < 1e-10 ); + REQUIRE_THAT( f[0], Catch::Matchers::WithinAbs( 0, 1e-10 ) ); + REQUIRE_THAT( f[1], Catch::Matchers::WithinAbs( 0.1, 1e-10 ) ); + REQUIRE_THAT( f[2], Catch::Matchers::WithinAbs( 0.2, 1e-10 ) ); + REQUIRE_THAT( f[3], Catch::Matchers::WithinAbs( 0.3, 1e-10 ) ); + REQUIRE_THAT( f[4], Catch::Matchers::WithinAbs( 0.4, 1e-10 ) ); + REQUIRE_THAT( f[5], Catch::Matchers::WithinAbs( 0.5, 1e-10 ) ); + REQUIRE_THAT( f[6], Catch::Matchers::WithinAbs( -0.4, 1e-10 ) ); + REQUIRE_THAT( f[7], Catch::Matchers::WithinAbs( -0.3, 1e-10 ) ); + REQUIRE_THAT( f[8], Catch::Matchers::WithinAbs( -0.2, 1e-10 ) ); + REQUIRE_THAT( f[9], Catch::Matchers::WithinAbs( -0.1, 1e-10 ) ); } WHEN( "dt = 2" ) @@ -175,16 +176,16 @@ SCENARIO( "creating a 1D frequency grid", "[sigproc::psdUtils]" ) REQUIRE( mx::sigproc::frequencyGrid( f, 2.5 ) == 0 ); - REQUIRE( fabs( f[0] - 0 ) < 1e-10 ); - REQUIRE( fabs( f[1] - 0.04 ) < 1e-10 ); - REQUIRE( fabs( f[2] - 0.08 ) < 1e-10 ); - REQUIRE( fabs( f[3] - 0.12 ) < 1e-10 ); - REQUIRE( fabs( f[4] - 0.16 ) < 1e-10 ); - REQUIRE( fabs( f[5] - 0.2 ) < 1e-10 ); - REQUIRE( fabs( f[6] - -0.16 ) < 1e-10 ); - REQUIRE( fabs( f[7] - -0.12 ) < 1e-10 ); - REQUIRE( fabs( f[8] - -0.08 ) < 1e-10 ); - REQUIRE( fabs( f[9] - -0.04 ) < 1e-10 ); + REQUIRE_THAT( f[0], Catch::Matchers::WithinAbs( 0, 1e-10 ) ); + REQUIRE_THAT( f[1], Catch::Matchers::WithinAbs( 0.04, 1e-10 ) ); + REQUIRE_THAT( f[2], Catch::Matchers::WithinAbs( 0.08, 1e-10 ) ); + REQUIRE_THAT( f[3], Catch::Matchers::WithinAbs( 0.12, 1e-10 ) ); + REQUIRE_THAT( f[4], Catch::Matchers::WithinAbs( 0.16, 1e-10 ) ); + REQUIRE_THAT( f[5], Catch::Matchers::WithinAbs( 0.2, 1e-10 ) ); + REQUIRE_THAT( f[6], Catch::Matchers::WithinAbs( -0.16, 1e-10 ) ); + REQUIRE_THAT( f[7], Catch::Matchers::WithinAbs( -0.12, 1e-10 ) ); + REQUIRE_THAT( f[8], Catch::Matchers::WithinAbs( -0.08, 1e-10 ) ); + REQUIRE_THAT( f[9], Catch::Matchers::WithinAbs( -0.04, 1e-10 ) ); } } @@ -196,11 +197,11 @@ SCENARIO( "creating a 1D frequency grid", "[sigproc::psdUtils]" ) REQUIRE( mx::sigproc::frequencyGrid( f, 1.0, false ) == 0 ); - REQUIRE( fabs( f[0] - 0.1 ) < 1e-10 ); - REQUIRE( fabs( f[1] - 0.2 ) < 1e-10 ); - REQUIRE( fabs( f[2] - 0.3 ) < 1e-10 ); - REQUIRE( fabs( f[3] - 0.4 ) < 1e-10 ); - REQUIRE( fabs( f[4] - 0.5 ) < 1e-10 ); + REQUIRE_THAT( f[0], Catch::Matchers::WithinAbs( 0.1, 1e-10 ) ); + REQUIRE_THAT( f[1], Catch::Matchers::WithinAbs( 0.2, 1e-10 ) ); + REQUIRE_THAT( f[2], Catch::Matchers::WithinAbs( 0.3, 1e-10 ) ); + REQUIRE_THAT( f[3], Catch::Matchers::WithinAbs( 0.4, 1e-10 ) ); + REQUIRE_THAT( f[4], Catch::Matchers::WithinAbs( 0.5, 1e-10 ) ); } WHEN( "dt = 1, even size" ) @@ -209,12 +210,12 @@ SCENARIO( "creating a 1D frequency grid", "[sigproc::psdUtils]" ) REQUIRE( mx::sigproc::frequencyGrid( f, 1.0, false ) == 0 ); - REQUIRE( fabs( f[0] - 0.0 ) < 1e-10 ); - REQUIRE( fabs( f[1] - 0.1 ) < 1e-10 ); - REQUIRE( fabs( f[2] - 0.2 ) < 1e-10 ); - REQUIRE( fabs( f[3] - 0.3 ) < 1e-10 ); - REQUIRE( fabs( f[4] - 0.4 ) < 1e-10 ); - REQUIRE( fabs( f[5] - 0.5 ) < 1e-10 ); + REQUIRE_THAT( f[0], Catch::Matchers::WithinAbs( 0.0, 1e-10 ) ); + REQUIRE_THAT( f[1], Catch::Matchers::WithinAbs( 0.1, 1e-10 ) ); + REQUIRE_THAT( f[2], Catch::Matchers::WithinAbs( 0.2, 1e-10 ) ); + REQUIRE_THAT( f[3], Catch::Matchers::WithinAbs( 0.3, 1e-10 ) ); + REQUIRE_THAT( f[4], Catch::Matchers::WithinAbs( 0.4, 1e-10 ) ); + REQUIRE_THAT( f[5], Catch::Matchers::WithinAbs( 0.5, 1e-10 ) ); } } } diff --git a/tests/include/sigproc/signalWindows_test.cpp b/tests/include/sigproc/signalWindows_test.cpp index 118b4bac7..45c8faadd 100644 --- a/tests/include/sigproc/signalWindows_test.cpp +++ b/tests/include/sigproc/signalWindows_test.cpp @@ -9,7 +9,6 @@ #include "../../../include/sigproc/signalWindows.hpp" #include "../../../include/improc/eigenImage.hpp" -#include "../../../include/improc/milkImage.hpp" /** Scenario: creating 2D Rectangular Tukey Windows * @@ -57,8 +56,8 @@ SCENARIO( "creating 2D Rectangular Tukey Windows", "[sigproc::signalWindows::tuk std::vector win1( 256 ); mx::sigproc::window::tukey( win1, 1.0 ); - REQUIRE( win( 0, 0 ) == win1[0] * win1[0] ); - REQUIRE( win( 10, 15 ) == win1[10] * win1[15] ); + REQUIRE_THAT( win( 0, 0 ), Catch::Matchers::WithinAbs( win1[0] * win1[0], 1e-6 ) ); + REQUIRE_THAT( win( 10, 15 ), Catch::Matchers::WithinAbs( win1[10] * win1[15], 1e-6 ) ); } WHEN( "256x256, alpha=0.5" ) { @@ -78,11 +77,8 @@ SCENARIO( "creating 2D Rectangular Tukey Windows", "[sigproc::signalWindows::tuk std::vector win1( 256 ); mx::sigproc::window::tukey( win1, 0.5 ); - mx::improc::milkImage mwin; - mwin.create( "win", win ); - - REQUIRE( win( 0, 0 ) == win1[0] * win1[0] ); - REQUIRE( win( 10, 15 ) == win1[10] * win1[15] ); + REQUIRE_THAT( win( 0, 0 ), Catch::Matchers::WithinAbs( win1[0] * win1[0], 1e-6 ) ); + REQUIRE_THAT( win( 10, 15 ), Catch::Matchers::WithinAbs( win1[10] * win1[15], 1e-6 ) ); } } } diff --git a/tests/include/sys/timeUtils_test.cpp b/tests/include/sys/timeUtils_test.cpp index b806b343a..3b9cedee8 100644 --- a/tests/include/sys/timeUtils_test.cpp +++ b/tests/include/sys/timeUtils_test.cpp @@ -218,7 +218,7 @@ SCENARIO( "Verify parsing of a formatted time string", "[timeutils]" ) REQUIRE( hr == 1 ); REQUIRE( mn == 2 ); - REQUIRE( fabs( sec - 3.23 ) < 1e-7 ); + REQUIRE_THAT( sec, Catch::Matchers::WithinAbs( 3.23, 1e-7 ) ); } WHEN( "negative hour" ) @@ -231,7 +231,7 @@ SCENARIO( "Verify parsing of a formatted time string", "[timeutils]" ) REQUIRE( hr == -1 ); REQUIRE( mn == -2 ); - REQUIRE( fabs( sec - -3.23 ) < 1e-7 ); + REQUIRE_THAT( sec, Catch::Matchers::WithinAbs( -3.23, 1e-7 ) ); } WHEN( "0 pads" ) @@ -244,7 +244,7 @@ SCENARIO( "Verify parsing of a formatted time string", "[timeutils]" ) REQUIRE( hr == 1 ); REQUIRE( mn == 2 ); - REQUIRE( fabs( sec - 3.23 ) < 1e-7 ); + REQUIRE_THAT( sec, Catch::Matchers::WithinAbs( 3.23, 1e-7 ) ); } } } @@ -269,7 +269,7 @@ SCENARIO( "Verify calculation of MJD", "[timeutils]" ) WHEN( "floating seconds" ) { double mjd = mx::sys::Cal2mjd( 2020, 12, 31, 0, 0, 10.2357 ); - REQUIRE( fabs( mjd - 59214.00011846875 ) < 1e-14 ); + REQUIRE_THAT( mjd, Catch::Matchers::WithinAbs( 59214.00011846875, 1e-14 ) ); } } } @@ -311,7 +311,7 @@ SCENARIO( "Verify parsing of an ISO 8601 time string", "[timeutils]" ) REQUIRE( day == 31 ); REQUIRE( hr == 0 ); REQUIRE( min == 0 ); - REQUIRE( fabs( sec - 10.2357 ) < 1e-14 ); + REQUIRE_THAT( sec, Catch::Matchers::WithinAbs( 10.2357, 1e-14 ) ); } } @@ -348,7 +348,7 @@ SCENARIO( "Verify conversion of an ISO 8601 time string to MJD", "[timeutils]" ) { double mjd = mx::sys::ISO8601date2mjd( "2020-12-31T00:00:10.2357" ); - REQUIRE( fabs( mjd - 59214.00011846875 ) < 1e-14 ); + REQUIRE_THAT( mjd, Catch::Matchers::WithinAbs( 59214.00011846875, 1e-14 ) ); } } } diff --git a/tests/include/wfp/fraunhoferPropagator_test.cpp b/tests/include/wfp/fraunhoferPropagator_test.cpp index 008fb5ba6..f0a6d6cd1 100644 --- a/tests/include/wfp/fraunhoferPropagator_test.cpp +++ b/tests/include/wfp/fraunhoferPropagator_test.cpp @@ -6,7 +6,7 @@ #include -//#define DEBUG +// #define DEBUG #include "../../../include/wfp/fraunhoferPropagator.hpp" #include "../../../include/improc/eigenImage.hpp" @@ -23,7 +23,6 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on CPU", "[wfp]" ) typedef std::complex complexT; typedef mx::improc::eigenImage> complexFieldT; - int wfSz = 512; int pupSz = 128; mx::improc::eigenImage pupil; @@ -37,7 +36,7 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on CPU", "[wfp]" ) complexPupil.resize( wfSz, wfSz ); complexFocal.resize( wfSz, wfSz ); realFocal.resize( wfSz, wfSz ); - realPupil.resize(wfSz, wfSz); + realPupil.resize( wfSz, wfSz ); mx::wfp::fraunhoferPropagator fi; mx::wfp::makeComplexPupil( complexPupil, pupil, wfSz ); @@ -49,14 +48,14 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on CPU", "[wfp]" ) realT expwr = pupil.square().sum(); realT pwr = realFocal.sum(); - REQUIRE( fabs(expwr - pwr)/pwr < 1e-4 ); + REQUIRE_THAT( expwr, Catch::Matchers::WithinAbs( pwr, ( pwr ) * ( 1e-4 ) ) ); complexPupil.setZero(); - fi.propagateFocalToPupil( complexPupil, complexFocal); + fi.propagateFocalToPupil( complexPupil, complexFocal ); mx::wfp::extractIntensityImage( realPupil, 0, complexPupil.rows(), 0, complexPupil.cols(), complexPupil, 0, 0 ); - REQUIRE(realPupil.sum() == pupil.sum()); + REQUIRE_THAT( realPupil.sum(), Catch::Matchers::WithinAbs( pupil.sum(), pupil.sum() * 1e-4 ) ); } /// Make an Airy pattern and go back to pupil on GPU @@ -69,7 +68,6 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on GPU", "[wfp]" ) typedef std::complex complexT; typedef mx::improc::eigenImage> complexFieldT; - int wfSz = 512; int pupSz = 128; mx::improc::eigenImage pupil; @@ -83,15 +81,15 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on GPU", "[wfp]" ) complexPupil.resize( wfSz, wfSz ); complexFocal.resize( wfSz, wfSz ); realFocal.resize( wfSz, wfSz ); - realPupil.resize(wfSz, wfSz); + realPupil.resize( wfSz, wfSz ); mx::wfp::fraunhoferPropagator fi; mx::wfp::makeComplexPupil( complexPupil, pupil, wfSz ); - mx::cuda::cudaPtr dev_complexPupil,dev_complexFocal; + mx::cuda::cudaPtr dev_complexPupil, dev_complexFocal; - dev_complexPupil.upload(complexPupil.data(), complexPupil.rows(), complexPupil.cols()); - dev_complexFocal.resize(complexPupil.rows(), complexPupil.cols()); + dev_complexPupil.upload( complexPupil.data(), complexPupil.rows(), complexPupil.cols() ); + dev_complexFocal.resize( complexPupil.rows(), complexPupil.cols() ); BREAD_CRUMB; @@ -99,7 +97,7 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on GPU", "[wfp]" ) BREAD_CRUMB; - dev_complexFocal.download(complexFocal.data()); + dev_complexFocal.download( complexFocal.data() ); BREAD_CRUMB; @@ -110,18 +108,18 @@ TEST_CASE( "Make an Airy pattern and go back to pupil on GPU", "[wfp]" ) realT expwr = pupil.square().sum(); realT pwr = realFocal.sum(); - REQUIRE( fabs(expwr - pwr)/pwr < 1e-4 ); + REQUIRE_THAT( expwr, Catch::Matchers::WithinAbs( pwr, ( pwr ) * ( 1e-4 ) ) ); BREAD_CRUMB; - fi.propagateFocalToPupil( dev_complexPupil, dev_complexFocal); + fi.propagateFocalToPupil( dev_complexPupil, dev_complexFocal ); BREAD_CRUMB; - complexPupil.setZero(); //make sure - dev_complexPupil.download(complexPupil.data()); + complexPupil.setZero(); // make sure + dev_complexPupil.download( complexPupil.data() ); mx::wfp::extractIntensityImage( realPupil, 0, complexPupil.rows(), 0, complexPupil.cols(), complexPupil, 0, 0 ); - REQUIRE(realPupil.sum() == pupil.sum()); + REQUIRE_THAT( realPupil.sum(), Catch::Matchers::WithinAbs( pupil.sum(), pupil.sum() * 1e-4 ) ); } From 8c39656d2c2a2d2134ebba1f86f2e6e6d203ccf7 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 19 Feb 2026 09:30:45 -0700 Subject: [PATCH 14/21] Add agent context guidance file --- agent_context.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 agent_context.md diff --git a/agent_context.md b/agent_context.md new file mode 100644 index 000000000..e249807fc --- /dev/null +++ b/agent_context.md @@ -0,0 +1,65 @@ +Follow these code style and documentation rules exactly. + +1) File-Level Documentation +- Each header/source should have a top Doxygen file block: + - \file + - \brief + - \author (if project uses it) + +2) Include Guards and Includes +- Match existing project include-guard naming convention. +- Keep include ordering consistent with project style. +- Do not introduce new include style unless project already uses it. + +3) Function Declaration Documentation +- Every function declaration must have a brief `///` summary. +- Add a short `/** ... */` details block only when needed. +- Document every parameter inline at declaration site using: + - `type name /**< [in] description */` +- Apply to normal methods, constructors, slots, and signals. +- Keep return-value docs where project uses them. + +4) Member Variable Documentation +- Document non-trivial class members with `///`. +- Describe role/ownership/state, not just type. + +5) Naming and Structure +- Use project member naming convention (e.g. `m_` prefix). +- Keep declaration ordering/grouping stable: + - public/protected/private + - slots/signals grouped consistently. +- Leave a blank line between declarations for readability. + +6) Header vs Source Placement +- Keep non-trivial definitions out of headers. +- Move implementations to `.cpp` unless intentionally inline. + +7) Editing Discipline +- Preserve existing behavior unless explicitly requested. +- When renaming members/APIs, update all dependent call sites. +- Keep changes minimal and scoped. + +8) Formatting and Verification +- Run `clang-format` on touched files. +- Ensure docs and naming are consistent after formatting. +- Report any places where project style is ambiguous before making assumptions. + +9) Doxygen Named Section Ordering +- For classes that expose configuration via member data + accessors, keep named sections split into: + - `... - Data` for protected/private member state + - `...` (without `- Data`) for public access functions +- Place the `... - Data` section before the corresponding public accessor section. + +10) Header Declaration Parameter Docs +- In headers, prefer inline parameter documentation on declarations (`type name /**< ... */`) rather than separate `\param` lists, unless there is a specific reason to deviate. + +11) PR Prompt Attribution +- At the top of PR descriptions, include an explicit attribution line when work was performed with Codex. +- Preferred format: + - `This work was performed by GPT-5.3-Codex in response to the prompt: "...".` +- Include the primary user prompt verbatim (or a faithful condensed version if it is extremely long). + +When you finish: +- Summarize what changed. +- List affected files. +- Note any follow-up items or potential edge cases. From b54ed5d55e8d88fdf2c25f690e98757d96892210 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 19 Feb 2026 12:32:28 -0700 Subject: [PATCH 15/21] Split CMake tests into per-source executables and add coverage targets --- CMakeLists.txt | 46 ++- README.md | 49 +++- tests/CMakeLists.txt | 46 ++- tests/coverage/gcov.css | 497 +++++++++++++++++++++++++++++++++ tests/coverage/make_coverage | 6 + tests/coverage/update_coverage | 5 + 6 files changed, 631 insertions(+), 18 deletions(-) create mode 100644 tests/coverage/gcov.css create mode 100755 tests/coverage/make_coverage create mode 100755 tests/coverage/update_coverage diff --git a/CMakeLists.txt b/CMakeLists.txt index db3792476..223033bb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,8 @@ option(MXLIB_USE_OPENMP "Whether or not to use OpenMP in mxlib and other librari option(MXLIB_USE_CUDA "Whether or not to use CUDA library for mxlib" ON) option(MXLIB_USE_ISIO "Whether or not to use the ImageStreamIO library for mxlib" ON) option(MXLIB_BUILD_TESTS "Whether or not to build mxlib tests" OFF) +option(MXLIB_BUILD_COVERAGE "Whether or not to enable coverage instrumentation for C/C++ builds" OFF) +set(MXLIB_COVERAGE_TEST_TIMEOUT 300 CACHE STRING "CTest timeout (seconds) for coverage target") set(MXLIB_USE_FFT_FROM "fftw" CACHE STRING "Which library to use for the FFT interface in mxlib") @@ -361,6 +363,18 @@ set(CMAKE_CXX_FLAGS "${MXLIB_CXXVERSION} ${MXLIB_OPTIMIZE} ${MXLIB_CXXFLAGS}") set(MXLIB_PC_CFLAGS "${MXLIB_PC_CFLAGS} ${CMAKE_CXX_FLAGS} ${MXLIB_DEFINES}") +if(MXLIB_BUILD_COVERAGE) + # Match the legacy Make coverage behavior: --coverage and -O0 for instrumented builds. + add_compile_definitions(MXLIB_BUILD_COVERAGE=1) + add_compile_options( + $<$:--coverage> + $<$:-O0> + $<$:--coverage> + $<$:-O0> + ) + add_link_options(--coverage) +endif() + ############################################ # OpenMP setup ############################################ @@ -798,7 +812,37 @@ else() endif() add_custom_target(tests - DEPENDS mxlibTest + DEPENDS mxlibTests +) + +set(MXLIB_COVERAGE_BUILD_DIR ${CMAKE_BINARY_DIR}/coverage-build) +set(MXLIB_COVERAGE_INFO ${CMAKE_BINARY_DIR}/coverage.info) +set(MXLIB_COVERAGE_FILTERED_INFO ${CMAKE_BINARY_DIR}/coverage_filtered.info) +set(MXLIB_COVERAGE_REPORT_DIR ${CMAKE_BINARY_DIR}/coverage_report) + +find_program(MXLIB_LCOV_EXECUTABLE lcov) +find_program(MXLIB_GENHTML_EXECUTABLE genhtml) + +if(MXLIB_LCOV_EXECUTABLE AND MXLIB_GENHTML_EXECUTABLE) + add_custom_target(coverage + COMMAND ${CMAKE_COMMAND} -S ${CMAKE_SOURCE_DIR} -B ${MXLIB_COVERAGE_BUILD_DIR} -DMXLIB_BUILD_TESTS=ON -DMXLIB_BUILD_COVERAGE=ON -DMXLIB_USE_CUDA=OFF -DMXLIB_USE_OPENMP=${MXLIB_USE_OPENMP} -DMXLIB_USE_ISIO=${MXLIB_USE_ISIO} -DMXLIB_USE_FFT_FROM=${MXLIB_USE_FFT_FROM} -DMXLIB_USE_BLAS_FROM=${MXLIB_USE_BLAS_FROM} + COMMAND ${CMAKE_COMMAND} --build ${MXLIB_COVERAGE_BUILD_DIR} --target mxlibTests -j + COMMAND ${MXLIB_LCOV_EXECUTABLE} --directory ${MXLIB_COVERAGE_BUILD_DIR} --zerocounters + COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${MXLIB_COVERAGE_BUILD_DIR} --output-on-failure --timeout ${MXLIB_COVERAGE_TEST_TIMEOUT} + COMMAND ${MXLIB_LCOV_EXECUTABLE} --directory ${MXLIB_COVERAGE_BUILD_DIR} --capture --output-file ${MXLIB_COVERAGE_INFO} + COMMAND ${MXLIB_LCOV_EXECUTABLE} --remove ${MXLIB_COVERAGE_INFO} ${CMAKE_SOURCE_DIR}/tests/* /usr/* /sys/* /tty/* --ignore-errors unused,unused --ignore-errors inconsistent,inconsistent --output-file ${MXLIB_COVERAGE_FILTERED_INFO} + COMMAND ${MXLIB_GENHTML_EXECUTABLE} ${MXLIB_COVERAGE_FILTERED_INFO} --output-directory ${MXLIB_COVERAGE_REPORT_DIR} --title mxlib --hierarchical --merge-aliases --suppress-aliases --filter function --demangle-cpp --css-file ${CMAKE_SOURCE_DIR}/tests/coverage/gcov.css + USES_TERMINAL + ) +else() + message(WARNING "lcov/genhtml not found. The coverage target will not be available.") +endif() + +add_custom_target(coverage_clean + COMMAND ${CMAKE_COMMAND} -E rm -f ${MXLIB_COVERAGE_INFO} + COMMAND ${CMAKE_COMMAND} -E rm -f ${MXLIB_COVERAGE_FILTERED_INFO} + COMMAND ${CMAKE_COMMAND} -E rm -rf ${MXLIB_COVERAGE_REPORT_DIR} + COMMAND ${CMAKE_COMMAND} -E rm -rf ${MXLIB_COVERAGE_BUILD_DIR} ) #dump_cmake_variables(".") diff --git a/README.md b/README.md index 68d92721f..9b81e71d8 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON ``` With `MXLIB_BUILD_TESTS=ON`, test executables are part of the default build. -With `MXLIB_BUILD_TESTS=OFF`, they are skipped by default and built only via `tests`/`mxlibTest` targets. +With `MXLIB_BUILD_TESTS=OFF`, they are skipped by default and built only via `tests`/`mxlibTests` targets. -Build the aggregate test executable: +Build all test executables: ```bash -cmake --build _build --target mxlibTest -j +cmake --build _build --target mxlibTests -j ``` Run tests: @@ -38,7 +38,7 @@ Run tests: ctest --test-dir _build --output-on-failure ``` -Run the aggregate test executable directly: +Run the CTest test suite directly: ```bash cmake --build _build --target mxlibTestRun @@ -51,3 +51,44 @@ cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON -DMXLIB_ONE_TEST=include/math/geo_te cmake --build _build --target mxlibTestOne -j cmake --build _build --target mxlibTestOneRun ``` + +## Coverage + +Coverage generation is integrated into CMake and modeled after the MagAOX flow. + +Prerequisites: + +```bash +lcov --version +genhtml --version +``` + +Generate an HTML report: + +```bash +cmake -S . -B _build +cmake --build _build --target coverage +``` + +Optional: tune coverage test timeout (default `300` seconds): + +```bash +cmake -S . -B _build -DMXLIB_COVERAGE_TEST_TIMEOUT=600 +``` + +Coverage artifacts are written under `_build/`: + +- `_build/coverage.info` +- `_build/coverage_filtered.info` +- `_build/coverage_report/index.html` + +Clean coverage artifacts: + +```bash +cmake --build _build --target coverage_clean +``` + +Convenience scripts are also available: + +- `tests/coverage/make_coverage` +- `tests/coverage/update_coverage` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 63d51c6f5..1c3238a4c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -31,20 +31,40 @@ if(MXLIB_USE_CUDA) list(APPEND MXLIB_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/math/cuda/templateCublas_test.cpp) endif() -add_executable(mxlibTest ${MXLIB_TEST_MAIN_SOURCE} ${MXLIB_TEST_SOURCES}) -target_link_libraries(mxlibTest PRIVATE mxlib-shared) -if(NOT MXLIB_BUILD_TESTS) - set_target_properties(mxlibTest PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) -endif() +set(MXLIB_TEST_TARGETS) -add_custom_target(mxlibTestRun - COMMAND $ - DEPENDS mxlibTest - USES_TERMINAL -) +function(mxlib_add_test_executable TEST_SOURCE) + file(RELATIVE_PATH _mxlib_test_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${TEST_SOURCE}") + string(REPLACE "/" "_" _mxlib_test_name "${_mxlib_test_rel}") + string(REPLACE "." "_" _mxlib_test_name "${_mxlib_test_name}") + set(_mxlib_test_target "mxlibTest_${_mxlib_test_name}") -if(BUILD_TESTING) - add_test(NAME mxlibTest COMMAND mxlibTest) + add_executable(${_mxlib_test_target} ${MXLIB_TEST_MAIN_SOURCE} ${TEST_SOURCE}) + target_link_libraries(${_mxlib_test_target} PRIVATE mxlib-shared) + + if(NOT MXLIB_BUILD_TESTS) + set_target_properties(${_mxlib_test_target} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) + endif() + + if(MXLIB_BUILD_TESTS AND BUILD_TESTING) + add_test(NAME ${_mxlib_test_target} COMMAND ${_mxlib_test_target}) + endif() + + set(MXLIB_TEST_TARGETS ${MXLIB_TEST_TARGETS} ${_mxlib_test_target} PARENT_SCOPE) +endfunction() + +foreach(_mxlib_test_source IN LISTS MXLIB_TEST_SOURCES) + mxlib_add_test_executable("${_mxlib_test_source}") +endforeach() + +add_custom_target(mxlibTests DEPENDS ${MXLIB_TEST_TARGETS}) + +if(MXLIB_BUILD_TESTS AND BUILD_TESTING) + add_custom_target(mxlibTestRun + COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${CMAKE_BINARY_DIR} --output-on-failure + DEPENDS mxlibTests + USES_TERMINAL + ) endif() set(MXLIB_ONE_TEST @@ -84,7 +104,7 @@ if(MXLIB_ONE_TEST) USES_TERMINAL ) - if(BUILD_TESTING) + if(MXLIB_BUILD_TESTS AND BUILD_TESTING) add_test(NAME mxlibTestOne COMMAND mxlibTestOne) endif() endif() diff --git a/tests/coverage/gcov.css b/tests/coverage/gcov.css new file mode 100644 index 000000000..a3db13df9 --- /dev/null +++ b/tests/coverage/gcov.css @@ -0,0 +1,497 @@ +@import url("https://fonts.googleapis.com/css?family=Source Code Pro|Quicksand"); + +:root { + --textColor: aliceblue; + --mainBg: #131313; + --linkColor: aquamarine; + + --lighter: #ffffff05; +} + +a, +a:visited { + color: var(--linkColor); + text-decoration: none; +} + +body center table tbody tr:nth-child(even):not(:nth-child(2)) { + background-color: var(--lighter); +} + +body { + font-family: "Quicksand", "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", + sans-serif; + letter-spacing: 0.03em; + background-color: var(--mainBg); + color: var(--textColor); +} + +center table { + background-color: var(--lighter); + border-radius: 1em; + padding: 1em; + padding-top: 0; +} + +td, +th { + padding: 0.5em; +} + +td.coverBar > table { + background: none; + border-radius: 0; + padding: 0; +} + +/*code-related style is below this line*/ + +td.lineCov, +span.lineCov { + background-color: rgb(0, 30, 30); +} +td.lineNoCov, +span.lineNoCov { + background-color: rgb(50, 0, 0); +} + +.source > a { + color: var(--textColor); + font-family: "Source Code Pro", "Courier New", Courier, monospace; +} + +.source { + font-size: 1.1em; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +td.tlaUNC +{ + text-align: right; + background-color: #6d2914; +} +td.tlaBgUNC { + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): +Newly added code is not tested" */ +span.tlaUNC +{ + text-align: left; + background-color: #6d2914; +} +span.tlaBgUNC { + background-color: #6d2914; +} +a.tlaBgUNC { + background-color: #6d2914; + color: #000000; +} + +td.headerCovTableHeadUNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +td.tlaLBC +{ + text-align: right; + background-color: #6d2914; +} +td.tlaBgLBC { + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): +Unchanged code is no longer tested" */ +span.tlaLBC +{ + text-align: left; + background-color: #6d2914; +} +span.tlaBgLBC { + background-color: #6d2914; +} +a.tlaBgLBC { + background-color: #6d2914; + color: #000000; +} + +td.headerCovTableHeadLBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +td.tlaUIC +{ + text-align: right; + background-color: #6d2914; +} +td.tlaBgUIC { + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): +Previously unused code is untested" */ +span.tlaUIC +{ + text-align: left; + background-color: #6d2914; +} +span.tlaBgUIC { + background-color: #6d2914; +} +a.tlaBgUIC { + background-color: #6d2914; + color: #000000; +} + +td.headerCovTableHeadUIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +td.tlaUBC +{ + text-align: right; + background-color: #6d2914; +} +td.tlaBgUBC { + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): +Unchanged code was untested before, is untested now" */ +span.tlaUBC +{ + text-align: left; + background-color: #6d2914; +} +span.tlaBgUBC { + background-color: #6d2914; +} +a.tlaBgUBC { + background-color: #6d2914; + color: #000000; +} + +td.headerCovTableHeadUBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #6d2914; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +td.tlaGBC +{ + text-align: right; + background-color: #414963; +} +td.tlaBgGBC { + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): +Unchanged code is tested now" */ +span.tlaGBC +{ + text-align: left; + background-color: #414963; +} +span.tlaBgGBC { + background-color: #414963; +} +a.tlaBgGBC { + background-color: #414963; + color: #000000; +} + +td.headerCovTableHeadGBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +td.tlaGIC +{ + text-align: right; + background-color: #414963; +} +td.tlaBgGIC { + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): +Previously unused code is tested now" */ +span.tlaGIC +{ + text-align: left; + background-color: #414963; +} +span.tlaBgGIC { + background-color: #414963; +} +a.tlaBgGIC { + background-color: #414963; + color: #000000; +} + +td.headerCovTableHeadGIC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +td.tlaGNC +{ + text-align: right; + background-color: #414963; +} +td.tlaBgGNC { + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): +Newly added code is tested" */ +span.tlaGNC +{ + text-align: left; + background-color: #414963; +} +span.tlaBgGNC { + background-color: #414963; +} +a.tlaBgGNC { + background-color: #414963; + color: #000000; +} + +td.headerCovTableHeadGNC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +td.tlaCBC +{ + text-align: right; + background-color: #414963; +} +td.tlaBgCBC { + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): +Unchanged code was tested before and is still tested" */ +span.tlaCBC +{ + text-align: left; + background-color: #414963; +} +span.tlaBgCBC { + background-color: #414963; +} +a.tlaBgCBC { + background-color: #414963; + color: #000000; +} + +td.headerCovTableHeadCBC { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #414963; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +td.tlaEUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgEUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): +Previously untested code is unused now" */ +span.tlaEUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgEUB { + background-color: #FFFFFF; +} +a.tlaBgEUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadEUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +td.tlaECB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgECB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): +Previously tested code is unused now" */ +span.tlaECB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgECB { + background-color: #FFFFFF; +} +a.tlaBgECB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadECB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +td.tlaDUB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDUB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): +Previously untested code has been deleted" */ +span.tlaDUB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDUB { + background-color: #FFFFFF; +} +a.tlaBgDUB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDUB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +td.tlaDCB +{ + text-align: right; + background-color: #FFFFFF; +} +td.tlaBgDCB { + background-color: #FFFFFF; +} + +/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): +Previously tested code has been deleted" */ +span.tlaDCB +{ + text-align: left; + background-color: #FFFFFF; +} +span.tlaBgDCB { + background-color: #FFFFFF; +} +a.tlaBgDCB { + background-color: #FFFFFF; + color: #000000; +} + +td.headerCovTableHeadDCB { + text-align: center; + padding-right: 6px; + padding-left: 6px; + padding-bottom: 0px; + font-family: sans-serif; + white-space: nowrap; + background-color: #FFFFFF; +} diff --git a/tests/coverage/make_coverage b/tests/coverage/make_coverage new file mode 100755 index 000000000..73f23b824 --- /dev/null +++ b/tests/coverage/make_coverage @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Configure default build tree, then run the CMake coverage workflow. +cmake -S . -B _build +cmake --build _build --target coverage diff --git a/tests/coverage/update_coverage b/tests/coverage/update_coverage new file mode 100755 index 000000000..35c9ddfd2 --- /dev/null +++ b/tests/coverage/update_coverage @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Re-run coverage workflow in the configured build tree. +cmake --build _build --target coverage From c01a99ff2fe46a4be2570037a91994a36ce088e2 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 19 Feb 2026 12:32:28 -0700 Subject: [PATCH 16/21] Stabilize long and coverage-sensitive tests --- tests/include/improc/imageTransforms_test.cpp | 48 ++++++++----------- tests/include/sigproc/psdFilter_test.cpp | 44 ++++++++++------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/tests/include/improc/imageTransforms_test.cpp b/tests/include/improc/imageTransforms_test.cpp index f8f103f2e..bc50e8777 100644 --- a/tests/include/improc/imageTransforms_test.cpp +++ b/tests/include/improc/imageTransforms_test.cpp @@ -14,7 +14,13 @@ #include "../../../include/improc/imageTransforms.hpp" -#include "../../../include/math/fit/fitGaussian.hpp" +namespace +{ +double imageMSE( const mx::improc::eigenImage &a, const mx::improc::eigenImage &b ) +{ + return ( a - b ).square().mean(); +} +} // namespace /** Scenario: Verify direction and accuracy of various image shifts * @@ -28,50 +34,38 @@ SCENARIO( "Verify direction and accuracy of various image shifts", "[improc::ima { WHEN( "shifting" ) { - mx::improc::eigenImage im, shift; - mx::math::fit::fitGaussian2Dsym fit; - double x, y; + mx::improc::eigenImage im, shift, ref; im.resize( 256, 256 ); shift.resize( im.rows(), im.cols() ); - - fit.setArray( shift.data(), shift.rows(), shift.cols() ); + ref.resize( im.rows(), im.cols() ); // Use sigma = 8 to get a well oversampled image, making shifts more accurate mx::math::func::gaussian2D( im.data(), im.rows(), im.cols(), 0., 1.0, 127.5, 127.5, 8 ); - fit.setGuess( 0.0, 1.0, 127.5, 127.5, 8.0 ); mx::improc::imageShift( shift, im, -0.5, -0.5, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( - fit.x0(), - Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); // should be much better than this, but this is a test - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 127.0, 127.0, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); mx::improc::imageShift( shift, im, +0.5, +0.5, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 128.0, 128.0, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); mx::improc::imageShift( shift, im, +1.0, +1.0, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.5, 1e-4 ) ); - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.5, 1e-4 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 128.5, 128.5, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); mx::improc::imageShift( shift, im, +0.5, -0.5, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.0, 1e-4 ) ); - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 127.0, 1e-4 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 128.0, 127.0, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); mx::improc::imageShift( shift, im, -0.3, +0.7, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 127.2, 1e-3 ) ); // non-0.5 pixel shifts are harder - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 128.2, 1e-3 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 127.2, 128.2, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); mx::improc::imageShift( shift, im, 1.3, -0.7, mx::improc::cubicConvolTransform() ); - fit.fit(); - REQUIRE_THAT( fit.x0(), Catch::Matchers::WithinAbs( 128.8, 1e-3 ) ); - REQUIRE_THAT( fit.y0(), Catch::Matchers::WithinAbs( 126.8, 1e-3 ) ); + mx::math::func::gaussian2D( ref.data(), ref.rows(), ref.cols(), 0., 1.0, 128.8, 126.8, 8 ); + REQUIRE_THAT( imageMSE( shift, ref ), Catch::Matchers::WithinAbs( 0.0, 1e-5 ) ); } } } diff --git a/tests/include/sigproc/psdFilter_test.cpp b/tests/include/sigproc/psdFilter_test.cpp index be0a90a8e..5b4d61fd6 100644 --- a/tests/include/sigproc/psdFilter_test.cpp +++ b/tests/include/sigproc/psdFilter_test.cpp @@ -13,6 +13,14 @@ #include "../../../include/math/randomT.hpp" #include "../../../include/math/vectorUtils.hpp" +#ifdef MXLIB_BUILD_COVERAGE +constexpr int psdFilterTrials = 100; +constexpr double psdFilterTol = 0.06; +#else +constexpr int psdFilterTrials = 10000; +constexpr double psdFilterTol = 0.02; +#endif + /** Scenario: compiling psdFilter * * Verify compilation and initilization of the 3 ranks for psdFilter. @@ -252,7 +260,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( size_t n = 0; n < noise.size(); ++n ) noise[n] = normVar; @@ -260,9 +268,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += ( mx::math::vectorVariance( noise, 0.0 ) ); } - avgRms = sqrt( avgRms / 10000 ); + avgRms = sqrt( avgRms / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, psdFilterTol ) ); } WHEN( "alpha=-1.5, df arbitrary, var = 2.2" ) { @@ -297,7 +305,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( size_t n = 0; n < noise.size(); ++n ) noise[n] = normVar; @@ -305,9 +313,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += ( mx::math::vectorVariance( noise, 0.0 ) ); } - avgRms = sqrt( avgRms / 10000 ); + avgRms = sqrt( avgRms / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), 0.02 * sqrt( 2.2 ) ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), psdFilterTol * sqrt( 2.2 ) ) ); } } GIVEN( "a rank 2 psd" ) @@ -350,7 +358,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( int cc = 0; cc < psd.cols(); ++cc ) { @@ -364,9 +372,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += noise.square().sum(); //(mx::math::vectorVariance(noise,0.0)); } - avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / 10000 ); + avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, psdFilterTol ) ); } WHEN( "alpha=-1.5, dk arb, var=2.2" ) { @@ -406,7 +414,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( int cc = 0; cc < psd.cols(); ++cc ) { @@ -420,9 +428,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += noise.square().sum(); //(mx::math::vectorVariance(noise,0.0)); } - avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / 10000 ); + avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() ) / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), 0.02 * sqrt( 2.2 ) ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.2 ), psdFilterTol * sqrt( 2.2 ) ) ); } } GIVEN( "a rank 3 psd" ) @@ -496,7 +504,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( int pp = 0; pp < psd.planes(); ++pp ) { @@ -514,9 +522,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += noise.image( pp ).square().sum(); } - avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / 10000 ); + avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, 0.02 ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( 1.0, psdFilterTol ) ); } WHEN( "k-alpha=-3.5, f-alph=-1.5, dk arb, df arb, var=2" ) { @@ -587,7 +595,7 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) double avgRms = 0; - for( int k = 0; k < 10000; ++k ) + for( int k = 0; k < psdFilterTrials; ++k ) { for( int pp = 0; pp < psd.planes(); ++pp ) { @@ -605,9 +613,9 @@ SCENARIO( "filtering with psdFilter", "[sigproc::psdFilter]" ) avgRms += noise.image( pp ).square().sum(); } - avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / 10000 ); + avgRms = sqrt( avgRms / ( psd.rows() * psd.cols() * psd.planes() ) / psdFilterTrials ); - REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.0 ), 0.02 * sqrt( 2 ) ) ); + REQUIRE_THAT( avgRms, Catch::Matchers::WithinAbs( sqrt( 2.0 ), psdFilterTol * sqrt( 2 ) ) ); } } } From 5726911e4239f7878f56dc10c1d6a178640661c9 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 19 Feb 2026 17:01:40 -0700 Subject: [PATCH 17/21] Integrate testing docs/coverage pages and add coverage placeholder --- CMakeLists.txt | 47 +- README.md | 86 +--- cmake/ensure_coverage_placeholder.cmake | 47 ++ doc/building_tests.dox | 100 +++++ doc/coverage_report.dox | 26 ++ doc/groupdefs.dox | 9 +- doc/mxlib_doxygen.in | 9 +- doc/testing.dox | 7 + tests/coverage/coverage_epilog.html | 1 + tests/coverage/coverage_prolog.html | 41 ++ tests/coverage/gcov.css | 520 ++--------------------- tests/include/sigproc/psdFilter_test.cpp | 2 +- 12 files changed, 329 insertions(+), 566 deletions(-) create mode 100644 cmake/ensure_coverage_placeholder.cmake create mode 100644 doc/building_tests.dox create mode 100644 doc/coverage_report.dox create mode 100644 doc/testing.dox create mode 100644 tests/coverage/coverage_epilog.html create mode 100644 tests/coverage/coverage_prolog.html diff --git a/CMakeLists.txt b/CMakeLists.txt index 223033bb5..38e7900d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,6 +176,7 @@ option(MXLIB_USE_ISIO "Whether or not to use the ImageStreamIO library for mxli option(MXLIB_BUILD_TESTS "Whether or not to build mxlib tests" OFF) option(MXLIB_BUILD_COVERAGE "Whether or not to enable coverage instrumentation for C/C++ builds" OFF) set(MXLIB_COVERAGE_TEST_TIMEOUT 300 CACHE STRING "CTest timeout (seconds) for coverage target") +option(MXLIB_BUILD_DOCS "Whether or not to build Doxygen docs as part of the default build" OFF) set(MXLIB_USE_FFT_FROM "fftw" CACHE STRING "Which library to use for the FFT interface in mxlib") @@ -818,7 +819,41 @@ add_custom_target(tests set(MXLIB_COVERAGE_BUILD_DIR ${CMAKE_BINARY_DIR}/coverage-build) set(MXLIB_COVERAGE_INFO ${CMAKE_BINARY_DIR}/coverage.info) set(MXLIB_COVERAGE_FILTERED_INFO ${CMAKE_BINARY_DIR}/coverage_filtered.info) -set(MXLIB_COVERAGE_REPORT_DIR ${CMAKE_BINARY_DIR}/coverage_report) +set(MXLIB_DOC_OUTPUT_DIR ${CMAKE_BINARY_DIR}/doc) +set(MXLIB_DOC_HTML_DIR ${MXLIB_DOC_OUTPUT_DIR}/html) +set(MXLIB_COVERAGE_REPORT_DIR ${MXLIB_DOC_HTML_DIR}/coverage) + +find_package(Doxygen QUIET) + +if(Doxygen_FOUND) + set(MXLIB_DOXYGEN_CONFIG_IN ${CMAKE_SOURCE_DIR}/doc/mxlib_doxygen.in) + set(MXLIB_DOXYGEN_CONFIG ${CMAKE_BINARY_DIR}/mxlib_doxygen) + set(MXLIB_DOXYGEN_OUTPUT_DIRECTORY ${MXLIB_DOC_OUTPUT_DIR}) + + configure_file(${MXLIB_DOXYGEN_CONFIG_IN} ${MXLIB_DOXYGEN_CONFIG} @ONLY) + + if(MXLIB_BUILD_DOCS) + set(_mxlib_docs_all ALL) + else() + set(_mxlib_docs_all) + endif() + + add_custom_target(docs ${_mxlib_docs_all} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MXLIB_DOC_OUTPUT_DIR} + COMMAND ${DOXYGEN_EXECUTABLE} ${MXLIB_DOXYGEN_CONFIG} + COMMAND ${CMAKE_COMMAND} -DCOVERAGE_REPORT_DIR=${MXLIB_COVERAGE_REPORT_DIR} -P ${CMAKE_SOURCE_DIR}/cmake/ensure_coverage_placeholder.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/doc + DEPENDS ${MXLIB_DOXYGEN_CONFIG} + ${CMAKE_SOURCE_DIR}/doc/mxlib_header.html + ${CMAKE_SOURCE_DIR}/doc/mxlibDoxygenLayout.xml + ${CMAKE_SOURCE_DIR}/doc/main.dox + ${CMAKE_SOURCE_DIR}/doc/coverage_report.dox + ${CMAKE_SOURCE_DIR}/cmake/ensure_coverage_placeholder.cmake + USES_TERMINAL + ) +else() + message(WARNING "doxygen not found. The docs target will not be available.") +endif() find_program(MXLIB_LCOV_EXECUTABLE lcov) find_program(MXLIB_GENHTML_EXECUTABLE genhtml) @@ -831,9 +866,13 @@ if(MXLIB_LCOV_EXECUTABLE AND MXLIB_GENHTML_EXECUTABLE) COMMAND ${CMAKE_CTEST_COMMAND} --test-dir ${MXLIB_COVERAGE_BUILD_DIR} --output-on-failure --timeout ${MXLIB_COVERAGE_TEST_TIMEOUT} COMMAND ${MXLIB_LCOV_EXECUTABLE} --directory ${MXLIB_COVERAGE_BUILD_DIR} --capture --output-file ${MXLIB_COVERAGE_INFO} COMMAND ${MXLIB_LCOV_EXECUTABLE} --remove ${MXLIB_COVERAGE_INFO} ${CMAKE_SOURCE_DIR}/tests/* /usr/* /sys/* /tty/* --ignore-errors unused,unused --ignore-errors inconsistent,inconsistent --output-file ${MXLIB_COVERAGE_FILTERED_INFO} + COMMAND ${CMAKE_COMMAND} -E make_directory ${MXLIB_COVERAGE_REPORT_DIR} COMMAND ${MXLIB_GENHTML_EXECUTABLE} ${MXLIB_COVERAGE_FILTERED_INFO} --output-directory ${MXLIB_COVERAGE_REPORT_DIR} --title mxlib --hierarchical --merge-aliases --suppress-aliases --filter function --demangle-cpp --css-file ${CMAKE_SOURCE_DIR}/tests/coverage/gcov.css USES_TERMINAL ) + if(TARGET docs) + add_dependencies(coverage docs) + endif() else() message(WARNING "lcov/genhtml not found. The coverage target will not be available.") endif() @@ -845,4 +884,10 @@ add_custom_target(coverage_clean COMMAND ${CMAKE_COMMAND} -E rm -rf ${MXLIB_COVERAGE_BUILD_DIR} ) +if(TARGET docs) + add_custom_target(docs_clean + COMMAND ${CMAKE_COMMAND} -E rm -rf ${MXLIB_DOC_OUTPUT_DIR} + ) +endif() + #dump_cmake_variables(".") diff --git a/README.md b/README.md index 9b81e71d8..e08129bcc 100644 --- a/README.md +++ b/README.md @@ -8,87 +8,5 @@ The documentation is located here: https://jaredmales.github.io/mxlib-doc/ See the [User's Guide](https://jaredmales.github.io/mxlib-doc/modules.html) for [installation instructions](https://jaredmales.github.io/mxlib-doc/group__installation.html) -## CMake Tests - -Build tests on demand (always available, even if `MXLIB_BUILD_TESTS=OFF`): - -```bash -cmake -S . -B _build -cmake --build _build --target tests -j -``` - -Configure with tests enabled: - -```bash -cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON -``` - -With `MXLIB_BUILD_TESTS=ON`, test executables are part of the default build. -With `MXLIB_BUILD_TESTS=OFF`, they are skipped by default and built only via `tests`/`mxlibTests` targets. - -Build all test executables: - -```bash -cmake --build _build --target mxlibTests -j -``` - -Run tests: - -```bash -ctest --test-dir _build --output-on-failure -``` - -Run the CTest test suite directly: - -```bash -cmake --build _build --target mxlibTestRun -``` - -Build and run a single test source (Makefile.one equivalent): - -```bash -cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON -DMXLIB_ONE_TEST=include/math/geo_test.cpp -cmake --build _build --target mxlibTestOne -j -cmake --build _build --target mxlibTestOneRun -``` - -## Coverage - -Coverage generation is integrated into CMake and modeled after the MagAOX flow. - -Prerequisites: - -```bash -lcov --version -genhtml --version -``` - -Generate an HTML report: - -```bash -cmake -S . -B _build -cmake --build _build --target coverage -``` - -Optional: tune coverage test timeout (default `300` seconds): - -```bash -cmake -S . -B _build -DMXLIB_COVERAGE_TEST_TIMEOUT=600 -``` - -Coverage artifacts are written under `_build/`: - -- `_build/coverage.info` -- `_build/coverage_filtered.info` -- `_build/coverage_report/index.html` - -Clean coverage artifacts: - -```bash -cmake --build _build --target coverage_clean -``` - -Convenience scripts are also available: - -- `tests/coverage/make_coverage` -- `tests/coverage/update_coverage` +See the [Testing](https://jaredmales.github.io/mxlib-doc/group__mxlib__testing.html) +section for test build, test execution, and coverage instructions. diff --git a/cmake/ensure_coverage_placeholder.cmake b/cmake/ensure_coverage_placeholder.cmake new file mode 100644 index 000000000..d384e895f --- /dev/null +++ b/cmake/ensure_coverage_placeholder.cmake @@ -0,0 +1,47 @@ +if(NOT DEFINED COVERAGE_REPORT_DIR OR COVERAGE_REPORT_DIR STREQUAL "") + message(FATAL_ERROR "COVERAGE_REPORT_DIR is required") +endif() + +set(_coverage_index "${COVERAGE_REPORT_DIR}/index.html") + +# Never overwrite a real coverage report. +if(EXISTS "${_coverage_index}") + message(STATUS "Coverage index already exists at ${_coverage_index}; leaving it unchanged.") + return() +endif() + +file(MAKE_DIRECTORY "${COVERAGE_REPORT_DIR}") + +file(WRITE "${_coverage_index}" " + + + + + mxlib coverage report + + + + + + + +

Coverage report has not been generated.

+

Run cmake --build _build --target coverage to generate it.

+ + +") + +message(STATUS "Wrote coverage placeholder at ${_coverage_index}") diff --git a/doc/building_tests.dox b/doc/building_tests.dox new file mode 100644 index 000000000..22a505b33 --- /dev/null +++ b/doc/building_tests.dox @@ -0,0 +1,100 @@ +/** \addtogroup testing_building + * @{ + * + * Build tests on demand (always available, even if `MXLIB_BUILD_TESTS=OFF`): + * + * \code{.sh} + * cmake -S . -B _build + * cmake --build _build --target tests -j + * \endcode + * + * Configure with tests enabled: + * + * \code{.sh} + * cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON + * \endcode + * + * With `MXLIB_BUILD_TESTS=ON`, test executables are part of the default build. + * With `MXLIB_BUILD_TESTS=OFF`, they are skipped by default and built only via + * `tests`/`mxlibTests` targets. + * + * Build all test executables: + * + * \code{.sh} + * cmake --build _build --target mxlibTests -j + * \endcode + * + * Run tests: + * + * \code{.sh} + * ctest --test-dir _build --output-on-failure + * \endcode + * + * Run the CTest test suite directly: + * + * \code{.sh} + * cmake --build _build --target mxlibTestRun + * \endcode + * + * Build and run a single test source (Makefile.one equivalent): + * + * \code{.sh} + * cmake -S . -B _build -DMXLIB_BUILD_TESTS=ON -DMXLIB_ONE_TEST=include/math/geo_test.cpp + * cmake --build _build --target mxlibTestOne -j + * cmake --build _build --target mxlibTestOneRun + * \endcode + * + * Coverage generation is integrated into CMake and modeled after the MagAOX flow. + * + * Prerequisites: + * + * \code{.sh} + * lcov --version + * genhtml --version + * \endcode + * + * Generate an HTML coverage report: + * + * \code{.sh} + * cmake -S . -B _build + * cmake --build _build --target coverage + * \endcode + * + * Build Doxygen docs directly from CMake: + * + * \code{.sh} + * cmake --build _build --target docs + * \endcode + * + * Optionally include docs in the default build: + * + * \code{.sh} + * cmake -S . -B _build -DMXLIB_BUILD_DOCS=ON + * \endcode + * + * Optional: tune coverage test timeout (default `300` seconds): + * + * \code{.sh} + * cmake -S . -B _build -DMXLIB_COVERAGE_TEST_TIMEOUT=600 + * \endcode + * + * Coverage artifacts are written under `_build/`: + * + * - `_build/coverage.info` + * - `_build/coverage_filtered.info` + * - `_build/doc/html/coverage/index.html` + * - `_build/doc/html/index.html` + * + * Clean coverage artifacts: + * + * \code{.sh} + * cmake --build _build --target coverage_clean + * \endcode + * + * Convenience scripts are also available: + * + * - `tests/coverage/make_coverage` + * - `tests/coverage/update_coverage` + * + * @} + */ diff --git a/doc/coverage_report.dox b/doc/coverage_report.dox new file mode 100644 index 000000000..83a2a148e --- /dev/null +++ b/doc/coverage_report.dox @@ -0,0 +1,26 @@ +/** \addtogroup testing_coverage + * @{ + * + * \htmlonly + * + * + * \endhtmlonly + * + * @} + */ diff --git a/doc/groupdefs.dox b/doc/groupdefs.dox index 079b07417..0a5b686e9 100644 --- a/doc/groupdefs.dox +++ b/doc/groupdefs.dox @@ -32,7 +32,6 @@ * Various miscellaneous utilities */ - //------------------ error_handling ----------------------- /** \defgroup error_handling_codes Error Codes @@ -564,6 +563,14 @@ /** \defgroup mxlib_testing Testing */ + /** \defgroup testing_building Building Tests + * \ingroup mxlib_testing + */ + + /** \defgroup testing_coverage Coverage Report + * \ingroup mxlib_testing + */ + /** \defgroup unit_tests Unit Tests * \ingroup mxlib_testing */ diff --git a/doc/mxlib_doxygen.in b/doc/mxlib_doxygen.in index 40cb033e8..76ec8f654 100644 --- a/doc/mxlib_doxygen.in +++ b/doc/mxlib_doxygen.in @@ -68,7 +68,7 @@ PROJECT_LOGO = # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = @MXLIB_DOXYGEN_OUTPUT_DIRECTORY@ # If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 # sub-directories (in 2 levels) under the output directory of each output format @@ -935,6 +935,9 @@ WARN_LOGFILE = INPUT = ./groupdefs.dox \ ./main.dox \ + ./testing.dox \ + ./building_tests.dox \ + ./coverage_report.dox \ ./install.dox \ ./sofa.dox \ ./sofa_constants.h \ @@ -1047,7 +1050,9 @@ RECURSIVE = NO # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = ../source/vendor +EXCLUDE = ../source/vendor \ + ../agent_context.md \ + ../README.md # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/doc/testing.dox b/doc/testing.dox new file mode 100644 index 000000000..9a668ef08 --- /dev/null +++ b/doc/testing.dox @@ -0,0 +1,7 @@ +/** \addtogroup mxlib_testing + * @{ + * + * This section documents how to build and run the mxlib tests, and how to generate coverage reports. + * + * @} + */ diff --git a/tests/coverage/coverage_epilog.html b/tests/coverage/coverage_epilog.html new file mode 100644 index 000000000..04f5b8449 --- /dev/null +++ b/tests/coverage/coverage_epilog.html @@ -0,0 +1 @@ + diff --git a/tests/coverage/coverage_prolog.html b/tests/coverage/coverage_prolog.html new file mode 100644 index 000000000..3bdb46a9b --- /dev/null +++ b/tests/coverage/coverage_prolog.html @@ -0,0 +1,41 @@ +
+
+
Documentation
+ +
+
+
+ diff --git a/tests/coverage/gcov.css b/tests/coverage/gcov.css index a3db13df9..bcbb7d0a2 100644 --- a/tests/coverage/gcov.css +++ b/tests/coverage/gcov.css @@ -1,497 +1,63 @@ -@import url("https://fonts.googleapis.com/css?family=Source Code Pro|Quicksand"); - -:root { - --textColor: aliceblue; - --mainBg: #131313; - --linkColor: aquamarine; - - --lighter: #ffffff05; -} - -a, -a:visited { - color: var(--linkColor); - text-decoration: none; -} - -body center table tbody tr:nth-child(even):not(:nth-child(2)) { - background-color: var(--lighter); -} +/* Match coverage pages to mxlib Doxygen theme. */ +@import url("../doxygen.css"); +@import url("../tabs.css"); +@import url("../doxygen-awesome.css"); +@import url("../doxygen-awesome-sidebar-only.css"); +@import url("../doxygen-awesome-sidebar-only-darkmode-toggle.css"); body { - font-family: "Quicksand", "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", - sans-serif; - letter-spacing: 0.03em; - background-color: var(--mainBg); - color: var(--textColor); + margin: 0; + padding: 1rem; } -center table { - background-color: var(--lighter); - border-radius: 1em; - padding: 1em; - padding-top: 0; +body > table:first-child, +body > center > table { + width: min(1400px, 100%); + margin: 0 auto; } -td, -th { - padding: 0.5em; +th, +td { + padding: 0.3rem 0.45rem; } -td.coverBar > table { - background: none; - border-radius: 0; - padding: 0; +/* Keep lcov source readability aligned with Doxygen mono typography. */ +.source, +.source a { + font-family: var(--font-family-monospace, monospace); + font-size: 0.92rem; } -/*code-related style is below this line*/ - +/* Core line hit/miss coloring used by genhtml. + Newer lcov/genhtml uses tla* classes on source rows/spans. */ td.lineCov, -span.lineCov { - background-color: rgb(0, 30, 30); -} -td.lineNoCov, -span.lineNoCov { - background-color: rgb(50, 0, 0); -} - -.source > a { - color: var(--textColor); - font-family: "Source Code Pro", "Courier New", Courier, monospace; -} - -.source { - font-size: 1.1em; +span.lineCov, +td.tlaGNC, +span.tlaGNC, +a.tlaGNC { + background-color: #1a4f78 !important; } -/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): -Newly added code is not tested" */ -td.tlaUNC -{ - text-align: right; - background-color: #6d2914; -} -td.tlaBgUNC { - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered New Code (+ => 0): -Newly added code is not tested" */ -span.tlaUNC -{ - text-align: left; - background-color: #6d2914; -} -span.tlaBgUNC { - background-color: #6d2914; -} -a.tlaBgUNC { - background-color: #6d2914; - color: #000000; -} - -td.headerCovTableHeadUNC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): -Unchanged code is no longer tested" */ -td.tlaLBC -{ - text-align: right; - background-color: #6d2914; -} -td.tlaBgLBC { - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Lost Baseline Coverage (1 => 0): -Unchanged code is no longer tested" */ -span.tlaLBC -{ - text-align: left; - background-color: #6d2914; -} -span.tlaBgLBC { - background-color: #6d2914; -} -a.tlaBgLBC { - background-color: #6d2914; - color: #000000; -} - -td.headerCovTableHeadLBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): -Previously unused code is untested" */ -td.tlaUIC -{ - text-align: right; - background-color: #6d2914; -} -td.tlaBgUIC { - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Included Code (# => 0): -Previously unused code is untested" */ -span.tlaUIC -{ - text-align: left; - background-color: #6d2914; -} -span.tlaBgUIC { - background-color: #6d2914; -} -a.tlaBgUIC { - background-color: #6d2914; - color: #000000; -} - -td.headerCovTableHeadUIC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): -Unchanged code was untested before, is untested now" */ -td.tlaUBC -{ - text-align: right; - background-color: #6d2914; -} -td.tlaBgUBC { - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Uncovered Baseline Code (0 => 0): -Unchanged code was untested before, is untested now" */ -span.tlaUBC -{ - text-align: left; - background-color: #6d2914; -} -span.tlaBgUBC { - background-color: #6d2914; -} -a.tlaBgUBC { - background-color: #6d2914; - color: #000000; -} - -td.headerCovTableHeadUBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #6d2914; -} - -/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): -Unchanged code is tested now" */ -td.tlaGBC -{ - text-align: right; - background-color: #414963; -} -td.tlaBgGBC { - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Gained Baseline Coverage (0 => 1): -Unchanged code is tested now" */ -span.tlaGBC -{ - text-align: left; - background-color: #414963; -} -span.tlaBgGBC { - background-color: #414963; -} -a.tlaBgGBC { - background-color: #414963; - color: #000000; -} - -td.headerCovTableHeadGBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): -Previously unused code is tested now" */ -td.tlaGIC -{ - text-align: right; - background-color: #414963; -} -td.tlaBgGIC { - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Gained Included Coverage (# => 1): -Previously unused code is tested now" */ -span.tlaGIC -{ - text-align: left; - background-color: #414963; -} -span.tlaBgGIC { - background-color: #414963; -} -a.tlaBgGIC { - background-color: #414963; - color: #000000; -} - -td.headerCovTableHeadGIC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): -Newly added code is tested" */ -td.tlaGNC -{ - text-align: right; - background-color: #414963; -} -td.tlaBgGNC { - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Gained New Coverage (+ => 1): -Newly added code is tested" */ -span.tlaGNC -{ - text-align: left; - background-color: #414963; -} -span.tlaBgGNC { - background-color: #414963; -} -a.tlaBgGNC { - background-color: #414963; - color: #000000; -} - -td.headerCovTableHeadGNC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): -Unchanged code was tested before and is still tested" */ -td.tlaCBC -{ - text-align: right; - background-color: #414963; -} -td.tlaBgCBC { - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Covered Baseline Code (1 => 1): -Unchanged code was tested before and is still tested" */ -span.tlaCBC -{ - text-align: left; - background-color: #414963; -} -span.tlaBgCBC { - background-color: #414963; -} -a.tlaBgCBC { - background-color: #414963; - color: #000000; -} - -td.headerCovTableHeadCBC { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #414963; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): -Previously untested code is unused now" */ -td.tlaEUB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgEUB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Uncovered Baseline (0 => #): -Previously untested code is unused now" */ -span.tlaEUB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgEUB { - background-color: #FFFFFF; -} -a.tlaBgEUB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadEUB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): -Previously tested code is unused now" */ -td.tlaECB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgECB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Excluded Covered Baseline (1 => #): -Previously tested code is unused now" */ -span.tlaECB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgECB { - background-color: #FFFFFF; -} -a.tlaBgECB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadECB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): -Previously untested code has been deleted" */ -td.tlaDUB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgDUB { - background-color: #FFFFFF; -} - -/* Source code view/table entry background: format for lines classified as "Deleted Uncovered Baseline (0 => -): -Previously untested code has been deleted" */ -span.tlaDUB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgDUB { - background-color: #FFFFFF; -} -a.tlaBgDUB { - background-color: #FFFFFF; - color: #000000; +td.lineNoCov, +span.lineNoCov, +td.tlaUNC, +span.tlaUNC, +a.tlaUNC { + background-color: #5a2222 !important; } -td.headerCovTableHeadDUB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; +/* Ensure source line numbers/counters remain readable over highlight colors. */ +.source, +.source * { + color: var(--page-foreground-color, #d2dbde) !important; } -/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): -Previously tested code has been deleted" */ -td.tlaDCB -{ - text-align: right; - background-color: #FFFFFF; -} -td.tlaBgDCB { - background-color: #FFFFFF; +.lineNum { + color: var(--page-secondary-foreground-color, #9aa6ad) !important; } -/* Source code view/table entry background: format for lines classified as "Deleted Covered Baseline (1 => -): -Previously tested code has been deleted" */ -span.tlaDCB -{ - text-align: left; - background-color: #FFFFFF; -} -span.tlaBgDCB { - background-color: #FFFFFF; -} -a.tlaBgDCB { - background-color: #FFFFFF; - color: #000000; -} - -td.headerCovTableHeadDCB { - text-align: center; - padding-right: 6px; - padding-left: 6px; - padding-bottom: 0px; - font-family: sans-serif; - white-space: nowrap; - background-color: #FFFFFF; +/* Keep bar cells neutral; doxygen tables handle borders/background. */ +td.coverBar > table { + background: transparent; + border: 0; } diff --git a/tests/include/sigproc/psdFilter_test.cpp b/tests/include/sigproc/psdFilter_test.cpp index 5b4d61fd6..3ed4b087b 100644 --- a/tests/include/sigproc/psdFilter_test.cpp +++ b/tests/include/sigproc/psdFilter_test.cpp @@ -15,7 +15,7 @@ #ifdef MXLIB_BUILD_COVERAGE constexpr int psdFilterTrials = 100; -constexpr double psdFilterTol = 0.06; +constexpr double psdFilterTol = 0.09; #else constexpr int psdFilterTrials = 10000; constexpr double psdFilterTol = 0.02; From 5fef78dcf2c7f2cb781d01868f4095c8ddaf092b Mon Sep 17 00:00:00 2001 From: Jared Males Date: Thu, 26 Feb 2026 18:28:16 -0700 Subject: [PATCH 18/21] Fix non-square filter bounds and add regression test --- include/improc/imageFilters.hpp | 7 +++--- tests/CMakeLists.txt | 1 + tests/include/improc/imageFilters_test.cpp | 26 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/include/improc/imageFilters.hpp b/include/improc/imageFilters.hpp index 7109c2c0f..5ca2a40e6 100644 --- a/include/improc/imageFilters.hpp +++ b/include/improc/imageFilters.hpp @@ -8,6 +8,7 @@ #ifndef __imageFilters_hpp__ #define __imageFilters_hpp__ +#include #include #include @@ -329,7 +330,7 @@ struct precalcKernel std::vector m_kernels; - precalKernel() = delete; + precalcKernel() = delete; precalcKernel( const kernelT &kernel, /**< [in] A fully initialized kernel. Is copied.*/ uint32_t rows, /**< [in] The rows in the images to be filtered*/ @@ -409,7 +410,7 @@ error_t filterImage( imageOutT &fim, /**< [out] Contains the filtered ima if( maxr == 0 ) { - maxr = 0.5 * im.rows() - kernel.maxWidth(); + maxr = 0.5 * std::min( im.rows(), im.cols() ) - kernel.maxWidth(); } int mini = 0.5 * im.rows() - maxr; @@ -550,7 +551,7 @@ void medianFilterImage( imageOutT &fim, /**< [out] Contains the filtered float ycen = 0.5 * ( im.cols() - 1 ); if( maxr == 0 ) - maxr = 0.5 * im.rows() - kernel.maxWidth(); + maxr = 0.5 * std::min( im.rows(), im.cols() ) - kernel.maxWidth(); int mini = 0.5 * im.rows() - maxr; int maxi = 0.5 * im.rows() + maxr; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1c3238a4c..fc6ba464b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,7 @@ set(MXLIB_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/sigproc/signalWindows_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageTransforms_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageUtils_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageFilters_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/sys/timeUtils_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/include/improc/imageXCorrFFT_test.cpp ) diff --git a/tests/include/improc/imageFilters_test.cpp b/tests/include/improc/imageFilters_test.cpp index 297e1582a..88b1f75d3 100644 --- a/tests/include/improc/imageFilters_test.cpp +++ b/tests/include/improc/imageFilters_test.cpp @@ -2,6 +2,7 @@ */ #include "../../catch2/catch.hpp" +#include #include #include @@ -87,6 +88,31 @@ TEST_CASE( "Verify precalcKernel filter", "[improc::imageFilters]" ) } } +TEST_CASE( "Gaussian filter handles non-square image dimensions", "[improc::imageFilters]" ) +{ + mx::improc::eigenImage im; + mx::improc::eigenImage fim; + + im.resize( 640, 480 ); + for( int r = 0; r < im.rows(); ++r ) + { + for( int c = 0; c < im.cols(); ++c ) + { + im( r, c ) = static_cast( ( r + 2 * c ) % 17 ); + } + } + + mx::improc::gaussKernel, 2> kernel( 10.0f ); + auto err = mx::improc::filterImage( fim, im, kernel ); + + REQUIRE( err == mx::error_t::noerror ); + REQUIRE( fim.rows() == im.rows() ); + REQUIRE( fim.cols() == im.cols() ); + REQUIRE( std::isfinite( fim( 0, 0 ) ) ); + REQUIRE( std::isfinite( fim( fim.rows() - 1, fim.cols() - 1 ) ) ); + REQUIRE( std::isfinite( fim( fim.rows() / 2, fim.cols() / 2 ) ) ); +} + } // namespace imageFiltersTest } // namespace improcTest } // namespace unitTest From 078f4b46a8ff6c66f895c4c97180bb90547fb8e4 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Wed, 11 Mar 2026 11:08:44 -0700 Subject: [PATCH 19/21] updated milkImage --- include/improc/milkImage.hpp | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/include/improc/milkImage.hpp b/include/improc/milkImage.hpp index e0006db2c..229378bd9 100644 --- a/include/improc/milkImage.hpp +++ b/include/improc/milkImage.hpp @@ -6,7 +6,7 @@ */ //***********************************************************************// -// Copyright 2015, 2016, 2017 Jared R. Males (jaredmales@gmail.com) +// Copyright 2025 Jared R. Males (jaredmales@gmail.com) // // This file is part of mxlib. // @@ -403,11 +403,35 @@ class milkImage */ void post(); + /// Get the creation time + /** + * \returns the ImageStream creationtime + */ + const timespec & creationtime() const; + + /// Get the last access time + /** + * \returns ImageStream lastaccesstime + */ + const timespec & lastaccesstime() const; + + /// Get the acquisition time + /** + * \returns ImageStream acquisition time + */ + const timespec & atime() const; + + /// Get the write time + /** + * \returns ImageStream writetime + */ + const timespec & writetime() const; + /// Get the value of cnt0 (the frame counter) /** * \returns the current value of cnt0 */ - uint64_t cnt0(); + const uint64_t & cnt0() const; }; template @@ -792,11 +816,36 @@ void milkImage::post() } template -uint64_t milkImage::cnt0() +const timespec & milkImage::creationtime() const +{ + return m_image->md->creationtime; +} + +template +const timespec & milkImage::lastaccesstime() const +{ + return m_image->md->lastaccesstime; +} + +template +const timespec & milkImage::atime() const +{ + return m_image->md->atime; +} + +template +const timespec & milkImage::writetime() const +{ + return m_image->md->writetime; +} + +template +const uint64_t & milkImage::cnt0() const { return m_image->md->cnt0; } + } // namespace improc } // namespace mx From e9ae6516291fe9d04306c0bce70e72c6ea76aa24 Mon Sep 17 00:00:00 2001 From: Jared Males Date: Sat, 21 Mar 2026 16:42:32 -0300 Subject: [PATCH 20/21] fixes to turb prop --- include/ao/sim/turbAtmosphere.hpp | 7 ++----- include/ao/sim/turbLayer.hpp | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/include/ao/sim/turbAtmosphere.hpp b/include/ao/sim/turbAtmosphere.hpp index 728931f77..a34e9bc7a 100644 --- a/include/ao/sim/turbAtmosphere.hpp +++ b/include/ao/sim/turbAtmosphere.hpp @@ -736,7 +736,7 @@ int turbAtmosphere::shift( improc::milkImage &milkPh { } - // Don't use OMP if no multiple layers b/c it seems to make it use multiple threads in some awful way + // Don't use OMP if not multiple layers b/c it seems to make it use multiple threads in some awful way if( m_layers.size() > 1 ) { // clang-format off @@ -748,10 +748,7 @@ int turbAtmosphere::shift( improc::milkImage &milkPh } else { - for( size_t j = 0; j < m_layers.size(); ++j ) - { - m_layers[j].shift( dt ); - } + m_layers[0].shift( dt ); } milkPhase.setWrite(); diff --git a/include/ao/sim/turbLayer.hpp b/include/ao/sim/turbLayer.hpp index 89baa69d2..19b318d95 100644 --- a/include/ao/sim/turbLayer.hpp +++ b/include/ao/sim/turbLayer.hpp @@ -293,23 +293,32 @@ void turbLayer::shift( realT dt ) ddx = m_x0 + m_dx * dt; ddy = m_y0 + m_dy * dt; - wdx = (int)trunc( ddx ); + wdx = static_cast( std::floor( ddx ) ); ddx -= wdx; - wdy = (int)trunc( ddy ); + wdy = static_cast( std::floor( ddy ) ); ddy -= wdy; - wdx %= m_scrnSz; - wdy %= m_scrnSz; + wdx %= static_cast( m_scrnSz ); + wdy %= static_cast( m_scrnSz ); + + if( wdx < 0 ) + wdx += m_scrnSz; + if( wdy < 0 ) + wdy += m_scrnSz; if( dt == 0 ) { m_shiftPhase = m_phase; + // Prime both the whole-pixel and shifted buffers so the first + // subsequent sub-pixel shift has valid input data. + improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); + m_shiftPhase = m_shiftPhaseWP; } else { // Check for a new whole-pixel shift - if( wdx != m_last_wdx || wdy != m_last_wdy ) + if( wdx != m_last_wdx || wdy != m_last_wdy || m_last_wdx == m_scrnSz + 1 || m_last_wdy == m_scrnSz + 1 ) { // Need a whole pixel shift (this also extracts the m_wfSz + m_buffSz subarray) improc::imageShiftWP( m_shiftPhaseWP, m_phase, wdx, wdy ); From cdadd0b32db8314e19ca840a0428094f95f4163c Mon Sep 17 00:00:00 2001 From: Jared Males Date: Sat, 21 Mar 2026 18:30:29 -0300 Subject: [PATCH 21/21] circularBuffer: add fixed-size SPSC mode --- include/sigproc/circularBuffer.hpp | 363 +++++++++++++++++++++++++++-- 1 file changed, 346 insertions(+), 17 deletions(-) diff --git a/include/sigproc/circularBuffer.hpp b/include/sigproc/circularBuffer.hpp index b1ddac2f9..4ebca16a4 100644 --- a/include/sigproc/circularBuffer.hpp +++ b/include/sigproc/circularBuffer.hpp @@ -27,6 +27,7 @@ #ifndef sigproc_circularBuffer #define sigproc_circularBuffer +#include #include namespace mx @@ -80,20 +81,20 @@ class circularBufferBase public: typedef _derivedT derivedT; ///< The child class - typedef _storedT storedT; ///< The type stored in the circular buffer - typedef _indexT indexT; ///< The index type, also used for sizes + typedef _storedT storedT; ///< The type stored in the circular buffer + typedef _indexT indexT; ///< The index type, also used for sizes - static_assert(std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed"); + static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" ); protected: std::vector m_buffer; ///< The circular buffer storage - indexT m_maxEntries{ 0 }; ///< The maximum number of entries to allow in the buffer before wrapping - indexT m_nextEntry{ 0 }; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer. - indexT m_latest{ 0 }; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer. + indexT m_maxEntries{ 0 }; ///< The maximum number of entries to allow in the buffer before wrapping + indexT m_nextEntry{ 0 }; ///< Index into m_buffer of the next entry. This is the oldest entry in the buffer. + indexT m_latest{ 0 }; ///< Index into m_buff of the latest entry. This is the newest entry in the buffer. - uint64_t m_mono{ 0 }; /**< A monotonic counter, which is incremented for each new entry, - and reset on call to maxEntries.*/ + uint64_t m_mono{ 0 }; /**< A monotonic counter, which is incremented for each new entry, + and reset on call to maxEntries.*/ public: /// Default c'tor @@ -363,11 +364,11 @@ class circularBufferBranch : public circularBufferBasem_buffer[_idx + this->m_buffer.size()]; } - else if( _idx > this->m_buffer.size()-1 ) + else if( _idx > this->m_buffer.size() - 1 ) { return this->m_buffer[_idx - this->m_buffer.size()]; } @@ -389,11 +390,11 @@ class circularBufferBranch : public circularBufferBasem_buffer[_idx + this->m_buffer.size()]; } - else if( _idx > this->m_buffer.size()-1 ) + else if( _idx > this->m_buffer.size() - 1 ) { return this->m_buffer[_idx - this->m_buffer.size()]; } @@ -404,7 +405,7 @@ class circularBufferBranch : public circularBufferBase +class circularBufferIndex; + template -class circularBufferIndex : public circularBufferBase, _storedT, _indexT> +class circularBufferIndex<_storedT, _indexT, false> + : public circularBufferBase, _storedT, _indexT> { public: typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping @@ -502,7 +507,7 @@ class circularBufferIndex : public circularBufferBasem_maxEntries + i] = i; - m_indices[2*this->m_maxEntries + i] = i; + m_indices[2 * this->m_maxEntries + i] = i; } } @@ -515,7 +520,7 @@ class circularBufferIndex : public circularBufferBasem_buffer[m_indices[this->m_maxEntries+refEntry + idx]]; + return this->m_buffer[m_indices[this->m_maxEntries + refEntry + idx]]; } /// Interface implementation for entry access, const version @@ -529,7 +534,331 @@ class circularBufferIndex : public circularBufferBasem_buffer[this->m_maxEntries+m_indices[refEntry + idx]]; + return this->m_buffer[this->m_maxEntries + m_indices[refEntry + idx]]; + } +}; + +/// Fixed-size circular buffer specialization for single-producer/single-consumer use. +/** + * \ingroup circular_buffer + */ +template +class circularBufferIndex<_storedT, _indexT, true> +{ + public: + typedef _storedT storedT; ///< The maximum number of entries to allow in the buffer before wrapping + typedef _indexT indexT; ///< The index type, also used for sizes + + static_assert( std::is_signed_v<_indexT> == true, "circularBuffer indexT must be signed" ); + static_assert( std::is_trivially_copyable_v<_storedT> == true, + "fixed_size circularBufferIndex requires trivially copyable storedT" ); + + /// Snapshot of the readable state of the buffer. + struct snapshotT + { + indexT earliest{ 0 }; ///< Earliest readable slot in the current snapshot. + indexT latest{ 0 }; ///< Latest published slot in the current snapshot. + indexT validEntries{ 0 }; ///< Number of currently valid entries. + indexT maxEntries{ 0 }; ///< Fixed capacity. + uint64_t mono{ 0 }; ///< Publication generation for retry validation. + bool full{ false }; ///< Whether the buffer has reached fixed capacity. + }; + + protected: + std::vector m_buffer; ///< The circular buffer storage. + std::vector m_indices; ///< Vector of indices for fast indexing into m_buffer. + + std::atomic m_nextEntry{ 0 }; ///< The slot that will be written next by the producer. + std::atomic m_latest{ 0 }; ///< The latest slot published by the producer. + std::atomic m_validEntries{ 0 }; ///< The number of valid published entries. + std::atomic m_mono{ 0 }; ///< A monotonic publication counter. + + indexT m_maxEntries{ 0 }; ///< Fixed capacity of the buffer. + + public: + /// Default c'tor. + circularBufferIndex() = default; + + /// Sizing constructor. + explicit circularBufferIndex( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ ) + { + maxEntries( maxEnt ); + } + + /// Set the maximum size of the buffer. + void maxEntries( indexT maxEnt /**< [in] the maximum number of entries this buffer will hold*/ ) + { + m_buffer.clear(); + m_indices.clear(); + + m_maxEntries = maxEnt; + + if( m_maxEntries > 0 ) + { + m_buffer.resize( m_maxEntries ); + m_indices.resize( 3 * m_maxEntries ); + + for( size_t i = 0; i < static_cast( m_maxEntries ); ++i ) + { + m_indices[i] = i; + m_indices[m_maxEntries + i] = i; + m_indices[2 * m_maxEntries + i] = i; + } + } + + m_nextEntry.store( 0, std::memory_order_release ); + m_latest.store( 0, std::memory_order_release ); + m_validEntries.store( 0, std::memory_order_release ); + m_mono.store( 0, std::memory_order_release ); + } + + /// Get the fixed capacity of the buffer. + indexT maxEntries() + { + return m_maxEntries; + } + + /// Get the number of valid entries. + indexT size() const + { + return m_validEntries.load( std::memory_order_acquire ); + } + + /// Add the next entry to the circular buffer. + void nextEntry( const storedT &newEnt /**< [in] the new entry to add to the buffer*/ ) + { + if( m_maxEntries <= 0 ) + { + return; + } + + indexT next = m_nextEntry.load( std::memory_order_relaxed ); + + m_buffer[next] = newEnt; + + indexT latest = next; + ++next; + if( next >= m_maxEntries ) + { + next = 0; + } + + indexT validEntries = m_validEntries.load( std::memory_order_relaxed ); + if( validEntries < m_maxEntries ) + { + ++validEntries; + } + + m_latest.store( latest, std::memory_order_relaxed ); + m_validEntries.store( validEntries, std::memory_order_relaxed ); + m_nextEntry.store( next, std::memory_order_relaxed ); + m_mono.fetch_add( 1, std::memory_order_release ); + } + + /// Move to the next entry using a default constructed value. + void nextEntry() + { + nextEntry( storedT{} ); + } + + /// Returns the index of the earliest entry. + indexT earliest() const + { + snapshotT sn = snapshot(); + return sn.earliest; + } + + /// Returns the index of the latest entry. + indexT latest() const + { + return m_latest.load( std::memory_order_acquire ); + } + + /// Returns the publication counter. + uint64_t mono() const + { + return m_mono.load( std::memory_order_acquire ); + } + + /// Get the entry at a given index. + storedT operator[]( indexT idx /**< [in] the index of the entry to access*/ ) const + { + snapshotT sn = snapshot(); + + if( sn.validEntries <= 0 ) + { + return storedT{}; + } + + return at( sn.earliest, idx ); + } + + /// Get the entry at a given index relative a fixed reference entry. + storedT at( indexT refEntry, ///< [in] the entry to start counting from + indexT idx ///< [in] the index of the entry to access + ) const + { + return m_buffer[physicalIndex( refEntry, idx )]; + } + + /// Return a snapshot of the current readable region. + snapshotT snapshot() const + { + snapshotT sn; + + sn.mono = m_mono.load( std::memory_order_acquire ); + sn.latest = m_latest.load( std::memory_order_relaxed ); + sn.validEntries = m_validEntries.load( std::memory_order_relaxed ); + sn.maxEntries = m_maxEntries; + sn.full = ( sn.validEntries == sn.maxEntries && sn.maxEntries > 0 ); + + if( sn.full ) + { + sn.earliest = m_nextEntry.load( std::memory_order_relaxed ); + } + else + { + sn.earliest = 0; + } + + return sn; + } + + /// Determine whether a logical window is readable from the provided snapshot. + bool windowReadable( const snapshotT &sn, ///< [in] the snapshot to validate against + indexT refEntry, ///< [in] the entry to start counting from + indexT count ///< [in] the number of entries requested + ) const + { + if( count <= 0 || refEntry < 0 || count > sn.validEntries ) + { + return false; + } + + if( !sn.full ) + { + return refEntry + count <= sn.validEntries; + } + + for( indexT n = 0; n < count; ++n ) + { + if( physicalIndex( refEntry, n ) == sn.earliest ) + { + return false; + } + } + + return true; + } + + /// Copy a logical sequence into caller-owned storage, retrying if the producer advances. + bool loadSequence( indexT refEntry, ///< [in] the entry to start counting from + indexT count, ///< [in] the number of entries requested + storedT *dest, ///< [out] destination storage of size count + snapshotT &sn, ///< [out] the validated snapshot used for the copy + int maxRetries = 3 /**< [in] the maximum number of retries*/ + ) const + { + if( dest == nullptr ) + { + return false; + } + + for( int retry = 0; retry < maxRetries; ++retry ) + { + sn = snapshot(); + + if( !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + if( m_mono.load( std::memory_order_acquire ) == sn.mono ) + { + return true; + } + } + + return false; + } + + /// Copy a logical sequence from a caller-supplied snapshot, validating that the producer has not advanced. + bool loadSequence( const snapshotT &sn, ///< [in] the snapshot to validate against + indexT refEntry, ///< [in] the entry to start counting from + indexT count, ///< [in] the number of entries requested + storedT *dest ///< [out] destination storage of size count + ) const + { + if( dest == nullptr || !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + return m_mono.load( std::memory_order_acquire ) == sn.mono; + } + + /// Copy the latest logical sequence into caller-owned storage, retrying if the producer advances. + bool loadLatestSequence( indexT count, ///< [in] the number of entries requested + storedT *dest, ///< [out] destination storage of size count + snapshotT &sn, ///< [out] the validated snapshot used for the copy + int maxRetries = 3 /**< [in] the maximum number of retries*/ + ) const + { + for( int retry = 0; retry < maxRetries; ++retry ) + { + sn = snapshot(); + + if( count <= 0 || count > sn.validEntries ) + { + return false; + } + + indexT refEntry; + if( sn.latest >= count - 1 ) + { + refEntry = sn.latest - ( count - 1 ); + } + else + { + refEntry = sn.maxEntries + sn.latest - ( count - 1 ); + } + + if( !windowReadable( sn, refEntry, count ) ) + { + return false; + } + + for( indexT n = 0; n < count; ++n ) + { + dest[n] = m_buffer[physicalIndex( refEntry, n )]; + } + + if( m_mono.load( std::memory_order_acquire ) == sn.mono ) + { + return true; + } + } + + return false; + } + + protected: + /// Convert a logical index into a physical slot index. + indexT physicalIndex( indexT refEntry, ///< [in] the entry to start counting from + indexT idx ///< [in] the index of the entry to access + ) const + { + return static_cast( m_indices[m_maxEntries + refEntry + idx] ); } };