"""Tests for visualization functions. Each test creates a small synthetic DEM and runs a visualization function, checking that it produces a valid output file. """ import numpy as np import pytest from pathlib import Path # --- Core terrain visualizations (no GPU required) --- class TestHillshade: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_hillshade result = generate_hillshade(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() assert result.suffix == ".tif" def test_output_values_valid(self, synthetic_dem, tmp_output_dir): import rasterio from lidar_pipeline.visualizations import generate_hillshade result = generate_hillshade(synthetic_dem, "test", tmp_output_dir, 5.0) with rasterio.open(result) as src: data = src.read(1) assert data.shape[0] > 0 assert np.nanmin(data) >= 0 assert np.nanmax(data) <= 1 class TestSlope: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_slope result = generate_slope(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() def test_slope_values_degrees(self, synthetic_dem, tmp_output_dir): import rasterio from lidar_pipeline.visualizations import generate_slope result = generate_slope(synthetic_dem, "test", tmp_output_dir, 5.0) with rasterio.open(result) as src: data = src.read(1) assert np.nanmin(data) >= 0 assert np.nanmax(data) <= 90 # --- GPU-accelerated visualizations --- class TestSVF: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_svf result = generate_svf(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() def test_svf_values_0_1(self, synthetic_dem, tmp_output_dir): import rasterio from lidar_pipeline.visualizations import generate_svf result = generate_svf(synthetic_dem, "test", tmp_output_dir, 5.0) with rasterio.open(result) as src: data = src.read(1) valid = data[~np.isnan(data)] assert np.nanmin(valid) >= 0 assert np.nanmax(valid) <= 1 class TestOpenness: def test_positive_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_openness result = generate_openness(synthetic_dem, "test", tmp_output_dir, 5.0, positive=True) assert result is not None assert result.exists() def test_negative_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_openness result = generate_openness(synthetic_dem, "test", tmp_output_dir, 5.0, positive=False) assert result is not None assert result.exists() class TestMSLRM: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_mslrm result = generate_mslrm(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() class TestSAILORE: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_sailore result = generate_sailore(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() class TestRoughness: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_roughness result = generate_roughness(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() def test_roughness_non_negative(self, synthetic_dem, tmp_output_dir): import rasterio from lidar_pipeline.visualizations import generate_roughness result = generate_roughness(synthetic_dem, "test", tmp_output_dir, 5.0) with rasterio.open(result) as src: data = src.read(1) # Standard deviation is always >= 0 assert np.nanmin(data) >= 0 class TestWavelet: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_wavelet result = generate_wavelet(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() class TestFlowAccumulation: def test_generates_tif(self, synthetic_dem, tmp_output_dir): from lidar_pipeline.visualizations import generate_flow_accumulation result = generate_flow_accumulation(synthetic_dem, "test", tmp_output_dir, 5.0) assert result is not None assert result.exists() def test_flow_log_values(self, synthetic_dem, tmp_output_dir): import rasterio from lidar_pipeline.visualizations import generate_flow_accumulation result = generate_flow_accumulation(synthetic_dem, "test", tmp_output_dir, 5.0) with rasterio.open(result) as src: data = src.read(1) # log10(x) >= 0 for x >= 1 valid = data[~np.isnan(data)] assert np.nanmin(valid) >= 0 class TestRayTrace: def test_rays_are_traced(self, synthetic_dem, tmp_output_dir): """Verify _ray_trace_horizons returns expected shapes.""" from lidar_pipeline.visualizations import _ray_trace_horizons, _prepare_dem_for_raycast import rasterio with rasterio.open(synthetic_dem) as src: dem_np = src.read(1) rows, cols = dem_np.shape # Create a simple filled DEM for testing import numpy as np filled = np.nan_to_num(dem_np, nan=0) # Test with numpy (no GPU) pos, neg = _ray_trace_horizons( filled, rows, cols, 5.0, n_dirs=4, max_dist=10, radii_m=[25, 50] ) assert pos.shape == (4, 2, rows, cols) assert neg.shape == (4, 2, rows, cols)