diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3f80a2ed..9218e2e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,10 +12,10 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-14, macos-26] + os: [ubuntu-latest, windows-latest, macos-15, macos-26] env-type: [pip] include: - - os: macos-14 + - os: macos-15 llvm: llvm@15 - os: macos-26 llvm: llvm@20 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e4ff0fd2..9a18ae66 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -15,11 +15,11 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-14, macos-26] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-15, macos-26] include: - - os: macos-14 + - os: macos-15 llvm: llvm@15 - macos_deployment_target: "14.0" + macos_deployment_target: "15.0" - os: macos-26 llvm: llvm@20 macos_deployment_target: "26.0" diff --git a/.github/workflows/wheels_testpypi.yml b/.github/workflows/wheels_testpypi.yml index 9ebfbaba..47abcf8d 100644 --- a/.github/workflows/wheels_testpypi.yml +++ b/.github/workflows/wheels_testpypi.yml @@ -9,9 +9,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-14, macos-26] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-15, macos-26] include: - - os: macos-14 + - os: macos-15 llvm: llvm@15 - os: macos-26 llvm: llvm@20 diff --git a/pyproject.toml b/pyproject.toml index dc0d4b99..b468c7e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "matplotlib>=3.8", "pyvista>=0.43.3", "vtk>=9.5.2", - "netCDF4>=1.6.5", + "netCDF4==1.7.5", "sympy>=1.13.0", "PyQT6>=6.7.1", "imageio>=2.36.1", diff --git a/src/pyvale/dic/_dicchecks.py b/src/pyvale/dic/_dicchecks.py index 2bd2be4f..11fdab3f 100644 --- a/src/pyvale/dic/_dicchecks.py +++ b/src/pyvale/dic/_dicchecks.py @@ -87,9 +87,17 @@ def _multiwindow_init(subset_size: int, f"{multiwindow_search_area[i]} at index {i}") overlap = [x * (1.0-multiwindow_overlap) for x in multiwindow_subset_size] + configured_overlap = list(map(int, overlap)) overlap.append(subset_step) overlap = list(map(int,overlap)) + # Avoid running the same final FFT level twice when size and overlap are identical + if (multiwindow_subset_size and + multiwindow_subset_size[-1] == subset_size and + multiwindow_search_area[-1] == subset_size and + configured_overlap[-1] == subset_step): + return configured_overlap, multiwindow_subset_size, multiwindow_search_area + multiwindow_subset_size.append(subset_size) multiwindow_search_area.append(subset_size) @@ -245,7 +253,9 @@ def _check_thresholds(threshold: float, raise ValueError("Optimizer precision must be a float strictly " "between 0 and 1.") -def _check_subsets(subset_size: int, subset_step: int) -> None: +def _check_subsets(subset_size: int, + subset_step: int, + method: str | None = None) -> None: """ Parameters @@ -254,6 +264,9 @@ def _check_subsets(subset_size: int, subset_step: int) -> None: Threshold for the Levenberg optimization method. subset_step : int Threshold for the brute-force optimization method. + method : str, optional + Scan method. Even subset sizes are supported by the FFT-only + ``MULTIWINDOW`` method, but remain disallowed for the other methods. Raises ------ @@ -262,8 +275,8 @@ def _check_subsets(subset_size: int, subset_step: int) -> None: """ - # Enforce scalar types for non-FFT methods - if subset_size % 2 == 0: + # The FFT-only multiwindow path handles even-sized subsets explicitly. + if subset_size % 2 == 0 and method != "MULTIWINDOW": raise ValueError("subset_size must be an odd number.") # check if subset_step is larger than the subset_size diff --git a/src/pyvale/dic/dic2d.py b/src/pyvale/dic/dic2d.py index eb9da30d..383731d1 100644 --- a/src/pyvale/dic/dic2d.py +++ b/src/pyvale/dic/dic2d.py @@ -232,7 +232,7 @@ def calculate_2d(reference: np.ndarray | str | Path, dicchecks._check_thresholds(threshold, precision) common_util.check_output_directory(str(output_basepath), output_prefix, print_level) - dicchecks._check_subsets(subset_size, subset_step) + dicchecks._check_subsets(subset_size, subset_step, method_enum.value) updated_seeds = dicchecks._check_and_update_rg_seed(seed, roi_mask, method_enum.value, w, h, subset_size, subset_step) num_params = dicchecks._check_shape_function(shape_function_enum) diff --git a/src/pyvale/dic/dic3d.py b/src/pyvale/dic/dic3d.py index 762ea576..9b82a9c2 100644 --- a/src/pyvale/dic/dic3d.py +++ b/src/pyvale/dic/dic3d.py @@ -130,7 +130,9 @@ def calculate_3d(reference: list[np.ndarray] | list[str] | list[Path], the existing flat format ``[x0, y0, x1, y1, ...]`` or tuple format ``[(x0, y0), (x1, y1), ...]``. If the method is not RG, this will be ignored. subset_size : int, optional - Size of the square subset window in pixels (default: 21). + Size of the square subset window in pixels (default: 21). Must be odd + except for the FFT-only ``"MULTIWINDOW"`` method, which also supports + even sizes. subset_step : int, optional Step size between subset centers in pixels (default: 10). correlation_criteria : str, optional @@ -323,7 +325,7 @@ def calculate_3d(reference: list[np.ndarray] | list[str] | list[Path], # checks on the config dicchecks._check_thresholds(threshold, precision) common_util.check_output_directory(str(output_basepath), output_prefix, print_level) - dicchecks._check_subsets(subset_size, subset_step) + dicchecks._check_subsets(subset_size, subset_step, method_enum.value) updated_seeds = dicchecks._check_and_update_rg_seed(seed, roi_mask, method_enum.value, w0, h0, subset_size, subset_step) num_params = dicchecks._check_shape_function(shape_function_enum) diff --git a/tests/dic/test_dic.py b/tests/dic/test_dic.py index 4070a6f1..8a261287 100644 --- a/tests/dic/test_dic.py +++ b/tests/dic/test_dic.py @@ -479,8 +479,8 @@ def test_2d_multiwindow_fft_large(dic_temp_dir: Path) -> None: deformed=def_large, roi_mask=roi_rigid.mask, seed=seed, - subset_size=31, - subset_step=15, + subset_size=32, + subset_step=16, max_displacement=100, correlation_criteria="ZNSSD", shape_function="RIGID",