Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 2 additions & 27 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand All @@ -61,25 +49,12 @@ 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"

- name: Run sensorsim and DIC tests
shell: bash -el {0}
run: pytest -v tests/dic/. tests/sensorsim/.
run: pytest -v tests/dic/. tests/sensorsim/. tests/strain/.

- name: Run blender tests (skip macOS ARM)
if: ${{ !startsWith(matrix.os, 'macos') || contains(matrix.os, 'intel') }}
Expand Down
2 changes: 1 addition & 1 deletion src/pyvale/dic/cpp/dicfourier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// DIC Header files
#include "dicfourier.hpp"
#include "dicsubset.hpp"
#include "dicinterpolator.hpp"
#include "dicinterp.hpp"

namespace fourier {

Expand Down
2 changes: 1 addition & 1 deletion src/pyvale/dic/cpp/dicfourier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#include <Eigen/Dense>

// DIC Header files
#include "./dicinterpolator.hpp"
#include "./dicinterp.hpp"
#include "./dicsubset.hpp"
#include "./dicutil.hpp"

Expand Down
97 changes: 97 additions & 0 deletions src/pyvale/dic/cpp/dicinterp.hpp
Original file line number Diff line number Diff line change
@@ -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




201 changes: 201 additions & 0 deletions src/pyvale/dic/cpp/dicinterpBspline.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// ================================================================================
// pyvale: the python validation engine
// License: MIT
// Copyright (C) 2025 The Computer Aided Validation Team
// ================================================================================



#include <vector>
#include <cmath>
#include <algorithm>

#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;
}
Loading
Loading