add acf and psdf nodes

This commit is contained in:
2026-03-27 23:11:04 -07:00
parent ab71688e01
commit 61d7b0fdcc
8 changed files with 325 additions and 81 deletions

View File

@@ -2316,7 +2316,7 @@ def test_line_cursors():
# =========================================================================
# Analysis — FFT2D
# Analysis — FFT2D / ACF / PSDF
# =========================================================================
def test_fft2d():
@@ -2374,6 +2374,86 @@ def test_fft2d():
print(" PASS\n")
def test_acf():
print("=== Test: ACF ===")
from backend.nodes.acf import ACF
node = ACF()
data = np.array([
[1.0, 2.0, 3.0, 4.0],
[5.0, 6.0, 7.0, 8.0],
[2.0, 1.0, 0.0, -1.0],
[0.0, 1.0, 2.0, 3.0],
], dtype=np.float64)
field = DataField(data=data, xreal=8.0, yreal=4.0, si_unit_xy="nm", si_unit_z="V")
acf, = node.process(field, level="none")
assert acf.data.shape == (3, 3)
assert acf.domain == "spatial"
assert acf.si_unit_xy == "nm"
assert acf.si_unit_z == "V^2"
assert np.isclose(acf.xreal, 6.0)
assert np.isclose(acf.yreal, 3.0)
assert np.isclose(acf.xoff, -3.0)
assert np.isclose(acf.yoff, -1.5)
expected = np.zeros((3, 3), dtype=np.float64)
for iy, dy in enumerate(range(-1, 2)):
for ix, dx in enumerate(range(-1, 2)):
y0a = max(0, dy)
y1a = min(data.shape[0], data.shape[0] + dy)
x0a = max(0, dx)
x1a = min(data.shape[1], data.shape[1] + dx)
lhs = data[y0a:y1a, x0a:x1a]
rhs = data[y0a - dy:y1a - dy, x0a - dx:x1a - dx]
expected[iy, ix] = float(np.mean(lhs * rhs))
assert np.allclose(acf.data, expected)
assert np.allclose(acf.data, acf.data[::-1, ::-1])
print(" PASS\n")
def test_psdf_node():
print("=== Test: PSDF ===")
from backend.nodes.fft_2d import FFT2D
from backend.nodes.psdf import PSDF
field = DataField(
data=np.random.default_rng(17).standard_normal((64, 64)),
xreal=2.0e-6,
yreal=1.0e-6,
si_unit_xy="m",
si_unit_z="nm",
)
fft_node = FFT2D()
psdf_node = PSDF()
fft_psdf = fft_node.process(field, windowing="hann", level="plane")[3]
psdf, = psdf_node.process(field, windowing="hann", level="plane")
assert np.allclose(psdf.data, fft_psdf.data)
assert psdf.data.shape == field.data.shape
assert psdf.domain == "frequency"
assert psdf.si_unit_xy == "1/m"
assert psdf.si_unit_z == "nm^2 m^2"
assert np.all(psdf.data >= 0.0)
white = DataField(
data=np.random.default_rng(123).standard_normal((128, 128)),
xreal=1.0e-6,
yreal=1.0e-6,
si_unit_xy="m",
si_unit_z="m",
)
psdf_white, = psdf_node.process(white, windowing="none", level="none")
variance = float(np.var(white.data))
dk_x = psdf_white.xreal / psdf_white.xres
dk_y = psdf_white.yreal / psdf_white.yres
integral = float(np.sum(psdf_white.data) * dk_x * dk_y)
assert 0.8 < integral / variance < 1.2
print(" PASS\n")
# =========================================================================
# Analysis — Stats
# =========================================================================