simplify tests

This commit is contained in:
2026-04-04 23:14:17 -07:00
parent 2d9e1a1ecf
commit b8d5c11ee9
5 changed files with 14 additions and 128 deletions

View File

@@ -1,14 +0,0 @@
def test_coordinate():
from backend.nodes.coordinate import Coordinate
node = Coordinate()
result = node.process(x=0.3, y=0.7)
assert len(result) == 1
assert result[0] == (0.3, 0.7)
result_zero = node.process(x=0.0, y=0.0)
assert result_zero[0] == (0.0, 0.0)
result_one = node.process(x=1.0, y=1.0)
assert result_one[0] == (1.0, 1.0)

View File

@@ -1,10 +0,0 @@
def test_number():
from backend.nodes.number import Number
node = Number()
result = node.process(value=1.25)
assert result == (1.25,)
result_neg = node.process(value=-3.5)
assert result_neg == (-3.5,)

View File

@@ -10,7 +10,6 @@ import numpy as np
sys.path.insert(0, ".")
from backend.data_types import DataField
from backend.nodes.fft_2d import FFT2D
from backend.nodes.fft_2d_inverse import FFT2DInverse
def make_field(data, xreal=1e-6, yreal=1e-6):
@@ -247,91 +246,6 @@ def test_log_magnitude_visual_range():
print(" PASS\n")
def test_inverse_fft_reconstructs_from_magnitude_and_phase():
"""Magnitude + phase from FFT2D should reconstruct the original image."""
print("=== Test: Inverse FFT from magnitude + phase ===")
rng = np.random.default_rng(123)
data = rng.standard_normal((64, 96))
field = make_field(data, xreal=2.4e-6, yreal=1.6e-6)
fft_node = FFT2D()
ifft_node = FFT2DInverse()
_, magnitude, phase, _ = fft_node.process(field, windowing="none", level="none")
reconstructed, = ifft_node.process(magnitude, representation="magnitude", phase=phase)
max_err = np.max(np.abs(reconstructed.data - field.data))
print(f" Reconstruction max error: {max_err:.3e}")
assert reconstructed.domain == "spatial"
assert reconstructed.data.shape == field.data.shape
assert np.isclose(reconstructed.xreal, field.xreal)
assert np.isclose(reconstructed.yreal, field.yreal)
assert max_err < 1e-9, f"Expected near-exact reconstruction, got {max_err}"
print(" PASS\n")
def test_inverse_fft_reconstructs_from_log_magnitude_and_phase():
"""log(|F|) + phase should also reconstruct after expm1 inversion."""
print("=== Test: Inverse FFT from log magnitude + phase ===")
y, x = np.mgrid[0:72, 0:80] / 80.0
data = (
0.8 * np.sin(2 * np.pi * 6 * x)
+ 0.35 * np.cos(2 * np.pi * 9 * y)
+ 0.15 * np.sin(2 * np.pi * (4 * x + 3 * y))
)
field = make_field(data, xreal=1.6e-6, yreal=1.44e-6)
fft_node = FFT2D()
ifft_node = FFT2DInverse()
log_magnitude, _, phase, _ = fft_node.process(field, windowing="none", level="none")
reconstructed, = ifft_node.process(log_magnitude, representation="log_magnitude", phase=phase)
rms_err = np.sqrt(np.mean((reconstructed.data - field.data) ** 2))
print(f" Reconstruction RMS error: {rms_err:.3e}")
assert rms_err < 1e-9, f"Expected near-exact reconstruction, got {rms_err}"
print(" PASS\n")
def test_inverse_fft_reconstructs_from_psdf_and_phase():
"""PSDF + phase should reconstruct after undoing PSDF scaling."""
print("=== Test: Inverse FFT from PSDF + phase ===")
rng = np.random.default_rng(321)
data = rng.standard_normal((48, 64))
field = make_field(data, xreal=3.2e-6, yreal=2.4e-6)
fft_node = FFT2D()
ifft_node = FFT2DInverse()
_, _, phase, psdf = fft_node.process(field, windowing="none", level="none")
reconstructed, = ifft_node.process(psdf, representation="psdf", phase=phase)
max_err = np.max(np.abs(reconstructed.data - field.data))
print(f" Reconstruction max error: {max_err:.3e}")
assert reconstructed.si_unit_z == field.si_unit_z
assert max_err < 1e-8, f"Expected near-exact reconstruction, got {max_err}"
print(" PASS\n")
def test_inverse_fft_zero_phase_mode_returns_valid_image():
"""Spectrum-only inversion should return a finite spatial image with the right shape."""
print("=== Test: Inverse FFT zero-phase mode ===")
data = np.sin(2 * np.pi * 5 * np.mgrid[0:64, 0:64][1] / 64.0)
field = make_field(data, xreal=1e-6, yreal=1e-6)
fft_node = FFT2D()
ifft_node = FFT2DInverse()
_, magnitude, _, _ = fft_node.process(field, windowing="none", level="none")
reconstructed, = ifft_node.process(magnitude, representation="magnitude")
print(f" Output shape: {reconstructed.data.shape}")
assert reconstructed.domain == "spatial"
assert reconstructed.data.shape == field.data.shape
assert np.all(np.isfinite(reconstructed.data))
print(" PASS\n")
if __name__ == "__main__":
test_dc_removal()
test_single_frequency()
@@ -341,8 +255,4 @@ if __name__ == "__main__":
test_plane_subtraction()
test_non_square()
test_log_magnitude_visual_range()
test_inverse_fft_reconstructs_from_magnitude_and_phase()
test_inverse_fft_reconstructs_from_log_magnitude_and_phase()
test_inverse_fft_reconstructs_from_psdf_and_phase()
test_inverse_fft_zero_phase_mode_returns_valid_image()
print("All tests passed!")

