Skip to content

GPU S2N validation has shape mismatch and inverted threshold logic #31

@DumbledoreCalrissian

Description

@DumbledoreCalrissian

Summary

openpiv.gpu.process.gpu_piv(..., validation_method="s2n") fails with a PyCUDA shape mismatch during invalid-vector replacement.

Additionally, the GPU S2N validation logic appears to invalidate high-S2N vectors instead of low-S2N vectors.

This makes pure S2N validation unusable and may make combined validation modes such as {"median_velocity", "s2n"} misleading.

Environment

  • Package: openpiv-python-gpu
  • Relevant files:
    • openpiv/gpu/process.py
    • openpiv/gpu/validation.py
  • Python: 3.11
  • Backend: PyCUDA

Reproduction

from openpiv.gpu import process

params = {
    "window_size_iters": ((32, 2), (16, 1)),
    "overlap_ratio": 0.5,
    "dt": 0.02,
    "mask": mask,
    "deform": True,
    "smooth": True,
    "num_validation_iters": 2,
    "validation_method": "s2n",
    "s2n_tol": 1.3,
    "return_s2n": True,
}

x, y, u, v, mask_out, s2n = process.gpu_piv(
    frame_a,
    frame_b,
    **params
)

Actual Behavior

Pure S2N validation fails with:

ValueError: shapes do not match

File ".../openpiv/gpu/process.py", line 1203, in _replace_invalid_vectors
    u, v = self._validation_gpu.replace_vectors(*self._validation_gpu.median)

File ".../openpiv/gpu/validation.py", line 200, in <listcomp>
    gpuarray.if_positive(self.val_locations, f_replace[i], self._f[i])

File ".../pycuda/gpuarray.py", line 1965, in if_positive
    raise ValueError("shapes do not match")

Observed behavior:

validation_method="median_velocity"           # OK
validation_method="mean_velocity"             # OK
validation_method="rms_velocity"              # OK
validation_method="s2n"                       # crashes
validation_method={"median_velocity","s2n"}   # runs, but behavior suspicious

Suspected Cause 1: S2N Shape Mismatch

In process.py:

if "s2n" in self.validation_method:
    s2n_ratio = self._corr_gpu.s2n_ratio

self._corr_gpu.s2n_ratio appears to be flattened. If S2N validation runs first, _local_validation() creates val_locations with the flattened shape, but replace_vectors() later expects 2D velocity-field shapes.

The existing PIV.s2n_ratio property already reshapes correctly:

shape = self._piv_fields[-1].shape
return self._corr_gpu.s2n_ratio.reshape(shape)

Possible fix:

if "s2n" in self.validation_method:
    s2n_ratio = self._corr_gpu.s2n_ratio.reshape(u.shape)

Suspected Cause 2: S2N Threshold Logic Appears Inverted

In process.py:

s2n_ratio = cumath.log10(corr_peak1 / corr_peak2)

In validation.py:

s2n_tol = log10(self.s2n_tol)
sig2noise_tol = s2n_ratio / DTYPE_f(s2n_tol)

self.val_locations = _local_validation(
    sig2noise_tol,
    1,
    self.val_locations,
)

_local_validation() marks vectors invalid where:

f[t_idx] > tol

This invalidates vectors when:

s2n_ratio > log10(s2n_tol)

which corresponds to:

actual_s2n > s2n_tol

That appears backwards.

The CPU implementation instead does:

ind = s2n < threshold

Expected Behavior

  • validation_method="s2n" should not crash.
  • Low-S2N vectors should be invalidated.
  • High-S2N vectors should remain valid.

Proposed Fix

def _s2n_validation(self, s2n_ratio):
    if s2n_ratio is None:
        return

    s2n_tol = log10(self.s2n_tol)

    self.val_locations = _local_validation(
        DTYPE_f(s2n_tol) - s2n_ratio,
        0,
        self.val_locations,
    )

Additional Notes

Combined validation such as:

validation_method = {"median_velocity", "s2n"}

does not crash, likely because median validation initializes val_locations with the correct 2D shape first.

However, if the threshold logic is inverted, combined validation may still invalidate the wrong vectors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions