"""762-1 지면점 다듬기(SMRF 꼴) · 766-1 다듬기 기본 흐름 · 762-2 품질 지표 지킴 시험. 합성 비탈(17°) + 능선(높이 6 m · 폭 약 16 m) + 덤불 혹(3 × 3 m · 1.5 m 뜸) — 필터가 덤불까지 지면으로 고른 판 → 다듬기가 혹은 지우고 능선 · 비탈은 남김. """ import numpy as np import B04_PreProcess.B04_PreProcess_Engine_Ground as ground from B04_PreProcess.B04_PreProcess_Engine_Filter_Refine import refine_ground_mask from B04_PreProcess.B04_PreProcess_Engine_Quality import spike_stats RNG = np.random.default_rng(7621) def _terrain(x: np.ndarray, y: np.ndarray) -> np.ndarray: return 0.3 * x + 6.0 * np.exp(-(((x - 60.0) / 8.0) ** 2)) + 0.02 * y def _scene() -> tuple[np.ndarray, np.ndarray, np.ndarray]: count = 120 * 120 * 4 x = RNG.uniform(0, 120, count) y = RNG.uniform(0, 120, count) z = _terrain(x, y) + RNG.normal(0, 0.03, count) bush = np.zeros(count, dtype=bool) for cx, cy in RNG.uniform(10, 110, (15, 2)): if abs(cx - 60) < 12: # 능선 위 덤불은 따로 가름 — 여기선 비탈 · 골 덤불 cx += 25 inside = (np.abs(x - cx) < 1.5) & (np.abs(y - cy) < 1.5) z[inside] += 1.5 bush |= inside ridge = (np.abs(x - 60) < 4) & ~bush return np.column_stack((x, y, z)), bush, ridge def test_refine_drops_bushes_keeps_ridge() -> None: xyz, bush, ridge = _scene() kept = refine_ground_mask(xyz, np.ones(len(xyz), dtype=bool)) print(kept[bush].mean(), kept[ridge].mean(), kept[~bush].mean()) assert kept[bush].mean() < 0.1 # 덤불 혹 90 % 넘게 지움 assert kept[ridge].mean() > 0.95 # 능선 안 깎음 assert kept[~bush].mean() > 0.97 # 땅 점 거의 그대로 def test_refine_never_adds_points() -> None: xyz, _, _ = _scene() mask = RNG.random(len(xyz)) < 0.5 kept = refine_ground_mask(xyz, mask) assert not (kept & ~mask).any() assert refine_ground_mask(xyz, np.zeros(len(xyz), dtype=bool)).sum() == 0 def test_refine_flow_is_default() -> None: xyz, _, _ = _scene() bounds = np.array([[0, 120], [0, 120], [0, 50]], dtype=np.float64) data = {"xyz": xyz, "bounds": bounds, "classification": np.full(len(xyz), 2, np.uint8)} assert ground.resolve_auto_source_filters(data) == ["classification_refine", "ptd_refine"] data["classification"][:] = 0 assert ground.resolve_auto_source_filters(data) == ["ptd_refine"] assert ground.base_filter("ptd_refine") == "ptd" assert ground.base_filter("ptd") is None assert {"classification_refine", "ptd_refine"} <= set(ground.available_filters()) # 바탕 마스크를 넘기면 바탕 필터를 다시 돌리지 않고 다듬기만 base = np.ones(len(xyz), dtype=bool) refined = ground.run_ground_filter("ptd_refine", data, base) assert refined.sum() < base.sum() def test_spike_stat_sees_bumps() -> None: yy, xx = np.mgrid[0:100, 0:100].astype(float) z = 0.3 * xx valid = np.ones(z.shape, dtype=bool) assert spike_stats(z, valid)["spike_pct"] == 0.0 z[40:42, 40:42] += 1.5 stats = spike_stats(z, valid) assert stats["spike_count"] == 1 and stats["spike_pct"] > 0