View File

@@ -1,98 +0,0 @@
"""
Generate test images and their FFT outputs for visual comparison with Gwyddion.
Saves PNG files to tests/output/.
Run: .venv/bin/python -m tests.test_fft_visual
"""
import sys
import os
import numpy as np
sys.path.insert(0, ".")
from backend.data_types import DataField, datafield_to_uint8, encode_preview
from backend.nodes.fft_2d import FFT2D
OUT_DIR = os.path.join(os.path.dirname(__file__), "output")
os.makedirs(OUT_DIR, exist_ok=True)
def save_field(field, name, colormap="viridis"):
"""Save a DataField as a PNG for visual inspection."""
from PIL import Image
arr = datafield_to_uint8(field, colormap)
img = Image.fromarray(arr)
path = os.path.join(OUT_DIR, f"{name}.png")
img.save(path)
print(f" Saved {path} (range: [{field.data.min():.4g}, {field.data.max():.4g}])")
def make_field(data, xreal=1e-6, yreal=1e-6):
return DataField(data=data, xreal=xreal, yreal=yreal)
def main():
node = FFT2D()
N = 256
# --- Test 1: Multi-frequency sine waves ---
print("Test 1: Multi-frequency sine waves")
y, x = np.mgrid[0:N, 0:N] / N
data = (np.sin(2 * np.pi * 10 * x)
+ 0.7 * np.sin(2 * np.pi * 25 * y)
+ 0.3 * np.sin(2 * np.pi * (15 * x + 8 * y)))
field = make_field(data)
save_field(field, "01_sines_input")
log_magnitude, magnitude, _, psdf = node.process(field, windowing="hann", level="mean")
save_field(log_magnitude, "01_sines_log_magnitude")
save_field(magnitude, "01_sines_magnitude")
save_field(psdf, "01_sines_psdf")
# --- Test 2: Real-world-like surface with noise + tilt ---
print("\nTest 2: Tilted surface with features")
rng = np.random.default_rng(42)
data = (50 * x + 30 * y # tilt
+ np.sin(2 * np.pi * 20 * x) # periodic feature
+ 0.5 * rng.standard_normal((N, N))) # noise
field = make_field(data)
save_field(field, "02_surface_input")
for level_mode in ["none", "mean", "plane"]:
result, _, _, _ = node.process(field, windowing="hann", level=level_mode)
save_field(result, f"02_surface_fft_level_{level_mode}")
# --- Test 3: Checkerboard pattern ---
print("\nTest 3: Checkerboard")
freq = 16
data = np.sign(np.sin(2 * np.pi * freq * x) * np.sin(2 * np.pi * freq * y))
field = make_field(data)
save_field(field, "03_checker_input")
result, _, _, _ = node.process(field, windowing="none", level="mean")
save_field(result, "03_checker_fft")
# --- Test 4: Concentric rings (radial frequency) ---
print("\nTest 4: Concentric rings")
r = np.sqrt((x - 0.5)**2 + (y - 0.5)**2)
data = np.sin(2 * np.pi * 30 * r)
field = make_field(data)
save_field(field, "04_rings_input")
result, _, _, _ = node.process(field, windowing="hann", level="mean")
save_field(result, "04_rings_fft")
# --- Test 5: Compare windowing effects ---
print("\nTest 5: Windowing comparison")
data = np.sin(2 * np.pi * 10.5 * x) + 0.5 * np.sin(2 * np.pi * 30.3 * y)
field = make_field(data)
save_field(field, "05_window_input")
for win in ["none", "hann", "hamming", "blackman"]:
result, _, _, _ = node.process(field, windowing=win, level="mean")
save_field(result, f"05_window_{win}")
print(f"\nAll outputs saved to {OUT_DIR}/")
if __name__ == "__main__":
main()