"""766-2 외곽 안 구멍 메움 지킴 시험 — 다듬기 판은 바탕 필터 외곽 · 구멍은 막 펴기로 매끈하게. 합성 비탈(평면) 120 × 120 m · 가운데 30 × 30 m 땅 점이 다 걸러짐(다듬기가 지운 자리 흉내). """ import numpy as np from B04_PreProcess.B04_PreProcess_Engine_ModelBuild import build_dtm, build_implicit from B04_PreProcess.B04_PreProcess_Engine_ModelContext import TerrainContext, fill_holes_smooth from B04_PreProcess.B04_PreProcess_Engine_Quality import filled_stats from common_util.common_util_surface_sampler import build_surface_sampler from config.config_system import build_surface_model_config RNG = np.random.default_rng(7662) def _scene() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: count = 120 * 120 * 3 x, y = RNG.uniform(0, 120, count), RNG.uniform(0, 120, count) xyz = np.column_stack((x, y, 0.3 * x + 0.1 * y + 100.0)).astype(np.float32) base = np.ones(count, dtype=bool) hole = (np.abs(x - 60) < 15) & (np.abs(y - 60) < 15) bounds = np.array([[0, 120], [0, 120], [90, 150]], dtype=np.float64) return xyz, base, base & ~hole, bounds def test_fill_keeps_plane_and_curved_slope() -> None: yy, xx = np.mgrid[0:60, 0:60].astype(float) known = np.ones(xx.shape, dtype=bool) known[20:40, 20:40] = False inside = np.ones(xx.shape, dtype=bool) for truth in (0.3 * xx + 0.1 * yy, 0.004 * (xx - 25) ** 2 + 0.002 * (yy - 35) ** 2): work = np.where(known, truth, 0.0) filled = fill_holes_smooth(work, known, inside, 3.0) assert filled.sum() == 14 * 14 # 땅 칸에서 3 칸 넘게 먼 자리만 메운 넓이로 셈 assert np.abs(work - truth).max() < 0.1 # 둘레 높이 · 기울기 그대로 이어짐(계단 · 혹 없음) # 작은 틈(3 칸 안)은 메움 대상이 아님 — 지금처럼 가까운 칸 값 small = np.ones(xx.shape, dtype=bool) small[10:13, 10:13] = False assert not fill_holes_smooth(xx.copy(), small, inside, 3.0).any() def test_refine_hole_inside_outer_is_filled(tmp_path) -> None: xyz, base, refined, bounds = _scene() config = build_surface_model_config() plain = TerrainContext(xyz=xyz, mask=refined, bounds=bounds, config=config) outer = TerrainContext(xyz=xyz, mask=refined, bounds=bounds, config=config, outer_mask=base) # 외곽 = 바탕 필터 그대로(넓히지 않음) · 구멍 칸만 메움 base_ctx = TerrainContext(xyz=xyz, mask=base, bounds=bounds, config=config) assert outer.footprint()[3].sum() == base_ctx.footprint()[3].sum() resolution = float(config["dtm_grid_resolution_meters"]) assert outer.filled(resolution).sum() > 400 assert not base_ctx.filled(resolution).any() build_dtm(outer, tmp_path, "dtm_ptd_refine", lambda _: None) build_implicit(outer, tmp_path, "implicit_ptd_refine", lambda _: None) xy = np.column_stack([np.linspace(50, 70, 21), np.full(21, 60.0)]) truth = 0.3 * xy[:, 0] + 0.1 * xy[:, 1] + 100.0 for method in ("dtm", "implicit"): z, valid = build_surface_sampler(tmp_path, "ptd_refine", method, False).sample_xy(xy) assert valid.all(), method # 구멍 0 # 둘레 비탈과 같은 면 — dtm 둘레 칸 = 칸 최저점(0.1~0.2 m 들쭉날쭉)이라 0.5 m 안 assert np.abs(z - truth).max() < 0.5, method with np.load(tmp_path / "dtm_ptd_refine.npz") as data: valid = np.asarray(data["valid_mask"], dtype=bool) stats = filled_stats(tmp_path, "ptd_refine", valid, 1.0) assert 400 < stats["filled_m2"] < 900 and stats["filled_pct"] > 0 # 바탕 외곽 없이(옛 길)는 구멍 자리가 외곽 밖 — 구멍이 열린 만이면 비어 있었음 assert plain.footprint()[3].sum() <= outer.footprint()[3].sum()