- Remove generate_rrim, generate_multi_hillshade, _compute_openness_both - Remove corresponding VIZ_STEPS entries, COLORMAPS, RGB_LEGENDS, and tests - Fix DTM resolution mismatch: existing DTM at different resolution is now regenerated instead of silently reused - Propagate actual DTM resolution to visualizations and rendering - Add --init to docker run commands for proper signal handling on Ctrl+C - Add .playwright-mcp/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
232 lines
9.1 KiB
Python
232 lines
9.1 KiB
Python
"""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
|
|
|
|
|
|
class TestAspect:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_aspect
|
|
result = generate_aspect(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
assert result is not None
|
|
assert result.exists()
|
|
|
|
def test_aspect_values_0_360(self, synthetic_dem, tmp_output_dir):
|
|
import rasterio
|
|
from lidar_pipeline.visualizations import generate_aspect
|
|
result = generate_aspect(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) <= 360
|
|
|
|
|
|
class TestCurvature:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_curvature
|
|
result = generate_curvature(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
assert result is not None
|
|
assert result.exists()
|
|
|
|
|
|
# --- GPU-accelerated visualizations ---
|
|
|
|
class TestLRM:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_lrm
|
|
result = generate_lrm(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
assert result is not None
|
|
assert result.exists()
|
|
|
|
def test_lrm_has_positive_negative(self, synthetic_dem, tmp_output_dir):
|
|
import rasterio
|
|
from lidar_pipeline.visualizations import generate_lrm
|
|
result = generate_lrm(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
with rasterio.open(result) as src:
|
|
data = src.read(1)
|
|
# LRM should have both positive and negative values
|
|
assert np.nanmax(data) > 0
|
|
assert np.nanmin(data) < 0
|
|
|
|
|
|
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 TestTPI:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_tpi
|
|
result = generate_tpi(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 TestAnomalies:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_anomalies
|
|
result = generate_anomalies(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
assert result is not None
|
|
assert result.exists()
|
|
|
|
|
|
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 TestFlow:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_flow
|
|
result = generate_flow(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
|
|
result = generate_flow(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
with rasterio.open(result) as src:
|
|
data = src.read(1)
|
|
# log1p(x) >= 0 for x >= 0
|
|
valid = data[~np.isnan(data)]
|
|
assert np.nanmin(valid) >= 0
|
|
|
|
|
|
class TestLocalDominance:
|
|
def test_generates_tif(self, synthetic_dem, tmp_output_dir):
|
|
from lidar_pipeline.visualizations import generate_local_dominance
|
|
result = generate_local_dominance(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
assert result is not None
|
|
assert result.exists()
|
|
assert result.suffix == ".tif"
|
|
|
|
def test_dominance_values_0_1(self, synthetic_dem, tmp_output_dir):
|
|
import rasterio
|
|
from lidar_pipeline.visualizations import generate_local_dominance
|
|
result = generate_local_dominance(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, "Local dominance should be >= 0"
|
|
assert np.nanmax(valid) <= 1, "Local dominance should be <= 1"
|
|
|
|
def test_dominance_nan_mask_preserved(self, synthetic_dem, tmp_output_dir):
|
|
"""Check that NaN zones from original DEM are preserved."""
|
|
import rasterio
|
|
from lidar_pipeline.visualizations import generate_local_dominance
|
|
result = generate_local_dominance(synthetic_dem, "test", tmp_output_dir, 5.0)
|
|
# The synthetic DEM has no NaN, so this just verifies the output is valid
|
|
with rasterio.open(result) as src:
|
|
data = src.read(1)
|
|
# Shape should match input
|
|
assert data.shape[0] > 0
|
|
assert data.shape[1] > 0 |