refactor(767-1): 안 쓰는 지면 필터 · 서피스 지움 — 필터 넷(CSF · 격자 최저 · PMF · RANSAC) · 표현 셋(TIN + Taubin 평활 · NURBS · meshfree) · 수치지형도 비교 방식 다섯 · 안 부르는 길 정리
- 필터 = 업체 분류 · 점진 TIN · 둘의 다듬기 판만 · 분석 허용 목록 = Ground 등록부(available_filters) 한 벌 - 표현 = DTM · implicit 만 · 평활 = DTM 만 · config 기본 목록 · PRECOMPUTE · 설정 사전 맞춤(설정 지문 바뀜 — 768 에서 다시 지음) - 등고선 = 격자 · 굳힌 격자만(삼각망 굽기 · 외곽 맞춤 · implicit RBF 폴백 걷음) · 표본기 · 경로 찾기 tin · nurbs · meshfree 갈래 걷음 - 수치지형도 = 라플라스만(TIN 둘 · TPS · ANUDEM형 · 다중해상도 걷음) · B04 단추 하나 - 안 부르는 길 — B04 JSON 점군 GET · ground-stats GET + TS · B07 옛 계획평면도(라이다) 짓기 · lidar_source · LIDAR_ID · plan_lidar 종류 이름 · SOURCE_FILTER_LABELS · MODEL_METHODS(py) · MESH_* 설정 - 옛 manifest 의 지운 갈래 줄은 DB 로 되살리지 않음 · 옛 csf 행은 미리보기 · 등고선 그대로 열림 · 지운 이름 요청 = 400 - 낡은 글 — Ground · Pipeline 머리 · WF1 「없으면 csf」 · B07 음영 기본 「csf」 → 빈 값 · 등고선 폴백 「csf」 → 404 · B04 3D 보기 처음값 · B05 Schema · B06 Prebuild · 씨닝 설명 - 시험 지움 2 — test_b04_contour_raster · test_b04_contour_equivalence(지운 삼각망 굽기 · 없는 프로젝트를 봐 건너뛰던 것) - 시험 덜어냄 — test_b04_ground_filters CSF · 격자 최저 네 경우(지운 필터) - 시험 고침 — 허용 목록 · 형 검사에 걸린 csf · tin · grid_min_z · meshfree · tin_sheet 견본을 ptd · implicit · laplace 로(54_22 · 238_1 · 443 · 342_2 · 346_3 · 351_1 · 442_2 · 271 + helper · 344_1 · 133_1 · 343_2 · 637 · 754_1 · 763_1 깨진 tin → 깨진 implicit · 763_3 · b05 둘 · b06 · sheet_surface TIN(도엽선) 경우 지움) · 이름만 바꿈(273_2 · 343_1 · 343_3 · 624_1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""B04 지표면 모델 공통 컨텍스트 및 메시 유틸리티.
|
||||
|
||||
지면 마스크가 적용된 포인트에서 footprint(외곽), 격자, 프리뷰 격자를 만들고,
|
||||
GLB/PLY 프리뷰 및 npz 모델을 원자적으로 저장하는 공통 기능을 제공한다.
|
||||
5개 표현(TIN/DTM/NURBS/implicit/meshfree) 빌더가 이 컨텍스트를 공유한다.
|
||||
GLB 프리뷰 및 npz 모델을 원자적으로 저장하는 공통 기능을 제공한다.
|
||||
표현(DTM/implicit) 빌더가 이 컨텍스트를 공유한다.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
@@ -16,7 +16,6 @@ from typing import Any, Callable
|
||||
import numpy as np
|
||||
import trimesh
|
||||
from scipy import ndimage
|
||||
from scipy.interpolate import RectBivariateSpline
|
||||
from skimage.restoration import inpaint_biharmonic
|
||||
|
||||
from common_util.common_util_atomic import atomic_write_bytes, atomic_write_npz
|
||||
@@ -24,8 +23,6 @@ from common_util.common_util_atomic import atomic_write_bytes, atomic_write_npz
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MODEL_VERSION = 1
|
||||
MODEL_METHODS = ("tin", "dtm", "nurbs", "implicit", "meshfree")
|
||||
SOURCE_FILTER_LABELS = {"grid_min_z": "Grid Min-Z", "csf": "CSF", "pmf": "PMF"}
|
||||
ProgressCallback = Callable[[int], None]
|
||||
|
||||
# 대용량 포인트 배치 처리 크기
|
||||
@@ -33,63 +30,6 @@ _BATCH_SIZE = 500_000
|
||||
# implicit 격자 칸 뽑기 씨앗 — 고정이라 다시 지어도 같은 중심점
|
||||
IMPLICIT_SAMPLE_SEED = 558
|
||||
|
||||
# NURBS 평활 계수 — 제어점당 허용 잔차제곱(m²).
|
||||
# 0.01(RMS 0.1m)은 요구가 너무 빡빡해 FITPACK이 제어점 수(122x113)보다 많은
|
||||
# knot(126x117)을 밀어넣고 계수가 발산했다 — csf 표고 1e105, grid_min_z 1e10
|
||||
# (2026-08-17 실측). 1.0(RMS 1m)이면 knot이 17~35로 떨어지고 세 지면 필터 모두
|
||||
# 데이터 표고 범위 안에 머문다. NURBS는 평활 곡면 표현이라 이 정도 완화가 맞다.
|
||||
NURBS_RESIDUAL_PER_CONTROL_POINT = 1.0
|
||||
|
||||
|
||||
def fit_nurbs_spline(
|
||||
x_control: np.ndarray, y_control: np.ndarray, z_control: np.ndarray, degree: int
|
||||
) -> tuple[RectBivariateSpline, tuple[float, float]]:
|
||||
"""제어 격자에 B-spline 곡면을 맞추고, 발산 판정용 표고 허용 범위를 함께 준다.
|
||||
|
||||
모델 빌더와 등고선 엔진이 같은 곡면을 각각 만들므로 여기서 한 번에 정의한다.
|
||||
"""
|
||||
spline = RectBivariateSpline(
|
||||
y_control,
|
||||
x_control,
|
||||
z_control,
|
||||
kx=min(degree, len(y_control) - 1),
|
||||
ky=min(degree, len(x_control) - 1),
|
||||
s=float(len(x_control) * len(y_control)) * NURBS_RESIDUAL_PER_CONTROL_POINT,
|
||||
)
|
||||
z_min = float(np.min(z_control))
|
||||
z_max = float(np.max(z_control))
|
||||
margin = max((z_max - z_min) * 0.5, 1.0)
|
||||
return spline, (z_min - margin, z_max + margin)
|
||||
|
||||
|
||||
def evaluate_nurbs_spline(
|
||||
spline: RectBivariateSpline,
|
||||
y_coords: np.ndarray,
|
||||
x_coords: np.ndarray,
|
||||
z_range: tuple[float, float],
|
||||
label: str = "",
|
||||
) -> np.ndarray:
|
||||
"""스플라인을 평가하고 표고 허용 범위를 벗어난 값을 잘라낸다.
|
||||
|
||||
평활 스플라인은 제어점이 성긴 구석에서 발산할 수 있다. 그대로 두면 float32
|
||||
캐스팅에서 inf가 되고 프리뷰 색상(nan)과 등고선 레벨 산출(`np.arange`가
|
||||
"Maximum allowed size exceeded"로 실패)까지 번진다.
|
||||
"""
|
||||
z_values = np.asarray(spline(y_coords, x_coords), dtype=np.float64)
|
||||
low, high = z_range
|
||||
outside = np.count_nonzero(~np.isfinite(z_values) | (z_values < low) | (z_values > high))
|
||||
if outside:
|
||||
logger.warning(
|
||||
"NURBS 곡면이 표고 범위를 벗어나 잘라냈습니다: %s%d개 셀 (허용 %.1f~%.1fm)",
|
||||
f"{label} " if label else "",
|
||||
int(outside),
|
||||
low,
|
||||
high,
|
||||
)
|
||||
z_values = np.nan_to_num(z_values, nan=low, posinf=high, neginf=low)
|
||||
return np.clip(z_values, low, high)
|
||||
|
||||
|
||||
# 서명에서 빼는 키 — 지오메트리를 만드는 방법이 아니라 "무엇을 만들지"를 고르는 값들.
|
||||
# source_filters·precompute를 넣으면 조합을 바꿀 때마다 manifest가 통째로 폐기돼
|
||||
# 이전에 만들어 둔 조합이 사라진다. manifest는 이미 필터·방식별로 키가 갈린다.
|
||||
@@ -155,44 +95,6 @@ def write_glb(path: Path, vertices: np.ndarray, faces: np.ndarray, bounds: np.nd
|
||||
atomic_write_bytes(path, payload)
|
||||
|
||||
|
||||
def write_binary_ply(
|
||||
path: Path, vertices: np.ndarray, normals: np.ndarray, bounds: np.ndarray
|
||||
) -> None:
|
||||
verts = scene_vertices(vertices, bounds)
|
||||
scene_normals = np.empty_like(normals, dtype=np.float32)
|
||||
scene_normals[:, 0] = normals[:, 0]
|
||||
scene_normals[:, 1] = normals[:, 2]
|
||||
scene_normals[:, 2] = -normals[:, 1]
|
||||
colors = height_colors(vertices)
|
||||
dtype = np.dtype(
|
||||
[
|
||||
("x", "<f4"),
|
||||
("y", "<f4"),
|
||||
("z", "<f4"),
|
||||
("nx", "<f4"),
|
||||
("ny", "<f4"),
|
||||
("nz", "<f4"),
|
||||
("red", "u1"),
|
||||
("green", "u1"),
|
||||
("blue", "u1"),
|
||||
("alpha", "u1"),
|
||||
]
|
||||
)
|
||||
records = np.empty(len(vertices), dtype=dtype)
|
||||
records["x"], records["y"], records["z"] = verts.T
|
||||
records["nx"], records["ny"], records["nz"] = scene_normals.T
|
||||
records["red"], records["green"], records["blue"], records["alpha"] = colors.T
|
||||
header = (
|
||||
"ply\nformat binary_little_endian 1.0\n"
|
||||
f"element vertex {len(vertices)}\n"
|
||||
"property float x\nproperty float y\nproperty float z\n"
|
||||
"property float nx\nproperty float ny\nproperty float nz\n"
|
||||
"property uchar red\nproperty uchar green\nproperty uchar blue\nproperty uchar alpha\n"
|
||||
"end_header\n"
|
||||
).encode("ascii")
|
||||
atomic_write_bytes(path, header + records.tobytes())
|
||||
|
||||
|
||||
def grid_faces(rows: int, cols: int) -> np.ndarray:
|
||||
"""정규 격자의 삼각형 면 인덱스를 만든다."""
|
||||
if rows < 2 or cols < 2:
|
||||
|
||||
Reference in New Issue
Block a user