From c94b4a786ac2d13c68b2e94e49159c12324ea598 Mon Sep 17 00:00:00 2001 From: HYUNJUNGLEE Date: Fri, 8 May 2026 15:08:21 +0900 Subject: [PATCH] Phase 1 (#11): wire perf_block into scanvas_maker.py at 4 hotspots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - import + 폴백 (line ~58): from harness.perf import perf_block, set_perf_log ImportError 시 contextlib.contextmanager 노옵으로 안전 폴백. - SCanvasApp.__init__ (line ~613): set_perf_log(self.log) — GUI 패널에 perf 라인 표시. - TIN densify Phase C (line ~4430): with perf_block("TIN densify Phase C (10m→1m)"). - 위성 타일 다운로드 (line ~5384): with perf_block("위성 타일 다운로드+병합"). - 제어맵 캡처 x3 + composite (line ~5864): with perf_block("control map capture x3 + composite"). 검증: py_compile + AST parse OK. 글로벌 ruff 미설치라 ruff Green 검증은 다음 세션 (uv pip install -e ".[dev]" 후). CHANGELOG.md에 wire 내역 + 측정 출력 예시 추가. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 23 +++++++++ scanvas_maker.py | 125 +++++++++++++++++++++++++++-------------------- 2 files changed, 94 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d2844..7fcbecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,29 @@ --- +## 2026-05-08 (후속 — wire) + +### [feat] `scanvas_maker.py`에 `perf_block` wire 완료 (#11) + +- **수정 파일**: `scanvas_maker.py` (5곳). +- **목적**: harness/perf.py scaffold가 즉시 동작하도록 핫스팟에 `with perf_block(...)` 래핑. +- **변경 위치**: + 1. **import 블록 (~line 58)**: `from harness.perf import perf_block, set_perf_log` + ImportError 시 `@contextlib.contextmanager` 노옵 폴백. + 2. **`SCanvasApp.__init__` (~line 613)**: `set_perf_log(self.log)` 등록 — perf 측정 라인이 GUI 텍스트박스에도 표시됨. + 3. **TIN densify Phase C (line ~4430)**: `with perf_block("TIN densify Phase C (10m→1m)")` 로 10단계 점진 격자 루프 감쌈. + 4. **위성 타일 다운로드 (line ~5384)**: `with perf_block("위성 타일 다운로드+병합")` 로 `_download_xyz_tiles()` 감쌈 — 사용자 피드백 #11이 명시한 "위성지도 결합" 핫스팟. + 5. **제어맵 캡처 파이프라인 (line ~5864)**: `with perf_block("control map capture x3 + composite")` 로 textured + depth + lineart 3-stage 캡처 + composite 감쌈 (PERFORMANCE_BASELINE.md H12). +- **출력 예 (실제 측정 시)**: + ``` + [PERF] 위성 타일 다운로드+병합: wall=12340.5ms cpu=860.3ms (I/O/Net-bound) + [PERF] TIN densify Phase C (10m→1m): wall=2150.7ms cpu=2080.4ms (CPU-bound) + [PERF] control map capture x3 + composite: wall=4520.1ms cpu=3760.8ms (CPU-bound) + ``` +- **검증**: `python -m py_compile scanvas_maker.py harness/perf.py` 통과. AST parse OK. ruff는 글로벌 미설치 환경이라 다음 세션 uv install 후 검증 (`uv pip install -e ".[dev]"; ruff check`). +- **다음**: 핫스팟 H1·H7·H9·H12·H13·H18 등 추가 측정 위치 확장은 본 라운드 미적용. 사용자 실제 도면으로 한 번 측정 후 PERFORMANCE_BASELINE.md 의 "측정 후 비교 표" 채울 때 결정. + +--- + ## 2026-05-08 ### [merge] Gitea s-canvas 원격(raw upload, 184185c)과 로컬 lint+Phase 0 history 통합 diff --git a/scanvas_maker.py b/scanvas_maker.py index 7d841db..10e72a7 100644 --- a/scanvas_maker.py +++ b/scanvas_maker.py @@ -55,6 +55,16 @@ try: except ImportError: HARNESS_AVAILABLE = False +# Perf instrumentation (#11) — ms 단위 wall/CPU 측정. import 실패 시 no-op 폴백. +try: + from harness.perf import perf_block, set_perf_log +except ImportError: + @contextlib.contextmanager + def perf_block(label): # type: ignore[no-redef] + yield + def set_perf_log(fn): # type: ignore[no-redef] + pass + # 구조물 상세도면 치수 파서 try: from detail_parser import DetailParser, dimensions_to_structure_params @@ -610,6 +620,10 @@ class SCanvasApp(ctk.CTk): self.log("S-CANVAS Generative Design Engine 구동 완료.") + # Perf 측정 라인을 GUI 로그에도 함께 표시 (#11). harness/perf.py 폴백 import 시 + # set_perf_log는 no-op이라 실패해도 안전. + set_perf_log(self.log) + def create_sidebar_button(self, text, command, row, **kwargs): btn = ctk.CTkButton( self.sidebar_frame, text=text, command=command, height=34, **kwargs) @@ -4427,40 +4441,41 @@ class SCanvasApp(ctk.CTk): from matplotlib.path import Path as _MplPath total_phase_c = 0 steps_log = [] - for _step in (10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0): - try: - hull_c = _ConvexHull(pts[:, :2]) - except Exception: - break - hull_poly_xy = pts[hull_c.vertices, :2] - hull_path_c = _MplPath(hull_poly_xy, closed=True) - gx = np.arange(x0_abs, x1_abs + _step * 0.5, _step) - gy = np.arange(y0_abs, y1_abs + _step * 0.5, _step) - ggx, ggy = np.meshgrid(gx, gy) - grid_xy_c = np.column_stack([ggx.ravel(), ggy.ravel()]) - inside_bbox = ( - (grid_xy_c[:, 0] >= x0_abs - 1e-6) - & (grid_xy_c[:, 0] <= x1_abs + 1e-6) - & (grid_xy_c[:, 1] >= y0_abs - 1e-6) - & (grid_xy_c[:, 1] <= y1_abs + 1e-6) - ) - grid_xy_c = grid_xy_c[inside_bbox] - if len(grid_xy_c) == 0: - continue - inside_hull = hull_path_c.contains_points(grid_xy_c) - outside_hull_xy = grid_xy_c[~inside_hull] - if len(outside_hull_xy) == 0: - continue - # 기존 점과 너무 가까운 격자점(≤ step×0.4) 제외 — 중복 방지 - tree_ex = _cKDTreeC(pts[:, :2]) - d_ex, _ = tree_ex.query(outside_hull_xy, k=1) - new_only_xy = outside_hull_xy[d_ex > _step * 0.4] - if len(new_only_xy) == 0: - continue - new_z_c = _dem_sample_minus_offset(new_only_xy) - pts = np.vstack([pts, np.column_stack([new_only_xy, new_z_c])]) - total_phase_c += len(new_only_xy) - steps_log.append(f"{_step:.0f}m:{len(new_only_xy)}") + with perf_block("TIN densify Phase C (10m→1m)"): + for _step in (10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0): + try: + hull_c = _ConvexHull(pts[:, :2]) + except Exception: + break + hull_poly_xy = pts[hull_c.vertices, :2] + hull_path_c = _MplPath(hull_poly_xy, closed=True) + gx = np.arange(x0_abs, x1_abs + _step * 0.5, _step) + gy = np.arange(y0_abs, y1_abs + _step * 0.5, _step) + ggx, ggy = np.meshgrid(gx, gy) + grid_xy_c = np.column_stack([ggx.ravel(), ggy.ravel()]) + inside_bbox = ( + (grid_xy_c[:, 0] >= x0_abs - 1e-6) + & (grid_xy_c[:, 0] <= x1_abs + 1e-6) + & (grid_xy_c[:, 1] >= y0_abs - 1e-6) + & (grid_xy_c[:, 1] <= y1_abs + 1e-6) + ) + grid_xy_c = grid_xy_c[inside_bbox] + if len(grid_xy_c) == 0: + continue + inside_hull = hull_path_c.contains_points(grid_xy_c) + outside_hull_xy = grid_xy_c[~inside_hull] + if len(outside_hull_xy) == 0: + continue + # 기존 점과 너무 가까운 격자점(≤ step×0.4) 제외 — 중복 방지 + tree_ex = _cKDTreeC(pts[:, :2]) + d_ex, _ = tree_ex.query(outside_hull_xy, k=1) + new_only_xy = outside_hull_xy[d_ex > _step * 0.4] + if len(new_only_xy) == 0: + continue + new_z_c = _dem_sample_minus_offset(new_only_xy) + pts = np.vstack([pts, np.column_stack([new_only_xy, new_z_c])]) + total_phase_c += len(new_only_xy) + steps_log.append(f"{_step:.0f}m:{len(new_only_xy)}") if total_phase_c > 0: self.log( f" [Phase C] hull 바깥 × bbox 내부 점진 densify: " @@ -5380,7 +5395,8 @@ class SCanvasApp(ctk.CTk): if not vk: raise ValueError("Vworld 타일 사용 시 API Key가 필요합니다. 사이드바에 입력해주세요.") tile_url_template = tile_url_template.replace("{vworld_key}", vk) - satellite_img = self._download_xyz_tiles(tile_url_template, min_lat, min_lon, max_lat, max_lon) + with perf_block("위성 타일 다운로드+병합"): + satellite_img = self._download_xyz_tiles(tile_url_template, min_lat, min_lon, max_lat, max_lon) img_path = "satellite_temp.png" satellite_img.save(img_path) @@ -5861,28 +5877,29 @@ class SCanvasApp(ctk.CTk): ar_label = f"비율 {ar[0]}:{ar[1]}" if ar else f"뷰어 창 {self._saved_window_size or '미저장'}" self.log(f" 캡처 해상도: {out_w}x{out_h} ({ar_label} 기반)") - # 1. 위성 텍스처 3D 캡처 - self.capture_image = self._capture_from_camera(out_w, out_h, textured=True) - self.capture_image.save("capture_textured.png") - self.log(f" 캡처 완료: {self.capture_image.size}") + with perf_block("control map capture x3 + composite"): + # 1. 위성 텍스처 3D 캡처 + self.capture_image = self._capture_from_camera(out_w, out_h, textured=True) + self.capture_image.save("capture_textured.png") + self.log(f" 캡처 완료: {self.capture_image.size}") - # 2. Depth Map - self.log(" Depth Map 추출 중...") - self.depth_map = self._capture_depth_from_camera(out_w, out_h) - self.depth_map.save("depth_map.png") - self.log(" Depth Map 완료.") + # 2. Depth Map + self.log(" Depth Map 추출 중...") + self.depth_map = self._capture_depth_from_camera(out_w, out_h) + self.depth_map.save("depth_map.png") + self.log(" Depth Map 완료.") - # 3. Lineart Map - self.log(" Lineart Map 추출 중...") - self.lineart_map = self._capture_lineart_from_camera(out_w, out_h) - self.lineart_map.save("lineart_map.png") - self.log(" Lineart Map 완료.") + # 3. Lineart Map + self.log(" Lineart Map 추출 중...") + self.lineart_map = self._capture_lineart_from_camera(out_w, out_h) + self.lineart_map.save("lineart_map.png") + self.log(" Lineart Map 완료.") - # 4. 가이드 이미지 합성 - self.guide_image = self._compose_guide_image( - self.capture_image, self.depth_map, self.lineart_map - ) - self.guide_image.save("guide_composite.png") + # 4. 가이드 이미지 합성 + self.guide_image = self._compose_guide_image( + self.capture_image, self.depth_map, self.lineart_map + ) + self.guide_image.save("guide_composite.png") self.set_status("제어맵 추출 완료", "#2ECC71") self.btn_step4.configure(fg_color=["#3a7ebf", "#1f538d"])