Files
Aislo/0_old/utils/test_terrain_skeleton.py
T

125 lines
5.4 KiB
Python

import sys
from pathlib import Path
import numpy as np
# Add project root to sys.path
root_dir = Path(__file__).resolve().parent.parent
if str(root_dir) not in sys.path:
sys.path.insert(0, str(root_dir))
from utils.terrain_skeleton import (
d8_flow_accumulation_numpy,
extract_skeleton_from_grid,
_trace_polylines,
)
def _v_valley_grid(rows=80, cols=81, res=2.0):
"""V자 계곡 합성 지형: 중앙 열이 계곡선, 좌우 가장자리가 능선.
y 가 작아질수록 낮아져 물이 중앙선을 따라 y=0 쪽으로 모인다."""
x = np.arange(cols, dtype=np.float64) * res
y = np.arange(rows, dtype=np.float64) * res
xc = x[cols // 2]
xx, yy = np.meshgrid(x, y)
z = 0.05 * np.abs(xx - xc) + 0.02 * yy
valid = np.ones_like(z, dtype=bool)
return x, y, z, valid, res, xc
def test_d8_accumulation_on_tilted_plane():
"""단순 경사면: 모든 물이 최저 셀 방향으로 모여 하류 누적값이 상류보다 크다."""
rows, cols = 20, 20
x = np.arange(cols, dtype=np.float64)
z = np.tile(x * 1.0, (rows, 1)) # x 방향으로만 상승 → 물은 -x 방향으로
valid = np.ones_like(z, dtype=bool)
acc = d8_flow_accumulation_numpy(z, valid)
# 좌측(하류) 열의 평균 누적값이 우측(상류) 열보다 커야 한다.
assert acc[:, 0].mean() > acc[:, -1].mean()
# 모든 유효 셀은 최소 자기 자신(1) 이상.
assert (acc >= 1.0).all()
print("[ok] D8 accumulation flows downhill on a tilted plane")
def test_d8_accumulation_v_valley_concentrates_center():
"""V자 계곡: 중앙 열 누적값이 사면 셀보다 훨씬 커야 한다."""
x, y, z, valid, res, xc = _v_valley_grid()
acc = d8_flow_accumulation_numpy(z, valid)
center_c = len(x) // 2
hillside_c = len(x) // 4
# 하류(작은 y) 중앙 셀은 사면 중간 셀 대비 큰 유역을 가진다.
assert acc[2, center_c] > acc[2, hillside_c] * 5
print("[ok] V-valley concentrates flow accumulation on the center line")
def test_extract_skeleton_valley_on_center():
"""스켈레톤 추출: 계곡 polyline 은 중앙선 부근, 능선 polyline 도 존재해야 한다."""
x, y, z, valid, res, xc = _v_valley_grid()
thresholds = {
"valley_acc": 40.0, "main_valley_acc": 800.0,
"ridge_acc": 40.0, "main_ridge_acc": 800.0,
}
skel = extract_skeleton_from_grid(
x, y, z, valid, res, thresholds=thresholds, use_whitebox=False
)
valley_polys = skel["main_valley"] + skel["minor_valley"]
ridge_polys = skel["main_ridge"] + skel["minor_ridge"]
assert valley_polys, "계곡 polyline 이 추출되어야 한다"
assert ridge_polys, "능선 polyline 이 추출되어야 한다"
# 계곡 정점은 중앙선(xc)에서 10m 이내에 있어야 한다 (V자 지형의 유일한 집수선).
for item in valley_polys:
for px, py, pz in item["polyline"]:
assert abs(px - xc) < 10.0, f"계곡 정점 x={px} 가 중앙선 {xc} 에서 벗어남"
print("[ok] skeleton valley polylines follow the V-valley center line")
def test_main_minor_split_by_threshold():
"""주/지 분류: 임계값을 낮추면 주계곡이 생기고, 높이면 전부 지계곡이 된다."""
x, y, z, valid, res, xc = _v_valley_grid()
low = extract_skeleton_from_grid(
x, y, z, valid, res, use_whitebox=False,
thresholds={"valley_acc": 40.0, "main_valley_acc": 200.0,
"ridge_acc": 40.0, "main_ridge_acc": 1e9},
)
high = extract_skeleton_from_grid(
x, y, z, valid, res, use_whitebox=False,
thresholds={"valley_acc": 40.0, "main_valley_acc": 1e9,
"ridge_acc": 40.0, "main_ridge_acc": 1e9},
)
assert low["main_valley"], "낮은 주계곡 임계값에서는 주계곡이 분류되어야 한다"
assert not high["main_valley"], "임계값이 매우 높으면 주계곡이 없어야 한다"
assert high["minor_valley"], "주계곡이 없으면 전부 지계곡으로 분류되어야 한다"
print("[ok] main/minor split responds to the accumulation threshold")
def test_trace_polylines_simple_line():
"""픽셀 추적: 직선 마스크는 한 개의 polyline 으로 이어져야 한다."""
mask = np.zeros((10, 10), dtype=bool)
mask[5, 1:9] = True
polys = _trace_polylines(mask)
assert len(polys) == 1
assert len(polys[0]) == 8
print("[ok] polyline tracing joins a straight pixel line into one path")
def test_whitebox_path_or_fallback():
"""whitebox 경로가 실패해도 numpy 폴백으로 결과가 나와야 한다 (통합 스모크).
whitebox 미설치/실행불가 환경이면 폴백 로그와 함께 동일 결과가 나온다."""
x, y, z, valid, res, xc = _v_valley_grid(rows=40, cols=41)
skel = extract_skeleton_from_grid(
x, y, z, valid, res, use_whitebox=True,
thresholds={"valley_acc": 40.0, "main_valley_acc": 1e9,
"ridge_acc": 40.0, "main_ridge_acc": 1e9},
)
assert skel["minor_valley"], "whitebox 또는 폴백으로 계곡이 추출되어야 한다"
print("[ok] whitebox path (or numpy fallback) produces a skeleton")
if __name__ == "__main__":
test_d8_accumulation_on_tilted_plane()
test_d8_accumulation_v_valley_concentrates_center()
test_extract_skeleton_valley_on_center()
test_main_minor_split_by_threshold()
test_trace_polylines_simple_line()
test_whitebox_path_or_fallback()
print("\n모든 terrain_skeleton 테스트 통과")