Download code/labeling/src/step2_temporal_overlap.py from lsh9034/ci-net: direct link, hf CLI and curl.
- Browser
- Download file 22.7 kB
-
https://huggingface.co/lsh9034/ci-net/resolve/main/code/labeling/src/step2_temporal_overlap.py
- Command line
-
hf download hf://lsh9034/ci-net/code/labeling/src/step2_temporal_overlap.py
-
curl -L -o step2_temporal_overlap.py https://huggingface.co/lsh9034/ci-net/resolve/main/code/labeling/src/step2_temporal_overlap.py
22.7 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Temporal Overlap Tracking (static R-tree, backward link μ μ₯) | |
| κ°μ κ°μ | |
| - step1μ region_id.nc + *_clusters.pkl + *_rtree μΈλ±μ€λ₯Ό μ½μ΄ μκ° μμΆμ μ μννλ€. | |
| - νμ¬ μκ°μ κ°μ ν보 ν΄λ¬μ€ν°λ₯Ό seedλ‘ λκ³ , κ³Όκ±° ν΄λ¬μ€ν°μ ν½μ overlapμ κ³μ°ν΄ λμΌ μμ μ£ΌκΈ°λ₯Ό μ°κ²°νλ€. | |
| - ν° ν΄λ¬μ€ν°/μμ ν΄λ¬μ€ν° λΉμ¨, μ΅μ overlap λΉμ¨, μ΅λ backtracking μκ°μΌλ‘ κ³Όμ°κ²°μ μ ννλ€. | |
| - μ΅μ λ²μ μ μκ²½λ/λλ’° 보쑰νμΌμ μ¬μ©νμ§ μκ³ , BT μ λ ₯μ hsr νλμ step1 κ²°κ³Όλ§ μ¬μ©νλ€. | |
| * μ λ ₯ | |
| - step1 region κ²°κ³Ό: region_id.nc, *_clusters.pkl, *_rtree.{idx,dat} | |
| - BT/RADAR npy: concat_gk2a_radar_YYYYMMDDHHMM.npy (hsr μ¬μ©) | |
| * κ²°κ³Όλ¬Ό | |
| - *_label.nc: temporal overlapμΌλ‘ μ°κ²°λ label | |
| - *_visited.pkl: μ²λ¦¬ν ν΄λ¬μ€ν° λ°©λ¬Έ μν | |
| - *_links.pkl: {"νμ¬μκ°_id": ["κ³Όκ±°μκ°_id", ...]} λ§ν¬ μ 보 | |
| κ²½λ‘ μ€μ | |
| - REGION_ROOT/BT_ROOT/OUT_ROOTλ import μμ μλ λΉ λ¬Έμμ΄μ΄λ€. | |
| - mainμμ build/run/step2_temporal_overlapping_config.jsonμ μ½μ λ€ apply_config()κ° μ€μ κ²½λ‘λ‘ μ±μ΄λ€. | |
| - bt_rootκ° data_preprocess/result/res_2kmμ²λΌ L1B/L2 ν΄λμ΄λ©΄, μ€μ labeling μ λ ₯μΈ L1B/YYYYMMDDλ₯Ό μλ μ ννλ€. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import os, sys, pickle, time, gc | |
| from datetime import datetime, timedelta | |
| from collections import deque | |
| from typing import Dict, Tuple, List, Optional | |
| import numpy as np | |
| import xarray as xr | |
| from rtree import index | |
| from tqdm import tqdm | |
| # βββββββββββ κ²½λ‘ / νλΌλ―Έν° ββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") | |
| sys.stderr.reconfigure(encoding="utf-8", errors="replace") | |
| except AttributeError: | |
| pass | |
| CONFIG_NAME = "step2_temporal_overlapping_config.json" | |
| def _find_package_root(config_name: str) -> str: | |
| cur = os.path.dirname(os.path.abspath(__file__)) | |
| while True: | |
| if os.path.exists(os.path.join(cur, "build", "run", config_name)): | |
| return cur | |
| parent = os.path.dirname(cur) | |
| if parent == cur: | |
| return os.path.abspath(os.getcwd()) | |
| cur = parent | |
| ROOT = _find_package_root(CONFIG_NAME) | |
| RUN_DIR = os.path.join(ROOT, "build", "run") | |
| sys.path.insert(0, ROOT) | |
| from .config_utils import load_config | |
| def _resolve_path(path: str) -> str: | |
| if os.path.isabs(path): | |
| return path | |
| return os.path.abspath(os.path.join(RUN_DIR, path)) | |
| def _has_date_dirs(path: str) -> bool: | |
| if not os.path.isdir(path): | |
| return False | |
| return any( | |
| os.path.isdir(os.path.join(path, name)) and len(name) == 8 and name.isdigit() | |
| for name in os.listdir(path) | |
| ) | |
| def _resolve_data_root(path: str) -> str: | |
| if _has_date_dirs(path): | |
| return path | |
| for subdir in ("L1B", "l1b"): | |
| candidate = os.path.join(path, subdir) | |
| if _has_date_dirs(candidate): | |
| return candidate | |
| return path | |
| # Config μ μ© μ placeholder. μ€μ κ°μ apply_config()μμ μ±μ΄λ€. | |
| REGION_ROOT = "" | |
| BT_ROOT = "" | |
| OUT_ROOT = "" | |
| DBZ_THR = 35.0 | |
| STEP_MIN = 10 | |
| MAX_BACK_MINUTES = 120 | |
| MIN_OVERLAP_RATIO = 0.4 | |
| SIZE_RATIO_SMALL = 0.1 | |
| SIZE_RATIO_LARGE = 1.2 | |
| MAX_BACKTRACK_PIXELS = 10000 | |
| START_DATE = '202508010000' | |
| END_DATE = '202510312350' | |
| def apply_config(cfg: dict) -> None: | |
| global REGION_ROOT, BT_ROOT, OUT_ROOT, DBZ_THR, STEP_MIN, MAX_BACK_MINUTES | |
| global MIN_OVERLAP_RATIO, SIZE_RATIO_SMALL, SIZE_RATIO_LARGE, MAX_BACKTRACK_PIXELS | |
| global START_DATE, END_DATE | |
| REGION_ROOT = _resolve_path(cfg["region_root"]) | |
| BT_ROOT = _resolve_data_root(_resolve_path(cfg["bt_root"])) | |
| OUT_ROOT = _resolve_path(cfg["output_dir"]) | |
| os.makedirs(OUT_ROOT, exist_ok=True) | |
| DBZ_THR = float(cfg.get("dbz_thr", DBZ_THR)) | |
| STEP_MIN = int(cfg.get("step_min", STEP_MIN)) | |
| MAX_BACK_MINUTES = int(cfg.get("max_back_minutes", MAX_BACK_MINUTES)) | |
| MIN_OVERLAP_RATIO = float(cfg.get("min_overlap_ratio", MIN_OVERLAP_RATIO)) | |
| SIZE_RATIO_SMALL = float(cfg.get("size_ratio_small", SIZE_RATIO_SMALL)) | |
| SIZE_RATIO_LARGE = float(cfg.get("size_ratio_large", SIZE_RATIO_LARGE)) | |
| MAX_BACKTRACK_PIXELS = int(cfg.get("max_backtrack_pixels", MAX_BACKTRACK_PIXELS)) | |
| date_pairs = cfg.get("date_pairs") or [] | |
| if date_pairs: | |
| START_DATE = date_pairs[0][0] | |
| END_DATE = date_pairs[-1][1] | |
| else: | |
| START_DATE = cfg.get("start_date", START_DATE) | |
| END_DATE = cfg.get("end_date", END_DATE) | |
| # βββββββββββ ν¨ν€μ§( seg / clusters / rtree ) LRU μΊμ βββββββββββ | |
| # segλ int32 λΌλ²¨λ§΅μΌλ‘ λ³νν΄μ μΊμ (0=λ°°κ²½) | |
| _pkg_cache : dict[str, Tuple[np.ndarray, Dict[str,dict], index.Index]] = {} | |
| _hsr_cache: dict[str, np.ndarray] = {} | |
| # βββββββββββ ROI mask μ νΈ (ν΅μ¬) ββββββββββββββββββββββββββββββββ | |
| def _bbox_slices(bbox: List[int]) -> Tuple[slice, slice]: | |
| c1, r1, c2, r2 = bbox | |
| return slice(r1, r2 + 1), slice(c1, c2 + 1) | |
| def roi_mask_from_seg(seg_i: np.ndarray, cid_int: int, bbox: List[int]) -> Tuple[slice, slice, np.ndarray]: | |
| """ | |
| seg_i(int32, 0=λ°°κ²½) + bboxλ‘ ROIμμλ§ mask μμ± (벑ν°ν, νμ΄μ¬ for μμ) | |
| λ°ν: (rsl, csl, mask_roi[bool]) | |
| """ | |
| rsl, csl = _bbox_slices(bbox) | |
| mask_roi = (seg_i[rsl, csl] == cid_int) | |
| return rsl, csl, mask_roi | |
| def parse_cid_int(cid_str: str) -> int: | |
| # "YYYYMMDDHHMM_N" -> N | |
| return int(cid_str.split('_')[-1]) | |
| # βββββββββββ I/O: seg/clusters/rtree λ‘λ ββββββββββββββββββββββββ | |
| def load_pkg(ts: str) -> Tuple[np.ndarray, Dict[str, dict], index.Index] | None: | |
| """seg_map(int32), clusters(dict), rtree (or None)""" | |
| if ts in _pkg_cache: | |
| return _pkg_cache[ts] | |
| day = ts[:8] | |
| pref = os.path.join(REGION_ROOT, day, f"concat_gk2a_radar_{ts}") | |
| nc = pref + ".nc" | |
| pkl = pref + "_clusters.pkl" | |
| idxf = pref + "_rtree" | |
| if not (os.path.exists(nc) and os.path.exists(pkl) and os.path.exists(idxf + ".idx")): | |
| return None | |
| # region_id: float32, NaN background -> int32, 0 background | |
| seg_f = xr.open_dataset(nc)["region_id"].values # float32 (NaN background) | |
| seg_i = np.where(np.isnan(seg_f), 0, seg_f).astype(np.int32) | |
| with open(pkl, "rb") as f: | |
| clusters = pickle.load(f) | |
| rtree_idx = index.Index(idxf) | |
| _pkg_cache[ts] = (seg_i, clusters, rtree_idx) | |
| return _pkg_cache[ts] | |
| def load_hsr(ts: str) -> Optional[np.ndarray]: | |
| """concat_gk2a_radar_{ts}.npyμμ HSR νλ λ‘λ (dict['hsr']) μΊμ""" | |
| if ts in _hsr_cache: | |
| return _hsr_cache[ts] | |
| npy_bt = os.path.join(BT_ROOT, ts[:8], f"concat_gk2a_radar_{ts}.npy") | |
| if not os.path.exists(npy_bt): | |
| raise FileNotFoundError(f"HSR file missing: {npy_bt}") | |
| # --- νμΌ μμ μμΈ μ²λ¦¬ --- | |
| try: | |
| data = np.load(npy_bt, allow_pickle=True).item() | |
| except (EOFError, pickle.UnpicklingError) as e: | |
| print(f"[κ²½κ³ ] νμΌ μμμΌλ‘ λ‘λ μ€ν¨ (건λλ): {npy_bt} | μλ¬: {e}") | |
| return None | |
| except Exception as e: | |
| print(f"[κ²½κ³ ] μμμΉ λͺ»ν μλ¬λ‘ νμΌ λ‘λ μ€ν¨: {npy_bt} | μλ¬: {e}") | |
| return None | |
| if not isinstance(data, dict): | |
| raise ValueError(f"BT npy λ‘λ κ²°κ³Όκ° dictκ° μλλλ€: {type(data)} ({npy_bt})") | |
| if "hsr" not in data: | |
| raise KeyError(f"'hsr' key missing in BT npy: {npy_bt}") | |
| hsr = data["hsr"] | |
| _hsr_cache[ts] = hsr | |
| return hsr | |
| # βββββββββββ truth / visited / links μΊμ+I/O ββββββββββββββββββββ | |
| _truth_cache : dict[str, np.ndarray] = {} | |
| _vis_cache : dict[str, dict[str, bool]] = {} | |
| _link_cache : dict[str, Dict[str, List[str]]] = {} | |
| def _path(kind: str, ts: str) -> str: | |
| day = ts[:8] | |
| if kind == "truth": return os.path.join(OUT_ROOT, day, f"{ts}_label.nc") | |
| elif kind == "visited": return os.path.join(OUT_ROOT, day, f"{ts}_visited.pkl") | |
| elif kind == "links": return os.path.join(OUT_ROOT, day, f"{ts}_links.pkl") | |
| else: raise ValueError | |
| def load_truth(ts: str, shape: Tuple[int, int]) -> np.ndarray: | |
| if ts in _truth_cache: | |
| return _truth_cache[ts] | |
| p = _path("truth", ts) | |
| if os.path.exists(p): | |
| _truth_cache[ts] = xr.open_dataset(p)["label"].values | |
| else: | |
| _truth_cache[ts] = np.full(shape, np.nan, dtype=np.float32) | |
| return _truth_cache[ts] | |
| def save_truth(ts: str): | |
| if ts not in _truth_cache: | |
| return | |
| arr = _truth_cache[ts] | |
| output_path = _path("truth", ts) | |
| day = ts[:8] | |
| os.makedirs(os.path.join(OUT_ROOT, day), exist_ok=True) | |
| da = xr.DataArray( | |
| arr, | |
| dims=("r", "c"), | |
| coords={"r": np.arange(arr.shape[0]), "c": np.arange(arr.shape[1])}, | |
| name="label", | |
| ) | |
| da.to_dataset().to_netcdf( | |
| output_path, | |
| format="NETCDF4", | |
| encoding={"label": {"dtype": "float32", "zlib": True, "complevel": 9}}, | |
| ) | |
| def load_visited(ts: str) -> dict[str, bool]: | |
| if ts in _vis_cache: | |
| return _vis_cache[ts] | |
| p = _path("visited", ts) | |
| if os.path.exists(p): | |
| with open(p, "rb") as f: | |
| _vis_cache[ts] = pickle.load(f) | |
| else: | |
| _vis_cache[ts] = {} | |
| return _vis_cache[ts] | |
| def save_visited(ts: str): | |
| if ts not in _vis_cache: | |
| return | |
| output_path = _path("visited", ts) | |
| day = ts[:8] | |
| os.makedirs(os.path.join(OUT_ROOT, day), exist_ok=True) | |
| with open(output_path, "wb") as f: | |
| pickle.dump(_vis_cache[ts], f, pickle.HIGHEST_PROTOCOL) | |
| def load_links(ts: str) -> Dict[str, List[str]]: | |
| if ts in _link_cache: | |
| return _link_cache[ts] | |
| p = _path("links", ts) | |
| if os.path.exists(p): | |
| with open(p, "rb") as f: | |
| _link_cache[ts] = pickle.load(f) | |
| else: | |
| _link_cache[ts] = {} | |
| return _link_cache[ts] | |
| def save_links(ts: str): | |
| if ts not in _link_cache: | |
| return | |
| output_path = _path("links", ts) | |
| day = ts[:8] | |
| os.makedirs(os.path.join(OUT_ROOT, day), exist_ok=True) | |
| with open(output_path, "wb") as f: | |
| pickle.dump(_link_cache[ts], f, pickle.HIGHEST_PROTOCOL) | |
| # βββββββββββ μΊμ λ©λͺ¨λ¦¬ κ΄λ¦¬ βββββββββββββββββββββββββββββββββββββ | |
| def cleanup_cache(current_ts: str): | |
| try: | |
| current_dt = datetime.strptime(current_ts, "%Y%m%d%H%M") | |
| cutoff_dt = current_dt - timedelta(minutes=MAX_BACK_MINUTES + 60) | |
| cutoff_ts = cutoff_dt.strftime("%Y%m%d%H%M") | |
| total_removed = 0 | |
| to_remove = [ts for ts in _pkg_cache.keys() if ts < cutoff_ts] | |
| for ts in to_remove: | |
| _, _, rtree_idx = _pkg_cache[ts] | |
| try: | |
| rtree_idx.close() | |
| except Exception: | |
| pass | |
| del _pkg_cache[ts] | |
| total_removed += len(to_remove) | |
| for cache in (_truth_cache, _vis_cache, _link_cache, _hsr_cache): | |
| to_remove = [ts for ts in cache.keys() if ts < cutoff_ts] | |
| for ts in to_remove: | |
| del cache[ts] | |
| total_removed += len(to_remove) | |
| if total_removed > 0: | |
| gc.collect() | |
| print(f"[μΊμ μ 리] {total_removed}κ° μκ° λ°μ΄ν° μ κ±° (cutoff: {cutoff_ts})") | |
| except Exception as e: | |
| print(f"[μΊμ μ 리 μ€λ₯] {e}") | |
| # βββββββββββ μ±μλ νλ³ (mask μμ΄: seg+bbox ROI) βββββββββββββββββ | |
| def check_cluster_maturity(ts: str, seg_i: np.ndarray, cid_int: int, bbox: List[int]) -> int | None: | |
| """ | |
| HSRμμ ν΄λ¬μ€ν° λ΄λΆ μ΅λκ°μΌλ‘ μ±μλ νλ³. | |
| maskλ seg_i+bbox ROIμμ μμ±. | |
| Returns: 1(μ±μ: >=35dBZ), 2(λ―Έμ±μ), None(HSR νμΌ/λ°μ΄ν° μμ) | |
| """ | |
| try: | |
| hsr_full = load_hsr(ts) | |
| except (FileNotFoundError, KeyError, ValueError): | |
| return None | |
| if hsr_full is None: | |
| return None | |
| rsl, csl, mask_roi = roi_mask_from_seg(seg_i, cid_int, bbox) | |
| if not mask_roi.any(): | |
| return 2 | |
| hsr_roi = hsr_full[rsl, csl] | |
| vals = hsr_roi[mask_roi] | |
| vals = vals[~np.isnan(vals)] | |
| if vals.size == 0: | |
| return 2 | |
| return 1 if float(vals.max()) >= DBZ_THR else 2 | |
| # βββββββββββ λ©μΈ μ²λ¦¬ (ν νμμ€ν ) βββββββββββββββββββββββββββββββ | |
| def process_ts(ts: str) -> float: | |
| t0 = time.time() | |
| cleanup_cache(ts) | |
| pkg_now = load_pkg(ts) | |
| if pkg_now is None: | |
| print(f"[skip] {ts} (no package)") | |
| return 0.0 | |
| seg_now, clusters_now, _ = pkg_now | |
| h, w = seg_now.shape | |
| # ---- 1λ¨κ³: HSR μκ³κ°μΌλ‘ rainy cluster ν보 cid μμ§ --------- | |
| try: | |
| hsr = load_hsr(ts) | |
| except (FileNotFoundError, KeyError, ValueError): | |
| print(f"[skip] {ts} (no HSR or 'hsr' key for seed generation)") | |
| return 0.0 | |
| # hsrμ΄ NoneμΈ κ²½μ° (νμΌ μμ λ±) λ©μΈλ£¨ν μλ¬λ₯Ό λ§κΈ° μν΄ 0.0 λ°ν | |
| if hsr is None: | |
| print(f"[μλ¦Ό] {ts} μκ°μ HSR λ°μ΄ν° μμμΌλ‘ μ²λ¦¬λ₯Ό 건λλλλ€.") | |
| return 0.0 | |
| rs, cs = np.where(hsr >= DBZ_THR) | |
| cappi_seed_cids = set() | |
| for r, c in zip(rs, cs): | |
| cid = int(seg_now[r, c]) # seg_nowλ int32, 0=λ°°κ²½ | |
| if cid > 0: | |
| cappi_seed_cids.add(cid) | |
| if not cappi_seed_cids: | |
| print(f"[skip] {ts} (no rainy cluster)") | |
| return 0.0 | |
| # ---- 2λ¨κ³: κ°μ ν΄λ¬μ€ν°λ₯Ό λ°λ‘ seedλ‘ μ¬μ© (λλ’° νν°λ§ μ κ±°) ---- | |
| seed_cids = cappi_seed_cids | |
| # ---- cache κ°μ²΄ λ‘λ ------------------------------------------ | |
| load_truth(ts, (h, w)) | |
| load_visited(ts) | |
| links_now = load_links(ts) | |
| Q = deque() # cid_str only | |
| def enqueue_cluster(cid_str: str, cid_int: int, | |
| seg_ts: np.ndarray, | |
| bbox: List[int], | |
| is_seed: bool = False): | |
| """truth/visited/ν κ΄λ¦¬ (mask μμ΄ ROIμμλ§ truth μ±μ)""" | |
| ts_local = cid_str.split('_')[0] | |
| vdict = load_visited(ts_local) | |
| if cid_str in vdict: | |
| return | |
| vdict[cid_str] = is_seed | |
| truth = load_truth(ts_local, (h, w)) | |
| rsl, csl, mask_roi = roi_mask_from_seg(seg_ts, cid_int, bbox) | |
| # ROI viewμ μ§μ ν λΉ | |
| truth_roi = truth[rsl, csl] | |
| truth_roi[mask_roi] = cid_int | |
| Q.append(cid_str) | |
| # ---- seed enqueue --------------------------------------------- | |
| for cid_int in seed_cids: | |
| cid_str = f"{ts}_{cid_int}" | |
| if cid_str not in clusters_now: | |
| continue | |
| info = clusters_now[cid_str] | |
| # MAX_BACKTRACK_PIXELSλ₯Ό λλ ν΄λ¬μ€ν°λ νμ λ£μ§ μμ | |
| if info.get("pixel_count", 0) > MAX_BACKTRACK_PIXELS: | |
| continue | |
| enqueue_cluster(cid_str, cid_int, seg_now, info["bbox"], is_seed=True) | |
| links_now.setdefault(cid_str, []) | |
| ts_dt = datetime.strptime(ts, "%Y%m%d%H%M") | |
| # ---- μκ° μλ°©ν₯ BFS ------------------------------------------ | |
| while Q: | |
| cur_cid = Q.popleft() | |
| cur_ts = cur_cid.split('_')[0] | |
| cur_int = parse_cid_int(cur_cid) | |
| pkg_cur = load_pkg(cur_ts) | |
| if pkg_cur is None: | |
| continue | |
| seg_cur, clusters_cur, _ = pkg_cur | |
| if cur_cid not in clusters_cur: | |
| continue | |
| cur_info = clusters_cur[cur_cid] | |
| c1, r1, c2, r2 = cur_info["bbox"] | |
| # ts - cur_ts μ°¨μ΄κ° λ무 ν¬λ©΄ stop | |
| cur_dt = datetime.strptime(cur_ts, "%Y%m%d%H%M") | |
| if (ts_dt - cur_dt).total_seconds() / 60 > MAX_BACK_MINUTES: | |
| link_dict = load_links(cur_ts) | |
| link_dict.setdefault(cur_cid, []) | |
| save_truth(cur_ts); save_visited(cur_ts); save_links(cur_ts) | |
| continue | |
| prev_dt = cur_dt - timedelta(minutes=STEP_MIN) | |
| prev_ts = prev_dt.strftime("%Y%m%d%H%M") | |
| visited_prev = load_visited(prev_ts) | |
| overlaps: List[str] = [] | |
| def process_prev_clusters(pkg_prev) -> List[str]: | |
| """μ΄μ μμ ν΄λ¬μ€ν°λ€ μ²λ¦¬ (mask μμ΄ seg ROI λΉκ΅λ‘ overlap κ³μ°)""" | |
| if pkg_prev is None: | |
| return [] | |
| seg_prev, clusters_prev, rtree_prev = pkg_prev | |
| local_overlaps: List[str] = [] | |
| # bbox κ΅μ°¨ νλ³΄λ§ R-treeλ‘ κ°μ Έμ΄ | |
| for prev_int in rtree_prev.intersection((c1, r1, c2, r2)): | |
| prev_cid = f"{prev_ts}_{prev_int}" | |
| if prev_cid not in clusters_prev: | |
| continue | |
| prev_info = clusters_prev[prev_cid] | |
| p1, q1, p2, q2 = prev_info["bbox"] | |
| ic1 = max(c1, p1); ir1 = max(r1, q1) | |
| ic2 = min(c2, p2); ir2 = min(r2, q2) | |
| if ic1 > ic2 or ir1 > ir2: | |
| continue | |
| # overlap: seg κ° λΉκ΅λ‘ λ°λ‘ κ³μ° (mask λ§λ€ νμ μμ) | |
| cur_roi = seg_cur[ir1:ir2+1, ic1:ic2+1] | |
| prev_roi = seg_prev[ir1:ir2+1, ic1:ic2+1] | |
| overlap_count = np.count_nonzero((cur_roi == cur_int) & (prev_roi == prev_int)) | |
| if overlap_count == 0: | |
| continue | |
| # 쑰건 1: Overlap ratio (overlap / prev_cluster_pixels) | |
| prev_pix = prev_info.get("pixel_count", 0) | |
| if prev_pix <= 0: | |
| continue | |
| if (overlap_count / prev_pix) < MIN_OVERLAP_RATIO: | |
| continue | |
| # 쑰건 2/3: ν¬κΈ° λΉμ¨ νν° | |
| cur_pix = cur_info.get("pixel_count", 0) | |
| if cur_pix <= 0: | |
| continue | |
| if prev_pix < SIZE_RATIO_SMALL * cur_pix: | |
| continue | |
| if prev_pix > SIZE_RATIO_LARGE * cur_pix: | |
| continue | |
| local_overlaps.append(prev_cid) | |
| # μμ§ λ°©λ¬Έ μ λμκ³ truthμμ λΉμ΄μμΌλ©΄ νμ μΆκ° | |
| if prev_cid not in visited_prev: | |
| prev_truth_map = load_truth(prev_ts, (h, w)) | |
| # prev ν΄λ¬μ€ν° ROI maskλ‘ κΈ°μ‘΄ truth μ±μμ§ μ¬λΆ μ²΄ν¬ | |
| rsl, csl, mask_roi = roi_mask_from_seg(seg_prev, prev_int, prev_info["bbox"]) | |
| if mask_roi.any(): | |
| existing_values = prev_truth_map[rsl, csl][mask_roi] | |
| if not np.any(~np.isnan(existing_values)): | |
| enqueue_cluster(prev_cid, prev_int, seg_prev, prev_info["bbox"], is_seed=False) | |
| return local_overlaps | |
| pkg_prev = load_pkg(prev_ts) | |
| overlaps = process_prev_clusters(pkg_prev) | |
| # ---- λ§ν¬ λμ μ μ₯ -------------------------------------- | |
| link_dict = load_links(cur_ts) | |
| link_dict.setdefault(cur_cid, []) | |
| link_dict[cur_cid] = list(set(link_dict[cur_cid]) | set(overlaps)) | |
| save_truth(cur_ts); save_visited(cur_ts); save_links(cur_ts) | |
| print(f"β {ts} (total visited {len(load_visited(ts))})") | |
| return time.time() - t0 | |
| # βββββββββββ μ 체 λ μ§ μν βββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Link cloud objects through time.") | |
| parser.add_argument("--config", required=True, help="JSON configuration path") | |
| parser.add_argument("--device", default=None, help="Accepted for a common CLI; this stage runs on CPU") | |
| parser.add_argument("--output-dir", default=None, help="Override output_root") | |
| args = parser.parse_args() | |
| config_path = os.path.abspath(args.config) | |
| RUN_DIR = os.path.dirname(config_path) | |
| cfg = load_config(config_path) | |
| if args.output_dir: | |
| cfg["output_dir"] = os.path.abspath(args.output_dir) | |
| apply_config(cfg) | |
| nc_files: List[str] = [] | |
| for root, _, fs in os.walk(REGION_ROOT): | |
| nc_files += [ | |
| os.path.join(root, f) | |
| for f in fs | |
| if f.endswith(".nc") and "concat_gk2a_radar_" in f | |
| ] | |
| nc_files.sort(key=lambda p: os.path.basename(p)[-15:-3]) | |
| # λ μ§ νν°λ§ | |
| if START_DATE is not None or END_DATE is not None: | |
| original_count = len(nc_files) | |
| filtered_files = [] | |
| for f in nc_files: | |
| ts = os.path.basename(f)[-15:-3] | |
| if START_DATE is not None and ts < START_DATE: | |
| continue | |
| if END_DATE is not None and ts > END_DATE: | |
| break | |
| filtered_files.append(f) | |
| nc_files = filtered_files | |
| print(f"\nλ μ§ νν°λ§: {original_count}κ° β {len(nc_files)}κ° νμΌ") | |
| if START_DATE: print(f"μμ λ μ§: {START_DATE}") | |
| if END_DATE: print(f"μ’ λ£ λ μ§: {END_DATE}") | |
| if not nc_files: | |
| print("μ²λ¦¬ν νμΌμ΄ μμ΅λλ€.") | |
| raise SystemExit(0) | |
| total_time = 0.0 | |
| total_files = 0 | |
| t0 = datetime.now() | |
| print(f"\nμ΄ {len(nc_files)}κ° νμΌ μ²λ¦¬ μμ") | |
| print("=" * 50) | |
| # βββββββββββ λ©μΈ 루ν ββββββββββββββββββββββββββββββββββββββββ | |
| for ncp in tqdm(nc_files, desc="TIMESTEPS"): | |
| ts = os.path.basename(ncp)[-15:-3] | |
| file_time = process_ts(ts) | |
| # NoneType μλ¬ λ°©μ§μ© λ°©μ΄ μ½λ μΆκ° | |
| if file_time is None: | |
| continue | |
| if file_time > 0: | |
| total_time += file_time | |
| total_files += 1 | |
| avg_time = total_time / total_files | |
| print(f"ββ νμΌ μ²λ¦¬ μκ°: {file_time:.1f}μ΄ (νκ· : {avg_time:.1f}μ΄)") | |
| total_elapsed = (datetime.now() - t0).total_seconds() | |
| print("\n" + "=" * 50) | |
| print("μ²λ¦¬ μλ£!") | |
| print(f"μ΄ μ²λ¦¬ νμΌ μ: {total_files}κ°") | |
| print(f"μ΄ μμ μκ°: {total_elapsed:.1f}μ΄") | |
| if total_files > 0: | |
| print(f"νμΌλΉ νκ· μ²λ¦¬ μκ°: {total_time / total_files:.1f}μ΄") | |
| print("=" * 50 + "\n") | |