import numpy as np import pytest from tests.node_tests._shared import make_field from backend.data_types import LineData def test_slope_distribution_output_shape(): from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field() for dist in ("theta", "phi", "gradient"): result, = node.process(field, dist, 90) assert isinstance(result, LineData) assert len(result.data) == 90 assert len(result.x_axis) == 90 def test_slope_distribution_theta_non_negative_and_normalized(): from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field() result, = node.process(field, "theta", 90) assert np.all(result.data >= 0.0) assert result.x_unit == "deg" # Probability density: integral ≈ 1 (bin_width × sum ≈ 1) bin_width = result.x_axis[1] - result.x_axis[0] integral = float(np.sum(result.data) * bin_width) assert abs(integral - 1.0) < 0.05 def test_slope_distribution_gradient_normalized(): from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field() result, = node.process(field, "gradient", 100) assert np.all(result.data >= 0.0) # Probability density: integral ≈ 1 bin_width = result.x_axis[1] - result.x_axis[0] integral = float(np.sum(result.data) * bin_width) assert abs(integral - 1.0) < 0.05 def test_slope_distribution_phi_units(): from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field() result, = node.process(field, "phi", 180) assert result.x_unit == "deg" assert np.all(result.data >= 0.0) # x-axis must span [0, 360) assert result.x_axis[0] >= 0.0 assert result.x_axis[-1] < 360.0 def test_slope_distribution_flat_field_theta(): """Flat field: all gradients are zero → theta=0 everywhere, first bin gets all weight.""" from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field(data=np.zeros((32, 32))) result, = node.process(field, "theta", 90) # With max_theta=0 we use fallback 90; all pixels land in bin 0 assert result.data[0] == pytest.approx(result.data.sum(), abs=1e-10) def test_slope_distribution_x_ramp_phi(): """X-only ramp: slope direction should be concentrated near phi=180° (ascending left, or 0°/360°).""" from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() data = np.tile(np.linspace(0.0, 1.0, 64), (64, 1)) field = make_field(data=data) result, = node.process(field, "phi", 360) # Peak should be within first or last few bins (phi ≈ 0 or 2π, i.e., ascending in +x direction) # atan2(0, -gx) with gx>0 gives atan2(0,-gx) = π, so peak near 180° peak_idx = int(np.argmax(result.data)) peak_deg = float(result.x_axis[peak_idx]) assert 150.0 < peak_deg < 210.0, f"Unexpected peak at {peak_deg}°" def test_slope_distribution_unknown_distribution(): from backend.nodes.slope_distribution import SlopeDistribution node = SlopeDistribution() field = make_field() with pytest.raises(ValueError): node.process(field, "azimuth", 90)