From 5b0a71f0e569ac2ca5abe6abc097acd9bd9f1435 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Thu, 22 Jan 2026 11:30:43 +0000 Subject: [PATCH 01/13] split out cubic bspline and cubic hermite interpolation into two seperate classes. --- src/pyvale/dic/cpp/dicfourier.cpp | 2 +- src/pyvale/dic/cpp/dicfourier.hpp | 2 +- src/pyvale/dic/cpp/dicinterp.hpp | 97 +++++++++ src/pyvale/dic/cpp/dicinterpBspline.cpp | 201 ++++++++++++++++++ src/pyvale/dic/cpp/dicinterpBspline.hpp | 96 +++++++++ ...cinterpolator.cpp => dicinterpHermite.cpp} | 27 +-- ...cinterpolator.hpp => dicinterpHermite.hpp} | 51 ++--- src/pyvale/dic/cpp/dicmain.cpp | 28 ++- src/pyvale/dic/cpp/dicoptimizer.cpp | 14 +- src/pyvale/dic/cpp/dicoptimizer.hpp | 2 +- src/pyvale/dic/cpp/dicscanmethod.cpp | 2 +- src/pyvale/dic/cpp/dicsubset.cpp | 4 +- src/pyvale/dic/cpp/dicsubset.hpp | 2 +- src/pyvale/dic/dicchecks.py | 4 +- 14 files changed, 459 insertions(+), 73 deletions(-) create mode 100644 src/pyvale/dic/cpp/dicinterp.hpp create mode 100644 src/pyvale/dic/cpp/dicinterpBspline.cpp create mode 100644 src/pyvale/dic/cpp/dicinterpBspline.hpp rename src/pyvale/dic/cpp/{dicinterpolator.cpp => dicinterpHermite.cpp} (94%) rename src/pyvale/dic/cpp/{dicinterpolator.hpp => dicinterpHermite.hpp} (81%) diff --git a/src/pyvale/dic/cpp/dicfourier.cpp b/src/pyvale/dic/cpp/dicfourier.cpp index 267b67c2..e481b578 100644 --- a/src/pyvale/dic/cpp/dicfourier.cpp +++ b/src/pyvale/dic/cpp/dicfourier.cpp @@ -23,7 +23,7 @@ // DIC Header files #include "dicfourier.hpp" #include "dicsubset.hpp" -#include "dicinterpolator.hpp" +#include "dicinterp.hpp" namespace fourier { diff --git a/src/pyvale/dic/cpp/dicfourier.hpp b/src/pyvale/dic/cpp/dicfourier.hpp index 06c0e062..2110e954 100644 --- a/src/pyvale/dic/cpp/dicfourier.hpp +++ b/src/pyvale/dic/cpp/dicfourier.hpp @@ -19,7 +19,7 @@ #include // DIC Header files -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" #include "./dicsubset.hpp" #include "./dicutil.hpp" diff --git a/src/pyvale/dic/cpp/dicinterp.hpp b/src/pyvale/dic/cpp/dicinterp.hpp new file mode 100644 index 00000000..ea6e833e --- /dev/null +++ b/src/pyvale/dic/cpp/dicinterp.hpp @@ -0,0 +1,97 @@ +// ================================================================================ +// pyvale: the python validation engine +// License: MIT +// Copyright (C) 2025 The Computer Aided Validation Team +// ================================================================================ + +#ifndef DICINTERP_H +#define DICINTERP_H + +// STD library Header files + +// Program Header files + + + + +inline int idx_from_2d(const int x, const int y, const int length){ + return y*length+x; +} + + + +/** + * @brief namespace for bicubic spline interpolation. + * + * Based on the implementation by GNU Scientific Library (GSL). + * Main difference is the removal of the binary search for index lookup. + * For use in DIC, we only ever need integer locations and therefore its + * sufficient to get the floor value of the subpixel location. + * + */ +struct InterpVals { + double f; + double dfdx; + double dfdy; +}; + +class Interpolator { +public: + + int px_vert; + int px_hori; + + /** + * @brief Evaluates the bicubic interpolation at a specified point. + * + * Computes the interpolated value at (x,y) using bicubic interpolation from the surrounding pixel values. + * + * @param x The x-coordinate of the interpolation point + * @param y The y-coordinate of the interpolation point + * @return The interpolated value at (x,y) + */ + virtual double eval(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const = 0; + + /** + * @brief Evaluates the x-derivative of bicubic interpolation at a specified point. + * + * Computes the partial derivative with respect to x at point (x,y). + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return The x-derivative of the interpolated function at (x,y) + */ + virtual double eval_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const = 0; + + /** + * @brief Evaluates the y-derivative of bicubic interpolation at a specified point. + * + * Computes the partial derivative with respect to y at point (x,y). + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return The y-derivative of the interpolated function at (x,y) + */ + virtual double eval_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const = 0; + + /** + * @brief Evaluates the bicubic interpolation and its derivatives at a specified point. + * + * Computes the interpolated value and its partial derivatives at (x,y) in a single call. + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return Data struct containing the interpolated value and its x and y derivatives + */ + virtual InterpVals eval_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const = 0; + + + virtual ~Interpolator() = default; + +}; + +#endif //DICINTERP_H + + + + diff --git a/src/pyvale/dic/cpp/dicinterpBspline.cpp b/src/pyvale/dic/cpp/dicinterpBspline.cpp new file mode 100644 index 00000000..472fab6f --- /dev/null +++ b/src/pyvale/dic/cpp/dicinterpBspline.cpp @@ -0,0 +1,201 @@ +// ================================================================================ +// pyvale: the python validation engine +// License: MIT +// Copyright (C) 2025 The Computer Aided Validation Team +// ================================================================================ + + + +#include +#include +#include + +#include "./dicinterpBspline.hpp" + +Bspline::Bspline(double* img, int px_hori, int px_vert){ + + // intitialise vars used globally within Interpolator. + this->image = img; + this->px_vert = px_vert; + this->px_hori = px_hori; + coeff.resize(px_vert*px_hori); + + for (int i = 0; i < px_hori*px_vert; i++){ + coeff[i] = img[i]; + } + + prefilter_x(); + prefilter_y(); +} + +// 1D cubic B-spline basis and derivatives +inline void Bspline::basis(double t, double B[4]) { + double tt = t*t, ttt = tt*t; + + B[0] = (1.0 - 3.0*t + 3.0*tt - ttt) / 6.0; + B[1] = (4.0 - 6.0*tt + 3.0*ttt) / 6.0; + B[2] = (1.0 + 3.0*t + 3.0*tt - 3.0*ttt) / 6.0; + B[3] = ttt / 6.0; +} + +inline void Bspline::basis_d(double t, double Bd[4]) { + double tt = t*t; + + Bd[0] = (-3.0 + 6.0*t - 3.0*tt) / 6.0; + Bd[1] = (-12.0*t + 9.0*tt) / 6.0; + Bd[2] = (3.0 + 6.0*t - 9.0*tt) / 6.0; + Bd[3] = (3.0*tt) / 6.0; +} + + +void Bspline::prefilter_x() { + + const double z = std::sqrt(3.0) - 2.0; + const double lambda = (1.0 - z)*(1.0 - 1.0/z); + + // Normalize + for (int y = 0; y < px_vert; y++) + for (int x = 0; x < px_hori; x++) + coeff[y*px_hori + x] *= lambda; + + // Causal + for (int y = 0; y < px_vert; y++) { + double* row = &coeff[y*px_hori]; + for (int x = 1; x < px_hori; x++) + row[x] += z * row[x-1]; + } + + // Anticausal + for (int y = 0; y < px_vert; y++) { + double* row = &coeff[y*px_hori]; + row[px_hori-1] = z/(z*z - 1.0) * row[px_hori-1]; + for (int x = px_hori-2; x >= 0; x--) + row[x] = z*(row[x+1] - row[x]); + } +} + +// ------------------------------------------------------- +// Prefilter along each column +// ------------------------------------------------------- +void Bspline::prefilter_y() { + const double z = std::sqrt(3.0) - 2.0; + const double lambda = (1.0 - z)*(1.0 - 1.0/z); + + // Normalize + for (int y = 0; y < px_vert; y++) + for (int x = 0; x < px_hori; x++) + coeff[y*px_hori + x] *= lambda; + + // Causal + for (int x = 0; x < px_hori; x++) { + for (int y = 1; y < px_vert; y++) + coeff[y*px_hori + x] += z * coeff[(y-1)*px_hori + x]; + } + + // Anticausal + for (int x = 0; x < px_hori; x++) { + coeff[(px_vert-1)*px_hori + x] = z/(z*z - 1.0) * coeff[(px_vert-1)*px_hori + x]; + for (int y = px_vert-2; y >= 0; y--) + coeff[y*px_hori + x] = z*(coeff[(y+1)*px_hori + x] - coeff[y*px_hori + x]); + } +} + + +double Bspline::eval(const int ss_x, const int ss_y, const double subpx_x, double subpx_y) const { + int ix = (int)floor(subpx_x); + int iy = (int)floor(subpx_y); + + double tx = subpx_x - ix; + double ty = subpx_y - iy; + + double Bx[4], By[4]; + basis(tx, Bx); + basis(ty, By); + + double f = 0.0; + for (int j = 0; j < 4; j++) { + int yy = std::clamp(iy + j - 1, 0, px_vert-1); + + for (int i = 0; i < 4; i++) { + int xx = std::clamp(ix + i - 1, 0, px_hori-1); + double c = coeff[yy*px_hori + xx]; + f += c * Bx[i] * By[j]; + } + } + return f; +} + +double Bspline::eval_dx(const int ss_x, const int ss_y, const double subpx_x, double subpx_y) const { + int ix = (int)floor(subpx_x); + int iy = (int)floor(subpx_y); + + double tx = subpx_x - ix; + double ty = subpx_y - iy; + + double By[4], dBx[4]; + basis(ty, By); + basis_d(tx, dBx); + + double dfdx = 0.0; + for (int j = 0; j < 4; j++) { + int yy = std::clamp(iy + j - 1, 0, px_vert-1); + for (int i = 0; i < 4; i++) { + int xx = std::clamp(ix + i - 1, 0, px_hori-1); + double c = coeff[yy*px_hori + xx]; + dfdx += c * dBx[i] * By[j]; + } + } + return dfdx; +} + + +double Bspline::eval_dy(const int ss_x, const int ss_y, const double subpx_x, double subpx_y) const { + int ix = (int)floor(subpx_x); + int iy = (int)floor(subpx_y); + + double tx = subpx_x - ix; + double ty = subpx_y - iy; + + double Bx[4], dBy[4]; + basis(tx, Bx); + basis_d(ty, dBy); + + double dfdy = 0.0; + for (int j = 0; j < 4; j++) { + int yy = std::clamp(iy + j - 1, 0, px_vert-1); + for (int i = 0; i < 4; i++) { + int xx = std::clamp(ix + i - 1, 0, px_hori-1); + double c = coeff[yy*px_hori + xx]; + dfdy += c * Bx[i] * dBy[j]; + } + } + return dfdy; +} + +InterpVals Bspline::eval_and_derivs(const int ss_x, const int ss_y, const double subpx_x, double subpx_y) const { + int ix = (int)floor(subpx_x); + int iy = (int)floor(subpx_y); + + double tx = subpx_x - ix; + double ty = subpx_y - iy; + + double Bx[4], By[4], dBx[4], dBy[4]; + basis(tx, Bx); + basis(ty, By); + basis_d(tx, dBx); + basis_d(ty, dBy); + + InterpVals out {0,0,0}; + + for (int j = 0; j < 4; j++) { + int yy = std::clamp(iy + j - 1, 0, px_vert-1); + for (int i = 0; i < 4; i++) { + int xx = std::clamp(ix + i - 1, 0, px_hori-1); + double c = coeff[yy*px_hori + xx]; + out.f += c * Bx[i] * By[j]; + out.dfdx += c * dBx[i] * By[j]; + out.dfdy += c * Bx[i] * dBy[j]; + } + } + return out; +} diff --git a/src/pyvale/dic/cpp/dicinterpBspline.hpp b/src/pyvale/dic/cpp/dicinterpBspline.hpp new file mode 100644 index 00000000..70a5fbeb --- /dev/null +++ b/src/pyvale/dic/cpp/dicinterpBspline.hpp @@ -0,0 +1,96 @@ +// ================================================================================ +// pyvale: the python validation engine +// License: MIT +// Copyright (C) 2025 The Computer Aided Validation Team +// ================================================================================ + + + + +#ifndef DICINTERPBSPLINE_H +#define DICINTERPBSPLINE_H + + +// STD library Header files +#include +#include + +// Program Header files +#include "./dicinterp.hpp" + + +class Bspline : public Interpolator { + +private: + + std::vector coeff; + double *image; + + // Recursive spline prefilter + void prefilter_x(); + void prefilter_y(); + + // 1D cubic B-spline basis and derivatives + static inline void basis(double t, double B[4]); + static inline void basis_d(double t, double Bd[4]); + +public: + + /** + * @brief Initializes the bicubic interpolator with deformed image data. + * + * Sets up the necessary data structures and computes derivatives required for bicubic interpolation. + * + * @param img Pointer to the image data array + * @param px_hori Width of the image in pixels + * @param px_vert Height of the image in pixels + */ + Bspline(double * img, int px_hori, int px_vert); + + /** + * @brief Evaluates the bicubic interpolation at a specified point. + * + * Computes the interpolated value at (x,y) using bicubic interpolation from the surrounding pixel values. + * + * @param x The x-coordinate of the interpolation point + * @param y The y-coordinate of the interpolation point + * @return The interpolated value at (x,y) + */ + double eval(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; + + /** + * @brief Evaluates the x-derivative of bicubic interpolation at a specified point. + * + * Computes the partial derivative with respect to x at point (x,y). + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return The x-derivative of the interpolated function at (x,y) + */ + double eval_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; + + /** + * @brief Evaluates the y-derivative of bicubic interpolation at a specified point. + * + * Computes the partial derivative with respect to y at point (x,y). + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return The y-derivative of the interpolated function at (x,y) + */ + double eval_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; + + /** + * @brief Evaluates the bicubic interpolation and its derivatives at a specified point. + * + * Computes the interpolated value and its partial derivatives at (x,y) in a single call. + * + * @param x The x-coordinate of the point + * @param y The y-coordinate of the point + * @return Data struct containing the interpolated value and its x and y derivatives + */ + InterpVals eval_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; + +}; + +#endif //DICINTERPBSPLINE_H diff --git a/src/pyvale/dic/cpp/dicinterpolator.cpp b/src/pyvale/dic/cpp/dicinterpHermite.cpp similarity index 94% rename from src/pyvale/dic/cpp/dicinterpolator.cpp rename to src/pyvale/dic/cpp/dicinterpHermite.cpp index bf79d6b1..2a1b56e2 100644 --- a/src/pyvale/dic/cpp/dicinterpolator.cpp +++ b/src/pyvale/dic/cpp/dicinterpHermite.cpp @@ -17,16 +17,11 @@ #include "../../common_cpp/dicsignalhandler.hpp" // DIC Header files -#include "./dicinterpolator.hpp" - - -inline int idx_from_2d(const int x, const int y, const int length){ - return y*length+x; -} +#include "./dicinterpHermite.hpp" -Interpolator::Interpolator(double*img, int px_hori, int px_vert){ +Hermite::Hermite(double *img, int px_hori, int px_vert){ //Timer timer("interpolator initialisation"); @@ -171,7 +166,7 @@ Interpolator::Interpolator(double*img, int px_hori, int px_vert){ } } -double Interpolator::eval_bicubic(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const { +double Hermite::eval(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const { // get indices size_t xi,yi; @@ -241,7 +236,7 @@ double Interpolator::eval_bicubic(const int ss_x, const int ss_y, const double s -double Interpolator::eval_bicubic_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ +double Hermite::eval_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ /* first compute the indices into the data arrays where we are interpolating */ size_t xi,yi; @@ -297,7 +292,7 @@ double Interpolator::eval_bicubic_dx(const int ss_x, const int ss_y, const doubl } -double Interpolator::eval_bicubic_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ +double Hermite::eval_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ size_t xi,yi; index_lookup_xy(ss_x, ss_y, xi, yi, subpx_x, subpx_y); @@ -353,7 +348,7 @@ double Interpolator::eval_bicubic_dy(const int ss_x, const int ss_y, const doubl } -InterpVals Interpolator::eval_bicubic_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ +InterpVals Hermite::eval_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const{ // pixel floor of x and y size_t xi,yi; @@ -446,7 +441,7 @@ InterpVals Interpolator::eval_bicubic_and_derivs(const int ss_x, const int ss_y, -inline void Interpolator::coeff_calc(std::vector &tridiag_solution, double dy, double dx, size_t i, double *b, double *c, double *d) { +inline void Hermite::coeff_calc(std::vector &tridiag_solution, double dy, double dx, size_t i, double *b, double *c, double *d) { const double s_i = tridiag_solution[i]; const double s_ip1 = tridiag_solution[i + 1]; @@ -458,7 +453,7 @@ inline void Interpolator::coeff_calc(std::vector &tridiag_solution, doub } -inline void Interpolator::index_lookup_xy(const int ss_x, const int ss_y, size_t &xi, size_t &yi, const double subpx_x, const double subpx_y) const { +inline void Hermite::index_lookup_xy(const int ss_x, const int ss_y, size_t &xi, size_t &yi, const double subpx_x, const double subpx_y) const { if (subpx_x < px_x[0]) xi = 0; @@ -498,7 +493,7 @@ inline void Interpolator::index_lookup_xy(const int ss_x, const int ss_y, size_t } -inline int Interpolator::index_lookup(const std::vector &px, double x) const { +inline int Hermite::index_lookup(const std::vector &px, double x) const { // Clamp coordinates to valid range // double clamped_x = std::max(static_cast(index_lo), std::min(static_cast(index_hi), x)); @@ -526,7 +521,7 @@ inline int Interpolator::index_lookup(const std::vector &px, double x) c -void Interpolator::cspline_init(const std::vector &px, const std::vector &data, +void Hermite::cspline_init(const std::vector &px, const std::vector &data, std::vector &tridiag_solution){ @@ -598,7 +593,7 @@ void Interpolator::cspline_init(const std::vector &px, const std::vector } } -double Interpolator::cspline_eval_deriv(std::vector &px, std::vector &data, +double Hermite::cspline_eval_deriv(std::vector &px, std::vector &data, std::vector &local_tridiag_sol, double value, int length) { // Find the interval containing the evaluation point diff --git a/src/pyvale/dic/cpp/dicinterpolator.hpp b/src/pyvale/dic/cpp/dicinterpHermite.hpp similarity index 81% rename from src/pyvale/dic/cpp/dicinterpolator.hpp rename to src/pyvale/dic/cpp/dicinterpHermite.hpp index 3b1009d7..fe208d02 100644 --- a/src/pyvale/dic/cpp/dicinterpolator.hpp +++ b/src/pyvale/dic/cpp/dicinterpHermite.hpp @@ -7,8 +7,8 @@ -#ifndef DICINTERPOLATOR_H -#define DICINTERPOLATOR_H +#ifndef DICINTERHERMITE_H +#define DICINTERHERMITE_H @@ -16,24 +16,19 @@ #include // Program Header files -/** - * @brief namespace for bicubic spline interpolation. - * - * Based on the implementation by GNU Scientific Library (GSL). - * Main difference is the removal of the binary search for index lookup. - * For use in DIC, we only ever need integer locations and therefore its - * sufficient to get the floor value of the subpixel location. - * - */ -struct InterpVals { - double f; - double dfdx; - double dfdy; -}; +#include "./dicinterp.hpp" + +class Hermite : public Interpolator { -class Interpolator { +private: + + std::vector dx; + std::vector dy; + std::vector dxy; + std::vector px_y; + std::vector px_x; + double *image; -private: /** * @brief Calculates the coefficients for cubic spline interpolation. @@ -90,14 +85,6 @@ class Interpolator { public: - std::vector dx; - std::vector dy; - std::vector dxy; - std::vector px_y; - std::vector px_x; - double *image; - int px_vert; - int px_hori; /** * @brief Initializes the bicubic interpolator with deformed image data. @@ -108,7 +95,7 @@ class Interpolator { * @param px_hori Width of the image in pixels * @param px_vert Height of the image in pixels */ - Interpolator(double * img, int px_hori, int px_vert); + Hermite(double * img, int px_hori, int px_vert); /** * @brief Evaluates the bicubic interpolation at a specified point. @@ -119,7 +106,7 @@ class Interpolator { * @param y The y-coordinate of the interpolation point * @return The interpolated value at (x,y) */ - double eval_bicubic(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const; + double eval(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; /** * @brief Evaluates the x-derivative of bicubic interpolation at a specified point. @@ -130,7 +117,7 @@ class Interpolator { * @param y The y-coordinate of the point * @return The x-derivative of the interpolated function at (x,y) */ - double eval_bicubic_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const; + double eval_dx(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; /** * @brief Evaluates the y-derivative of bicubic interpolation at a specified point. @@ -141,7 +128,7 @@ class Interpolator { * @param y The y-coordinate of the point * @return The y-derivative of the interpolated function at (x,y) */ - double eval_bicubic_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const; + double eval_dy(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; /** * @brief Evaluates the bicubic interpolation and its derivatives at a specified point. @@ -152,10 +139,10 @@ class Interpolator { * @param y The y-coordinate of the point * @return Data struct containing the interpolated value and its x and y derivatives */ - InterpVals eval_bicubic_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const; + InterpVals eval_and_derivs(const int ss_x, const int ss_y, const double subpx_x, const double subpx_y) const override; }; -#endif //DICINTERPOLATOR_H +#endif //DICINTERPHERMITE_H diff --git a/src/pyvale/dic/cpp/dicmain.cpp b/src/pyvale/dic/cpp/dicmain.cpp index 24f7e06c..a730a2e6 100644 --- a/src/pyvale/dic/cpp/dicmain.cpp +++ b/src/pyvale/dic/cpp/dicmain.cpp @@ -7,6 +7,7 @@ // STD library Header files #include +#include #include #include #include @@ -25,7 +26,9 @@ #include "../../common_cpp/util.hpp" // DIC Header files -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" +#include "./dicinterpBspline.hpp" +#include "./dicinterpHermite.hpp" #include "./dicoptimizer.hpp" #include "./dicscanmethod.hpp" #include "./dicutil.hpp" @@ -115,6 +118,7 @@ void DICengine(const py::array_t& img_stack_arr, // pointer to hold the reference interpolator (will be created once) Interpolator* interp_ref = nullptr; + Interpolator* interp_ref_inc = nullptr; // loop over deformed images. They start at index 1 in the stack for (int img_num = 1; img_num < conf.num_def_img+1; img_num++){ @@ -124,13 +128,15 @@ void DICengine(const py::array_t& img_stack_arr, double *img_def = img_stack + img_num*num_px_in_image; // define our interpolator for the reference image - Interpolator interp_def(img_def, conf.px_hori, conf.px_vert); + Interpolator* interp_def = nullptr; + if (conf.interp_routine == "BSPLINE") interp_def = new Bspline(img_def, conf.px_hori, conf.px_vert); + else if (conf.interp_routine == "HERMITE") interp_def = new Hermite(img_def, conf.px_hori, conf.px_vert); // ------------------------------------------------------------------------------------------------------------------------------------------- // raster scan // ------------------------------------------------------------------------------------------------------------------------------------------- if (conf.scan_method=="IMAGE_SCAN") - scanmethod::image(img_ref, interp_def, ss_grids[0], conf, img_num, result_arrays); + scanmethod::image(img_ref, *interp_def, ss_grids[0], conf, img_num, result_arrays); @@ -139,7 +145,7 @@ void DICengine(const py::array_t& img_stack_arr, // multiwindow FFTCC + reliability Guided // ------------------------------------------------------------------------------------------------------------------------------------------- else if (conf.scan_method=="MULTIWINDOW_RG") - scanmethod::multiwindow_reliability_guided(img_ref, img_def, interp_def, ss_grids, conf, img_num, result_arrays); + scanmethod::multiwindow_reliability_guided(img_ref, img_def, *interp_def, ss_grids, conf, img_num, result_arrays); @@ -148,8 +154,9 @@ void DICengine(const py::array_t& img_stack_arr, // singlewindow FFTCC + reliability Guided // ------------------------------------------------------------------------------------------------------------------------------------------- else if (conf.scan_method=="SINGLEWINDOW_RG"){ - if (!interp_ref) interp_ref = new Interpolator(img_ref, conf.px_hori, conf.px_vert); - scanmethod::singlewindow_incremental_reliability_guided(img_ref, img_def, *interp_ref, interp_def, ss_grids, conf, 0, img_num, result_arrays); + if (!interp_ref && conf.interp_routine=="BSPLINE") interp_ref = new Bspline(img_ref, conf.px_hori, conf.px_vert); + if (!interp_ref && conf.interp_routine=="HERMITE") interp_ref = new Hermite(img_ref, conf.px_hori, conf.px_vert); + scanmethod::singlewindow_incremental_reliability_guided(img_ref, img_def, *interp_ref, *interp_def, ss_grids, conf, 0, img_num, result_arrays); } @@ -158,7 +165,7 @@ void DICengine(const py::array_t& img_stack_arr, // multi window FFTCC ONLY // ------------------------------------------------------------------------------------------------------------------------------------------- else if (conf.scan_method=="MULTIWINDOW") - scanmethod::multiwindow(img_ref, img_def, interp_def, ss_grids, conf, img_num, result_arrays); + scanmethod::multiwindow(img_ref, img_def, *interp_def, ss_grids, conf, img_num, result_arrays); @@ -170,8 +177,9 @@ void DICengine(const py::array_t& img_stack_arr, double *img_prev = nullptr; int img_num_prev = img_num-1; img_prev = img_stack + img_num_prev*num_px_in_image; - Interpolator interp_ref_inc(img_prev, conf.px_hori, conf.px_vert); - scanmethod::singlewindow_incremental_reliability_guided(img_prev, img_def, interp_ref_inc, interp_def, ss_grids, conf, img_num_prev, img_num, result_arrays); + if (conf.interp_routine=="BSPLINE") interp_ref_inc = new Bspline(img_prev, conf.px_hori, conf.px_vert); + if (conf.interp_routine=="HERMITE") interp_ref_inc = new Hermite(img_prev, conf.px_hori, conf.px_vert); + scanmethod::singlewindow_incremental_reliability_guided(img_prev, img_def, *interp_ref_inc, *interp_def, ss_grids, conf, img_num_prev, img_num, result_arrays); } @@ -181,6 +189,8 @@ void DICengine(const py::array_t& img_stack_arr, if (!saveconf.at_end) result_arrays.write_to_disk(img_num, saveconf, ss_grids.back(), conf.num_def_img, conf.filenames); + if (interp_def) delete interp_def; + if (stop_request) break; } diff --git a/src/pyvale/dic/cpp/dicoptimizer.cpp b/src/pyvale/dic/cpp/dicoptimizer.cpp index b3d6110b..59a956e4 100644 --- a/src/pyvale/dic/cpp/dicoptimizer.cpp +++ b/src/pyvale/dic/cpp/dicoptimizer.cpp @@ -17,7 +17,7 @@ // Program Header files -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" #include "./dicoptimizer.hpp" #include "./dicshapefunc.hpp" #include "./dicresults.hpp" @@ -158,7 +158,7 @@ namespace optimizer { double def_y = ss_def.y[i]; // get the subset value and derivitives - interp_vals = interp_def.eval_bicubic_and_derivs(global_x, global_y, def_x+global_x, def_y+global_y); + interp_vals = interp_def.eval_and_derivs(global_x, global_y, def_x+global_x, def_y+global_y); ss_def.vals[i] = interp_vals.f; double def = ss_def.vals[i]; @@ -199,7 +199,7 @@ namespace optimizer { opt.costpdp = 0.0; for (int i = 0; i < num_px; ++i) { shapefunc::get_pixel(ss_def.x[i], ss_def.y[i], ss_ref.x[i], ss_ref.y[i], opt.pdp); - ss_def.vals[i] = interp_def.eval_bicubic(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); + ss_def.vals[i] = interp_def.eval(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); opt.costpdp += (ss_ref.vals[i] - ss_def.vals[i]) * (ss_ref.vals[i] - ss_def.vals[i]); } } @@ -240,7 +240,7 @@ namespace optimizer { // apply shape function parameters to deformed subset shapefunc::get_pixel(ss_def.x[i], ss_def.y[i], ss_ref.x[i], ss_ref.y[i], opt.p); - interp_vals = interp_def.eval_bicubic_and_derivs(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); + interp_vals = interp_def.eval_and_derivs(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); ss_def.vals[i] = interp_vals.f; dfdx[i] = interp_vals.dfdx; dfdy[i] = interp_vals.dfdy; @@ -291,7 +291,7 @@ namespace optimizer { sum_squared_def = 0.0; for (int i = 0; i < num_px; ++i) { shapefunc::get_pixel(ss_def.x[i], ss_def.y[i], ss_ref.x[i], ss_ref.y[i], opt.pdp); - ss_def.vals[i] = interp_def.eval_bicubic(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); + ss_def.vals[i] = interp_def.eval(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); sum_squared_def += ss_def.vals[i] * ss_def.vals[i]; } @@ -340,7 +340,7 @@ namespace optimizer { // apply shape function parameters to deformed subset shapefunc::get_pixel(ss_def.x[i], ss_def.y[i], ss_ref.x[i], ss_ref.y[i], opt.p); - interp_vals = interp_def.eval_bicubic_and_derivs(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); + interp_vals = interp_def.eval_and_derivs(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); ss_def.vals[i] = interp_vals.f; dfdx[i] = interp_vals.dfdx; dfdy[i] = interp_vals.dfdy; @@ -403,7 +403,7 @@ namespace optimizer { mean_def = 0.0; for (int i = 0; i < num_px; ++i) { shapefunc::get_pixel(ss_def.x[i], ss_def.y[i], ss_ref.x[i], ss_ref.y[i], opt.pdp); - ss_def.vals[i] = interp_def.eval_bicubic(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); + ss_def.vals[i] = interp_def.eval(global_x, global_y, ss_def.x[i]+global_x, ss_def.y[i]+global_y); mean_def += ss_def.vals[i]; } diff --git a/src/pyvale/dic/cpp/dicoptimizer.hpp b/src/pyvale/dic/cpp/dicoptimizer.hpp index 373c05d2..2908f6e0 100644 --- a/src/pyvale/dic/cpp/dicoptimizer.hpp +++ b/src/pyvale/dic/cpp/dicoptimizer.hpp @@ -13,7 +13,7 @@ // Program Header files #include "./dicresults.hpp" -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" namespace optimizer { diff --git a/src/pyvale/dic/cpp/dicscanmethod.cpp b/src/pyvale/dic/cpp/dicscanmethod.cpp index b6c20b47..12743f13 100644 --- a/src/pyvale/dic/cpp/dicscanmethod.cpp +++ b/src/pyvale/dic/cpp/dicscanmethod.cpp @@ -27,7 +27,7 @@ // Program Header files -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" #include "./dicoptimizer.hpp" #include "./dicutil.hpp" #include "./dicrg.hpp" diff --git a/src/pyvale/dic/cpp/dicsubset.cpp b/src/pyvale/dic/cpp/dicsubset.cpp index 0b9966f2..828cf40c 100644 --- a/src/pyvale/dic/cpp/dicsubset.cpp +++ b/src/pyvale/dic/cpp/dicsubset.cpp @@ -66,7 +66,7 @@ namespace subset { ss_def.y[count] = subpx_y+y; // get pixel values - ss_def.vals[count] = interp_def.eval_bicubic(0, 0, ss_def.x[count], ss_def.y[count]); + ss_def.vals[count] = interp_def.eval(0, 0, ss_def.x[count], ss_def.y[count]); // debugging //std::cout << ss_def.x[count] << " " << ss_def.y[count] << " " << ss_def.vals[count] << std::endl; @@ -105,7 +105,7 @@ namespace subset { shapefunc::get_pixel(ss_def.x[count], ss_def.y[count], subpx_x+x, subpx_y+y, p); // get pixel values from interpolator - ss_def.vals[count] = interp_def.eval_bicubic(0, 0, ss_def.x[count], ss_def.y[count]); + ss_def.vals[count] = interp_def.eval(0, 0, ss_def.x[count], ss_def.y[count]); count++; } diff --git a/src/pyvale/dic/cpp/dicsubset.hpp b/src/pyvale/dic/cpp/dicsubset.hpp index 45bd01aa..dc792cc4 100644 --- a/src/pyvale/dic/cpp/dicsubset.hpp +++ b/src/pyvale/dic/cpp/dicsubset.hpp @@ -11,7 +11,7 @@ #include // Program Header files -#include "./dicinterpolator.hpp" +#include "./dicinterp.hpp" namespace subset { diff --git a/src/pyvale/dic/dicchecks.py b/src/pyvale/dic/dicchecks.py index 6ad8c0be..d0312a1a 100644 --- a/src/pyvale/dic/dicchecks.py +++ b/src/pyvale/dic/dicchecks.py @@ -88,7 +88,7 @@ def check_interpolation(interpolation_routine: str) -> None: Validate that the interpolation routine is one of the allowed methods. Checks whether interpolation_routine is a supported - interpolation method. Allowed values are "BILINEAR" and "BICUBIC". If the input + interpolation method. Allowed values are "BSPLINE" and "HERMITE". If the input is not one of these, a `ValueError` is raised. Parameters @@ -103,7 +103,7 @@ def check_interpolation(interpolation_routine: str) -> None: """ - allowed_values = {"BILINEAR", "BICUBIC"} + allowed_values = {"BSPLINE", "HERMITE"} if interpolation_routine not in allowed_values: raise ValueError(f"Invalid interpolation_routine: " From 9929fe934c94ec3e1e229edcd0997f9dca2148ce Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Fri, 23 Jan 2026 11:00:03 +0000 Subject: [PATCH 02/13] changed default interpolator value. --- src/pyvale/dic/dic2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyvale/dic/dic2d.py b/src/pyvale/dic/dic2d.py index 8800a869..489f38cf 100644 --- a/src/pyvale/dic/dic2d.py +++ b/src/pyvale/dic/dic2d.py @@ -24,7 +24,7 @@ def calculate_2d(reference: np.ndarray | str | Path, subset_step: int = 10, correlation_criteria: str="ZNSSD", shape_function: str="AFFINE", - interpolation_routine: str="BICUBIC", + interpolation_routine: str="BSPLINE", max_iterations: int=40, precision: float=0.001, threshold: float=0.9, From a024de977cacd485c331b30f55ffe0bc8a1dd182 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 27 Jan 2026 08:07:47 +0000 Subject: [PATCH 03/13] updated interpolator in tests. --- tests/dic/test_dic.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/dic/test_dic.py b/tests/dic/test_dic.py index 48c837c7..698a300a 100644 --- a/tests/dic/test_dic.py +++ b/tests/dic/test_dic.py @@ -130,7 +130,6 @@ def test_image_scan_znssd_affine(): subset_step=15, max_displacement=2, correlation_criteria="ZNSSD", - interpolation_routine="BICUBIC", shape_function="AFFINE", method="IMAGE_SCAN", output_basepath=test_dir, @@ -159,7 +158,6 @@ def test_image_scan_znssd_rigid(): subset_step=15, max_displacement=2, correlation_criteria="ZNSSD", - interpolation_routine="BICUBIC", shape_function="RIGID", method="IMAGE_SCAN", output_basepath=test_dir, @@ -188,7 +186,6 @@ def test_image_scan_nssd_affine(): subset_step=15, max_displacement=2, correlation_criteria="NSSD", - interpolation_routine="BICUBIC", shape_function="AFFINE", method="IMAGE_SCAN", output_basepath=test_dir, @@ -217,7 +214,6 @@ def test_rg_znssd_affine(): subset_step=15, max_displacement=2, correlation_criteria="ZNSSD", - interpolation_routine="BICUBIC", shape_function="AFFINE", method="MULTIWINDOW_RG", output_basepath=test_dir, @@ -246,7 +242,6 @@ def test_fft_large(): subset_step=15, max_displacement=100, correlation_criteria="ZNSSD", - interpolation_routine="BICUBIC", shape_function="RIGID", method="MULTIWINDOW", output_basepath=test_dir, From ab908957f092703fbdd1feed2c368f8618d4a436 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 27 Jan 2026 12:55:26 +0000 Subject: [PATCH 04/13] added strain tests. --- tests/strain/test_strain.py | 119 ++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/strain/test_strain.py diff --git a/tests/strain/test_strain.py b/tests/strain/test_strain.py new file mode 100644 index 00000000..ba7bb243 --- /dev/null +++ b/tests/strain/test_strain.py @@ -0,0 +1,119 @@ + + +import numpy as np +import pytest +import scipy.linalg +import os +import glob + +#pyvale stuff +import pyvale.dic as dic +import pyvale.strain as strain + + +def generate_affine_displacement_grid(F, nx=100, ny=100): + """Generate displacement field for a uniform deformation gradient F.""" + x = np.linspace(0, 990, nx) + y = np.linspace(0, 990, ny) + X, Y = np.meshgrid(x, y, indexing="ij") + + Ux = np.zeros((1, nx, ny)) + Uy = np.zeros((1, nx, ny)) + + # create displacement field + for i in range(nx): + for j in range(ny): + pos = np.array([X[i,j], Y[i,j]]) + disp = (F - np.eye(2)) @ pos + Ux[0, i, j] = disp[0] + Uy[0, i, j] = disp[1] + + return X, Y, Ux, Uy + + +def reference_strain(F, formulation): + I = np.eye(2) + C = F.T @ F + B = F @ F.T + U = scipy.linalg.sqrtm(C) + V = scipy.linalg.sqrtm(B) + + if formulation == "GREEN": + return 0.5 * (C - I) + if formulation == "ALMANSI": + return 0.5 * (I - np.linalg.inv(B)) + if formulation == "BIOT_LAGRANGE": + return U - I + if formulation == "BIOT_EULER": + return V - I + if formulation == "HENCKY": + return scipy.linalg.logm(U) + + raise ValueError(formulation) + + +# Strain formulations I've got implemented currently +@pytest.mark.parametrize("strain_formulation", [ + "GREEN", + "ALMANSI", + "BIOT_LAGRANGE", + "BIOT_EULER", + "HENCKY", +]) + +# list of deformation types to check against. +@pytest.mark.parametrize("deformation_type,F", [ + ("uniform_x_stretch", np.array([[1.02, 0], [0, 1.0]])), + ("uniform_y_stretch", np.array([[1.0, 0], [0, 1.03]])), + ("shear_x", np.array([[1.0, 0.05], [0.0, 1.0]])), + ("shear_y", np.array([[1.0, 0.0], [0.05, 1.0]])), + ("stretch_and_shear", np.array([[1.02, 0.03], [0.0, 1.01]])), + ("rotation", np.array([[np.cos(np.pi/12), -np.sin(np.pi/12)], + [np.sin(np.pi/12), np.cos(np.pi/12)]])), +]) + +def test_strain_deformations(strain_formulation, deformation_type, F): + # Generate displacement field + X, Y, Ux, Uy = generate_affine_displacement_grid(F) + + input_data = dic.Results(X, Y, Ux, Uy) + + # Compute strain + strain_results = strain.calculate_2d( + data=input_data, + window_size=5, + window_element=4, + strain_formulation=strain_formulation, + output_prefix=f"strain_{strain_formulation}_{deformation_type}_" + ) + + # Analytic reference strain + expected = reference_strain(F, strain_formulation) + + strainresults = strain.import_2d(f"./strain_{strain_formulation}_{deformation_type}_*.csv") + + # Map of deformation gradient and strain components + checks = { + "def_xx": F[0,0], + "def_xy": F[0,1], + "def_yx": F[1,0], + "def_yy": F[1,1], + "eps_xx": expected[0,0], + "eps_xy": expected[0,1], + "eps_yx": expected[1,0], + "eps_yy": expected[1,1], + } + + # Loop through and assert all + for attr, val in checks.items(): + np.testing.assert_allclose( + getattr(strainresults, attr), + val, + rtol=1e-5, + atol=1e-7, + err_msg=f"{attr} incorrect for {strain_formulation} / {deformation_type}" + ) + + for file_path in glob.glob(f"./strain_{strain_formulation}_{deformation_type}_*.csv"): + os.remove(file_path) + From c361ff71b84e937ba9885e536efe9cf48cc3305b Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 27 Jan 2026 12:56:37 +0000 Subject: [PATCH 05/13] make strain tests run as part of CI. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 56e35344..b322659f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -54,7 +54,7 @@ jobs: - name: Run sensorsim and DIC tests run: | pip install pytest pytest-mock - pytest -v tests/dic/. tests/sensorsim/. + pytest -v tests/dic/. tests/sensorsim/. tests/strain/. - name: Run blender tests (skip macOS ARM) if: ${{ !startsWith(matrix.os, 'macos') || contains(matrix.os, 'intel') }} From 9f7ac105271952b5b0ecbed9a8de7e33e537d717 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 27 Jan 2026 13:01:37 +0000 Subject: [PATCH 06/13] made some arguments in dicresults dataclass optional. --- src/pyvale/dic/dicresults.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pyvale/dic/dicresults.py b/src/pyvale/dic/dicresults.py index 3746b5e6..50d77895 100644 --- a/src/pyvale/dic/dicresults.py +++ b/src/pyvale/dic/dicresults.py @@ -26,21 +26,21 @@ class Results: Horizontal displacements at each subset location. shape=(img_num,y,x) v : np.ndarray Vertical displacements at each subset location. shape=(img_num,y,x) - mag : np.ndarray + mag : np.ndarray | None Displacement magnitude at each subset location, typically computed as sqrt(u^2 + v^2). shape=(img_num,y,x) - converged : np.ndarray + converged : np.ndarray | None boolean value for whether the subset has converged or not. shape=(img_num,y,x) - cost : np.ndarray + cost : np.ndarray | None Final cost or residual value from the correlation optimization (e.g., ZNSSD). shape=(img_num,y,x) - ftol : np.ndarray + ftol : np.ndarray | None Final `ftol` value from the optimization routine, indicating function tolerance. shape=(img_num,y,x) - xtol : np.ndarray + xtol : np.ndarray | None Final `xtol` value from the optimization routine, indicating solution tolerance. shape=(img_num,y,x) - niter : np.ndarray + niter : np.ndarray | None Number of iterations taken to converge for each subset point. shape=(img_num,y,x) shape_params : np.ndarray | None Optional shape parameters if output during DIC calculation (e.g., affine, rigid). shape=(img_num,y,x) - filenames : list[str] + filenames : list[str] | None name of DIC result files that have been found """ @@ -48,11 +48,11 @@ class Results: ss_y: np.ndarray u: np.ndarray v: np.ndarray - mag: np.ndarray - converged: np.ndarray - cost: np.ndarray - ftol: np.ndarray - xtol: np.ndarray - niter: np.ndarray - shape_params: np.ndarray - filenames: list[str] + mag: np.ndarray | None = None + converged: np.ndarray | None = None + cost: np.ndarray | None = None + ftol: np.ndarray | None = None + xtol: np.ndarray | None = None + niter: np.ndarray | None = None + shape_params: np.ndarray | None = None + filenames: list[str] | None = None From 8c49b2b41b8c29ca9884f4638eaca72583a0516b Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 27 Jan 2026 13:03:14 +0000 Subject: [PATCH 07/13] update to allow dicresults to be passed directly to strain calculation. --- src/pyvale/strain/strain.py | 57 ++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/pyvale/strain/strain.py b/src/pyvale/strain/strain.py index 9c1ae142..ed969553 100644 --- a/src/pyvale/strain/strain.py +++ b/src/pyvale/strain/strain.py @@ -10,11 +10,12 @@ from pyvale.strain.strainresults import StrainResults from pyvale.strain.strainchecks import check_strain_files from pyvale.dic.dicdataimport import import_2d +from pyvale.dic.dicresults import Results as dicResults from pyvale.common_py.util import check_output_directory import pyvale.strain.strain_cpp as strain_cpp import pyvale.common_cpp.common_cpp as common_cpp -def calculate_2d(data: str | Path, +def calculate_2d(data: dicResults | str | Path, window_size: int=5, window_element: int=9, input_binary: bool=False, @@ -33,15 +34,18 @@ def calculate_2d(data: str | Path, Parameters ---------- - data : pathlib.Path or str - A pathlib.Path or str to files from which the data should be imported. + data : dic.Results, pathlib.Path or str + input data can either be a dic.Results object or pathlib.Path / str if importing data + straight from a file input_delimiter: str - delimiter used for the input dic results files (default: ","). + delimiter used for the input dic results files if using + pathlib.Path or str for data import (default: ","). input_binary bool: - whether input data is in human-readable or binary format (default: - False). + whether input data is in human-readable or binary format if using + pathlib.Path or str for data import (default: False). window_size : int, optional - The size of the local window over which to compute strain (must be odd), by default 5. + The size of the local window over which to compute strain (must be odd, + default: 5). window_element : int, optional The type of finite element shape function used in the strain window: 4 (bilinear) or 9 (biquadratic), by default 4. @@ -79,16 +83,35 @@ def calculate_2d(data: str | Path, if window_size % 2 == 0: raise ValueError(f"Invalid strain window size: '{window_size}'. Must be an odd number.") - filenames = check_strain_files(strain_files=data) - # Load data if a file path is given - results = import_2d(layout="matrix", data=str(data), - binary=input_binary, delimiter=input_delimiter) + if isinstance(data, (str, Path)): + filenames = check_strain_files(strain_files=data) + + # Load data if a file path is given + dicresults = import_2d(layout="matrix", data=str(data), + binary=input_binary, delimiter=input_delimiter) + + elif isinstance(data, dicResults): + dicresults = data + print(dicresults.ss_x.shape, dicresults.ss_y.shape, dicresults.u.shape,dicresults.v.shape) + assert dicresults.ss_x.ndim == 2 and dicresults.ss_y.ndim == 2, "ss_x and ss_y must be 2D" + assert dicresults.ss_x.shape == dicresults.ss_y.shape, "ss_x and ss_y must have the same shape" + assert dicresults.u.ndim == 3 and dicresults.v.ndim == 3, "u and v must be 3D" + assert dicresults.u.shape == dicresults.v.shape, "u and v must have the same shape" + assert dicresults.u.shape[1:] == dicresults.ss_x.shape, "Spatial dimensions of u must match ss_x" + + # need to make dummy filenames + filenames = [] + for f in range(0,dicresults.u.shape[0]): + filenames.append(f"strain_data_{f:04d}") + + else: + raise TypeError(f"Unexpected displacement data type: {type(data)}") # Extract dimensions from the validated object - nss_x = results.ss_x.shape[1] - nss_y = results.ss_y.shape[0] - nimg = results.u.shape[0] + nss_x = dicresults.ss_x.shape[1] + nss_y = dicresults.ss_x.shape[0] + nimg = dicresults.u.shape[0] check_output_directory(str(output_basepath), output_prefix, 0) @@ -101,11 +124,11 @@ def calculate_2d(data: str | Path, strain_save_conf.delimiter = output_delimiter strain_save_conf.at_end = output_at_end - print(filenames) + print(type(filenames)) # Call to C++ backend - strain_cpp.strain_engine(results.ss_x, results.ss_y, - results.u, results.v, + strain_cpp.strain_engine(dicresults.ss_x, dicresults.ss_y, + dicresults.u, dicresults.v, nss_x, nss_y, nimg, window_size, window_element, strain_formulation, filenames, From 1eca184c15e3709d96a5c30b36b82d71f756fb0d Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 08:09:32 +0000 Subject: [PATCH 08/13] small fix for error message displaying incorrect allowed values. --- src/pyvale/dic/dicchecks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyvale/dic/dicchecks.py b/src/pyvale/dic/dicchecks.py index d0312a1a..0b402a12 100644 --- a/src/pyvale/dic/dicchecks.py +++ b/src/pyvale/dic/dicchecks.py @@ -94,7 +94,7 @@ def check_interpolation(interpolation_routine: str) -> None: Parameters ---------- interpolation_routine : str - The interpolation method to validate. Must be either "BILINEAR" or "BICUBIC". + The interpolation method to validate. Must be either "BSPLINE" or "HERMITE". Raises ------ From 672ce57e311ccac00825e5c40b9491418eddbc30 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 08:16:06 +0000 Subject: [PATCH 09/13] no need to build Eigen tests or Fortran stuff. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c778afa..4ca0d935 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ find_package(OpenMP REQUIRED) # Eigen3 # ---------------------------- find_package(Eigen3 3.4 QUIET) +set(EIGEN_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(EIGEN_TEST_NO_FORTRAN ON CACHE BOOL "" FORCE) +set(BUILD_TESTING OFF CACHE BOOL "" FORCE) if (Eigen3_FOUND) message(STATUS "Using system-installed Eigen3") From ded9c9f376696124e12869297eac45b48f60c12b Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 08:40:48 +0000 Subject: [PATCH 10/13] second attempt to turn off Eigen tests... --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ca0d935..2daceeb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,8 @@ find_package(OpenMP REQUIRED) # Eigen3 # ---------------------------- find_package(Eigen3 3.4 QUIET) -set(EIGEN_BUILD_TESTING OFF CACHE BOOL "" FORCE) -set(EIGEN_TEST_NO_FORTRAN ON CACHE BOOL "" FORCE) -set(BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(EIGEN_TEST_NOQT ON) +set(BUILD_TESTING OFF) if (Eigen3_FOUND) message(STATUS "Using system-installed Eigen3") From a95c2b677d24946993dd9c1cd0344997ec033a19 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 08:59:55 +0000 Subject: [PATCH 11/13] conda windows-latest fix --- .github/workflows/tests.yml | 1 + CMakeLists.txt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85787302..0d503ec1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -71,6 +71,7 @@ jobs: if: matrix.env-type == 'conda' run: | conda install -y pip + conda install conda-forge::fortran-compiler # fix to stop windows-latest timing out when searching for a fortran compiler... pip install -e . -v pip install pytest pytest-mock diff --git a/CMakeLists.txt b/CMakeLists.txt index 2daceeb9..1c778afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,6 @@ find_package(OpenMP REQUIRED) # Eigen3 # ---------------------------- find_package(Eigen3 3.4 QUIET) -set(EIGEN_TEST_NOQT ON) -set(BUILD_TESTING OFF) if (Eigen3_FOUND) message(STATUS "Using system-installed Eigen3") From e801e4316081318721f19124dec068c07c5d2d5c Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 09:30:48 +0000 Subject: [PATCH 12/13] conda windows-latest fix attempt 2 --- .github/workflows/tests.yml | 1 - CMakeLists.txt | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d503ec1..85787302 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -71,7 +71,6 @@ jobs: if: matrix.env-type == 'conda' run: | conda install -y pip - conda install conda-forge::fortran-compiler # fix to stop windows-latest timing out when searching for a fortran compiler... pip install -e . -v pip install pytest pytest-mock diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c778afa..a8f2f605 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,10 @@ if (Eigen3_FOUND) else() message(STATUS "System Eigen3 not found. fetching with FetchContent") include(FetchContent) + set(EIGEN_BUILD_TESTING OFF CACHE BOOL "" FORCE) + set(EIGEN_TEST_NO_FORTRAN ON CACHE BOOL "" FORCE) + set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + set(EIGEN_TEST_NOQT ON CACHE BOOL "" FORCE) FetchContent_Declare( eigen From bfa3a438c35d331b45c212b9a7922c81f26a5b05 Mon Sep 17 00:00:00 2001 From: JoelPhys Date: Tue, 3 Feb 2026 10:13:49 +0000 Subject: [PATCH 13/13] removed uv and conda from tests. Pointless. --- .github/workflows/tests.yml | 27 +-------------------------- CMakeLists.txt | 4 ---- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85787302..039b6a35 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-14, macos-15-intel, macos-latest] - env-type: [pip, uv, conda] + env-type: [pip] include: - os: macos-14 llvm: llvm@15 @@ -30,18 +30,6 @@ jobs: with: python-version: '3.11' - - name: Install uv - if: matrix.env-type == 'uv' - uses: astral-sh/setup-uv@v4 - - - name: Set up Miniconda - if: matrix.env-type == 'conda' - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - python-version: '3.11' - activate-environment: test-env - - name: Set default compiler on macOS to GCC if: startsWith(matrix.os, 'macos') run: | @@ -61,19 +49,6 @@ jobs: pip install -e . -v pip install pytest pytest-mock - - name: Install with uv - if: matrix.env-type == 'uv' - run: | - uv pip install --system -e . -v - uv pip install --system pytest pytest-mock - - - name: Install with conda - if: matrix.env-type == 'conda' - run: | - conda install -y pip - pip install -e . -v - pip install pytest pytest-mock - - name: Smoke test run: python -c "import pyvale" diff --git a/CMakeLists.txt b/CMakeLists.txt index a8f2f605..1c778afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,10 +43,6 @@ if (Eigen3_FOUND) else() message(STATUS "System Eigen3 not found. fetching with FetchContent") include(FetchContent) - set(EIGEN_BUILD_TESTING OFF CACHE BOOL "" FORCE) - set(EIGEN_TEST_NO_FORTRAN ON CACHE BOOL "" FORCE) - set(BUILD_TESTING OFF CACHE BOOL "" FORCE) - set(EIGEN_TEST_NOQT ON CACHE BOOL "" FORCE) FetchContent_Declare( eigen