{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13762876,"sourceType":"competition"},{"sourceId":12780021,"sourceType":"datasetVersion","datasetId":8079690}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Pipeline\n1. **DICOM → 3D Volume**: Normalize to `(32, 384, 384)`\n2. **EfficientNetV2-S**: 32-channel input, 14 binary outputs\n3. **Ensemble**: Average 5-fold predictions","metadata":{}},{"cell_type":"code","source":"pip install -q timm==1.0.9 albumentations==1.4.4 pydicom==2.4.4 polars==1.6.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T01:34:13.261333Z","iopub.execute_input":"2025-09-23T01:34:13.261603Z","iopub.status.idle":"2025-09-23T01:34:21.282585Z","shell.execute_reply.started":"2025-09-23T01:34:13.261582Z","shell.execute_reply":"2025-09-23T01:34:21.281811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile demo1.py\n# ← 这里粘贴“demo1_optimized.py”的全部代码\n\n\nimport os, math, gc, warnings\n\nfrom pathlib import Path\nfrom typing import List, Tuple, Optional\nimport numpy as np\nimport pydicom\nimport cv2\n\nwarnings.filterwarnings(\"ignore\")\n\n# =========================\n# Helpers\n# =========================\ndef _to_hu(ds) -> np.ndarray:\n    \"\"\"Convert raw DICOM pixels → HU using RescaleSlope/Intercept (safe defaults).\"\"\"\n    arr = ds.pixel_array.astype(np.float32, copy=False)\n    slope = float(getattr(ds, \"RescaleSlope\", 1.0))\n    inter = float(getattr(ds, \"RescaleIntercept\", 0.0))\n    return arr * slope + inter\n\ndef _window(img: np.ndarray, center: float, width: float) -> np.ndarray:\n    lo, hi = center - width/2.0, center + width/2.0\n    out = np.clip(img, lo, hi)\n    out = (out - lo) / (hi - lo + 1e-6)\n    return out.astype(np.float32, copy=False)\n\ndef _safe_read_dcm(fp: Path):\n    try:\n        return pydicom.dcmread(str(fp), stop_before_pixels=False, force=True)\n    except Exception:\n        return None\n\ndef _zpos(ds) -> float:\n    ipp = getattr(ds, \"ImagePositionPatient\", None)\n    if ipp is not None and len(ipp) == 3:\n        try:\n            return float(ipp[2])\n        except Exception:\n            pass\n    # fallback\n    try:\n        return float(getattr(ds, \"SliceLocation\", 0.0))\n    except Exception:\n        return 0.0\n\n# =========================\n# Series loader\n# =========================\ndef load_series(series_dir: str) -> List[pydicom.dataset.FileDataset]:\n    \"\"\"Load a DICOM series directory and sort by z. Skips unreadable files.\"\"\"\n    p = Path(series_dir)\n    files = [x for x in p.glob(\"*.dcm\")]\n    dsets = []\n    for f in files:\n        ds = _safe_read_dcm(f)\n        if ds is not None and getattr(ds, \"PixelData\", None) is not None:\n            dsets.append(ds)\n    if not dsets:\n        raise FileNotFoundError(f\"No readable DICOM files under: {series_dir}\")\n    dsets.sort(key=_zpos)\n    return dsets\n\ndef discover_series(root: str) -> List[str]:\n    \"\"\"Return series folders that contain at least one DICOM (useful for local debug).\"\"\"\n    rootp = Path(root)\n    out = []\n    for sp in rootp.iterdir():\n        if sp.is_dir() and any(sp.glob(\"*.dcm\")):\n            out.append(str(sp))\n    return out\n\n# =========================\n# 32‑channel builder\n# =========================\ndef build_32ch_volume(series_dir: str, target_hw=(512, 512)) -> np.ndarray:\n    \"\"\"Build a 32‑channel 2.5D representation from a DICOM series.\n\n    Strategy (fast & robust, no extra deps):\n      • Convert slices → HU, resize to target (H,W)\n      • Multi‑window views (brain, bone, vessel, soft) × aggregate (MIP/mean/std/p90)\n      • Edge/texture cues (SobelX/Y, gradient magnitude) on the brain window\n      • Z‑axis compressions (MIP/mean) to encode 3D context\n      • Zero‑pad/truncate to exactly 32 channels (deterministic ordering)\n\n    Returns:\n      np.float32 [32, H, W] values in [0, 1]\n    \"\"\"\n    H, W = target_hw\n    dsets = load_series(series_dir)\n    # stack to [Z,H,W] HU\n    vol = [_to_hu(ds) for ds in dsets]\n    vol = np.stack(vol, 0)  # [Z,h,w]\n    # resize all slices for consistent spatial size\n    zs, h0, w0 = vol.shape\n    if (h0, w0) != (H, W):\n        vol = np.stack([cv2.resize(s, (W, H), interpolation=cv2.INTER_AREA) for s in vol], 0)\n\n    # windows\n    WIN_LIST = [\n        (40, 80),    # brain\n        (600, 2800), # bone\n        (100, 700),  # vessel/CTA-ish\n        (50, 400),   # soft\n    ]\n    chans = []\n\n    for (c, w) in WIN_LIST:\n        vw = _window(vol, c, w)  # [Z,H,W] in [0,1]\n        # z-aggregations\n        mip  = vw.max(0)          # [H,W]\n        mean = vw.mean(0)\n        std  = vw.std(0)\n        p90  = np.percentile(vw, 90, axis=0).astype(np.float32)\n        chans += [mip, mean, std, p90]\n\n    # gradient/edge cues on brain window\n    brain = _window(vol, 40, 80).mean(0)  # [H,W]\n    sobelx = cv2.Sobel(brain, cv2.CV_32F, 1, 0, ksize=3)\n    sobely = cv2.Sobel(brain, cv2.CV_32F, 0, 1, ksize=3)\n    gradmag = np.sqrt(sobelx**2 + sobely**2)\n    # normalize safely\n    def _nz(x):\n        m, M = float(x.min()), float(x.max())\n        if M - m < 1e-6:\n            return np.zeros_like(x, dtype=np.float32)\n        return ((x - m) / (M - m)).astype(np.float32)\n    chans += [_nz(sobelx), _nz(sobely), _nz(gradmag)]\n\n    # z-compressions on vessel window (extra context)\n    vessel = _window(vol, 100, 700)\n    z_mip = vessel.max(0)\n    z_mean = vessel.mean(0)\n    z_min = vessel.min(0)\n    z_p95 = np.percentile(vessel, 95, axis=0).astype(np.float32)\n    chans += [z_mip, z_mean, z_min, z_p95]\n\n    # fill until 32 channels with additional stats from soft window\n    soft = _window(vol, 50, 400)\n    extra = [\n        soft.min(0), soft.var(0), np.percentile(soft, 25, axis=0).astype(np.float32),\n        np.percentile(soft, 75, axis=0).astype(np.float32)\n    ]\n    chans += extra\n\n    # ensure length == 32 (deterministic):\n    if len(chans) >= 32:\n        chans = chans[:32]\n    else:\n        # repeat first ones to reach 32 (rare)\n        k = 32 - len(chans)\n        chans += [chans[i % len(chans)] for i in range(k)]\n\n    X = np.stack(chans, 0).astype(np.float32)  # [32,H,W]\n    # clip to [0,1]\n    X = np.clip(X, 0.0, 1.0)\n    return X\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-23T01:57:37.743293Z","iopub.execute_input":"2025-09-23T01:57:37.744011Z","iopub.status.idle":"2025-09-23T01:57:37.750525Z","shell.execute_reply.started":"2025-09-23T01:57:37.743986Z","shell.execute_reply":"2025-09-23T01:57:37.749732Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n%%writefile tempate_optimized.py\n\nimport os, sys, gc, math, warnings, glob, json, random\nfrom pathlib import Path\nfrom typing import List, Dict, Optional, Tuple\nwarnings.filterwarnings(\"ignore\")\n\n# ----- Third‑party\nimport numpy as np\nimport pandas as pd\nimport polars as pl\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\n# optional: auto install timm if missing\ntry:\n    import timm\nexcept Exception:\n    os.system(\"pip install -q timm==1.0.9\")\n    import timm\n\n# Kaggle evaluation server (available in competition environment)\ntry:\n    import kaggle_evaluation\nexcept Exception:\n    kaggle_evaluation = None\n\n# ----- Local: 32‑ch builder\nfrom demo1 import build_32ch_volume\n\n# =============================\n# Config\n# =============================\nclass CFG:\n    seed = 3407\n    in_chans = 32\n    num_classes = 14\n    model_name = \"tf_efficientnetv2_m\"  # good capacity; try _s for smaller\n    image_size = (512, 512)\n    epochs = 8\n    batch_size = 8\n    lr = 2e-4\n    weight_decay = 1e-4\n    folds = 5\n    # data roots\n    COMP_ROOT = \"/kaggle/input/rsna-intracranial-aneurysm-detection\"\n    TRAIN_CSV = f\"{COMP_ROOT}/train.csv\"\n    SAMPLE_SUB = f\"{COMP_ROOT}/sample_submission.csv\"\n    TRAIN_IMG_ROOT = f\"{COMP_ROOT}/train_images\"\n    TEST_IMG_ROOT  = f\"{COMP_ROOT}/test_images\"\n    # weights discover pattern (attach your dataset under /kaggle/input/...)\n    WEIGHTS_GLOB = \"/kaggle/input/**/**/*.pth\"\n\n# reproducibility\ndef set_seed(seed=3407):\n    random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\nset_seed(CFG.seed)\n\n# =============================\n# Labels from sample_submission (robust order)\n# =============================\ndef load_label_order(sample_csv_path: str) -> Tuple[str, List[str]]:\n    df = pd.read_csv(sample_csv_path)\n    id_col = df.columns[0]\n    label_cols = list(df.columns[1:])\n    return id_col, label_cols\n\nID_COL, LABEL_COLS = load_label_order(CFG.SAMPLE_SUB)\n\n# Score weights: present (index 0) weighs 13, others 1\nCLASS_WEIGHTS = torch.tensor([13.0] + [1.0] * (len(LABEL_COLS)-1), dtype=torch.float32)\n\n# =============================\n# Dataset\n# =============================\nclass RSNADataset(Dataset):\n    def __init__(self, df: pd.DataFrame, img_root: str, transforms=None):\n        self.df = df.reset_index(drop=True)\n        self.img_root = img_root\n        self.transforms = transforms\n        self.ids = self.df[ID_COL].tolist()\n        self.has_labels = all(c in self.df.columns for c in LABEL_COLS)\n\n    def __len__(self): return len(self.ids)\n\n    def __getitem__(self, i):\n        sid = self.ids[i]\n        series_dir = os.path.join(self.img_root, sid)\n        x = build_32ch_volume(series_dir, target_hw=CFG.image_size).astype(np.float32)  # [32,H,W] in [0,1]\n        if self.transforms:\n            # transforms should accept [C,H,W] float np and return same\n            x = self.transforms(x)\n        # to tensor\n        x = torch.from_numpy(x)  # [C,H,W] float32\n        if self.has_labels:\n            y = self.df.loc[i, LABEL_COLS].values.astype(np.float32)\n            y = torch.from_numpy(y)  # [14]\n            return x, y, sid\n        else:\n            return x, sid\n\n# =============================\n# Model\n# =============================\ndef build_model():\n    model = timm.create_model(\n        CFG.model_name,\n        pretrained=True,\n        in_chans=CFG.in_chans,\n        num_classes=len(LABEL_COLS),\n        drop_path_rate=0.2,\n    )\n    return model\n\ndef weighted_bce_with_logits(logits, targets):\n    per_class = F.binary_cross_entropy_with_logits(logits, targets, reduction=\"none\")\n    w = CLASS_WEIGHTS.to(logits.device).unsqueeze(0)\n    return (per_class * w).mean()\n\n# =============================\n# Training utilities\n# =============================\ndef make_loader(ds, bs, train=True):\n    return DataLoader(\n        ds, batch_size=bs, shuffle=train,\n        num_workers=max(2, os.cpu_count()//2),\n        pin_memory=True, persistent_workers=True,\n        prefetch_factor=4, drop_last=train\n    )\n\n@torch.no_grad()\ndef valid_one_epoch(model, loader, device):\n    model.eval()\n    tot, n = 0.0, 0\n    for x, y, _ in loader:\n        x = x.to(device, non_blocking=True).to(memory_format=torch.channels_last)\n        y = y.to(device, non_blocking=True)\n        logits = model(x)\n        loss = weighted_bce_with_logits(logits, y)\n        bs = x.size(0)\n        tot += loss.item() * bs; n += bs\n    return tot / max(1, n)\n\nfrom torch.cuda.amp import autocast, GradScaler\ndef train_one_epoch(model, loader, device, optimizer, scaler):\n    model.train()\n    tot, n = 0.0, 0\n    for x, y, _ in loader:\n        x = x.to(device, non_blocking=True).to(memory_format=torch.channels_last)\n        y = y.to(device, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        with autocast(dtype=torch.bfloat16):\n            logits = model(x)\n            loss = weighted_bce_with_logits(logits, y)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer); scaler.update()\n        tot += loss.item() * x.size(0); n += x.size(0)\n    return tot / max(1, n)\n\ndef train_kfold(save_root=\"/kaggle/working/weights\"):\n    \"\"\"Minimal K‑fold training using SeriesInstanceUID splits (no leakage).\"\"\"\n    os.makedirs(save_root, exist_ok=True)\n    # read train.csv\n    df = pd.read_csv(CFG.TRAIN_CSV)\n    # patient‑level split can be approximated by SeriesInstanceUID stratification here (simple baseline)\n    ids = df[ID_COL].unique().tolist()\n    ids.sort()\n    from sklearn.model_selection import KFold\n    kf = KFold(n_splits=CFG.folds, shuffle=True, random_state=CFG.seed)\n\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    for fold, (tr_idx, va_idx) in enumerate(kf.split(ids), 1):\n        tr_ids = [ids[i] for i in tr_idx]; va_ids = [ids[i] for i in va_idx]\n        tr_df = df[df[ID_COL].isin(tr_ids)].reset_index(drop=True)\n        va_df = df[df[ID_COL].isin(va_ids)].reset_index(drop=True)\n\n        ds_tr = RSNADataset(tr_df, CFG.TRAIN_IMG_ROOT)\n        ds_va = RSNADataset(va_df, CFG.TRAIN_IMG_ROOT)\n        dl_tr = make_loader(ds_tr, CFG.batch_size, train=True)\n        dl_va = make_loader(ds_va, CFG.batch_size, train=False)\n\n        model = build_model().to(device).to(memory_format=torch.channels_last)\n        optimizer = torch.optim.AdamW(model.parameters(), lr=CFG.lr, weight_decay=CFG.weight_decay)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=CFG.epochs)\n        scaler = GradScaler(enabled=True)\n\n        best = 1e9; best_path = os.path.join(save_root, f\"{CFG.model_name}_fold{fold}.pth\")\n        for ep in range(1, CFG.epochs+1):\n            tr_loss = train_one_epoch(model, dl_tr, device, optimizer, scaler)\n            va_loss = valid_one_epoch(model, dl_va, device)\n            scheduler.step()\n            if va_loss < best:\n                best = va_loss\n                torch.save({\"model\": model.state_dict()}, best_path)\n            print(f\"[Fold {fold}] Epoch {ep}/{CFG.epochs}  train {tr_loss:.5f}  valid {va_loss:.5f}  best {best:.5f}\")\n        del model; gc.collect(); torch.cuda.empty_cache()\n\n# =============================\n# Inference (TTA + multi‑fold ensemble autodiscovery)\n# =============================\n@torch.no_grad()\ndef _predict_logits(model, x: torch.Tensor, device) -> torch.Tensor:\n    model.eval()\n    aug = [\n        lambda t: t,\n        lambda t: torch.flip(t, dims=[-1]),        # H‑flip\n        lambda t: torch.flip(t, dims=[-2]),        # V‑flip\n        lambda t: t.transpose(-1, -2),             # 90°\n    ]\n    probs = []\n    for f in aug:\n        xt = f(x).to(device, non_blocking=True).to(memory_format=torch.channels_last)\n        logits = model(xt)\n        probs.append(torch.sigmoid(logits).float().cpu())\n    return torch.stack(probs).mean(0)  # [B,C]\n\ndef discover_weight_paths(pattern=CFG.WEIGHTS_GLOB) -> List[str]:\n    paths = glob.glob(pattern, recursive=True)\n    paths = [p for p in paths if p.endswith(\".pth\")]\n    # limit to reasonable number\n    paths = sorted(paths)[:16]\n    return paths\n\ndef load_models():\n    \"\"\"Load all fold weights found under /kaggle/input/**.pth\"\"\"\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    wpaths = discover_weight_paths()\n    models = []\n    if not wpaths:\n        print(\"⚠️ No .pth weights found under /kaggle/input/** — running with random‑init (for debug only).\")\n        models = [build_model().to(device).eval()]\n    else:\n        for wp in wpaths:\n            m = build_model().to(device)\n            state = torch.load(wp, map_location=device)\n            key = \"model\" if isinstance(state, dict) and \"model\" in state else None\n            if key: m.load_state_dict(state[key], strict=False)\n            else:   m.load_state_dict(state, strict=False)\n            m.eval()\n            models.append(m)\n        print(f\"Loaded {len(models)} model(s):\")\n        for p in wpaths: print(\"  •\", p)\n    return models, device\n\nMODELS, DEVICE = load_models()\n\ndef predict(series_path: str) -> Dict[str, float]:\n    \"\"\"Main entry for Kaggle evaluation server.\n    Args:\n      series_path: path to a folder with .dcm files for a single SeriesInstanceUID\n    Returns:\n      {label: probability in [0,1]} for LABEL_COLS order.\n    \"\"\"\n    x = build_32ch_volume(series_path, target_hw=CFG.image_size)   # [32,H,W] float [0,1]\n    x = torch.from_numpy(x).unsqueeze(0)                           # [1,32,H,W]\n    # ensemble\n    probs_ens = []\n    for m in MODELS:\n        probs = _predict_logits(m, x, DEVICE)  # [1,C]\n        probs_ens.append(probs)\n    p = torch.stack(probs_ens).mean(0).numpy().reshape(-1).tolist()\n    return {LABEL_COLS[i]: float(p[i]) for i in range(len(LABEL_COLS))}\n\n# =============================\n# Local gateway (debug) or competition server\n# =============================\ndef run():\n    if kaggle_evaluation is not None and os.getenv(\"KAGGLE_IS_COMPETITION_RERUN\"):\n        # In competition\n        server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n        server.serve()\n    else:\n        # Local debug: iterate test_images to build a preview submission\n        test_root = CFG.TEST_IMG_ROOT\n        if not os.path.isdir(test_root):\n            print(\"Local preview: test_images not found; writing empty submission.parquet\")\n            pl.DataFrame({ID_COL: [], **{c: [] for c in LABEL_COLS}})\\\n                .write_parquet(\"/kaggle/working/submission.parquet\")\n            return\n        series_ids = [d.name for d in Path(test_root).iterdir() if d.is_dir()]\n        rows = []\n        for sid in series_ids:\n            series_path = os.path.join(test_root, sid)\n            y = predict(series_path)\n            rows.append({ID_COL: sid, **y})\n        df = pl.DataFrame(rows)\n        outp = \"/kaggle/working/submission.parquet\"\n        df.write_parquet(outp)\n        print(f\"Local submission preview → {outp}\")\n\nif __name__ == \"__main__\":\n    run()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T01:56:29.64621Z","iopub.execute_input":"2025-09-23T01:56:29.646822Z","iopub.status.idle":"2025-09-23T01:56:29.656992Z","shell.execute_reply.started":"2025-09-23T01:56:29.646775Z","shell.execute_reply":"2025-09-23T01:56:29.656247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Patch tempate_optimized.py to read labels from train.csv (API competition: no sample_submission.csv)\nfrom pathlib import Path\nimport re\n\npath = Path(\"tempate_optimized.py\")\nsrc  = path.read_text(encoding=\"utf-8\")\n\n# 1) 注释掉原来那行：从 sample_submission 取列名\nsrc = src.replace(\n    \"ID_COL, LABEL_COLS = load_label_order(CFG.SAMPLE_SUB)\",\n    \"# (API comp) disable sample_submission\\n# ID_COL, LABEL_COLS = load_label_order(CFG.SAMPLE_SUB)\"\n)\n\n# 2) 注释掉紧随其后的 CLASS_WEIGHTS 初始化（稍后我们重设）\nsrc = src.replace(\n    \"CLASS_WEIGHTS = torch.tensor([13.0] + [1.0] * (len(LABEL_COLS)-1), dtype=torch.float32)\",\n    \"# CLASS_WEIGHTS will be set after labels\"\n)\n\n# 3) 在文件末尾追加：用 train.csv 自动发现 ID/标签列\nhook = r'''\n# --- API competition: discover labels from train.csv (no sample_submission.csv) ---\ndef _load_labels_from_train():\n    import os, pandas as pd\n    root = CFG.COMP_ROOT\n    df = pd.read_csv(os.path.join(root, \"train.csv\"))\n    # 官方数据的系列唯一标识\n    id_col = \"SeriesInstanceUID\"\n    assert id_col in df.columns, f\"{id_col} not found in train.csv columns: {list(df.columns)[:12]}\"\n\n    ignore = {id_col, \"Modality\", \"PatientAge\", \"PatientSex\"}\n    label_cols = []\n    for c in df.columns:\n        if c in ignore:\n            continue\n        # 只保留 0/1 的数值列作为标签\n        if pd.api.types.is_numeric_dtype(df[c]):\n            vals = set(pd.Series(df[c]).dropna().unique().tolist())\n            if vals.issubset({0,1}):\n                label_cols.append(c)\n\n    # 确保“存在”在最前（若没有该列，就由 13 个部位合成）\n    present_name = \"Aneurysm Present\"\n    if present_name in df.columns:\n        if present_name in label_cols:\n            label_cols = [present_name] + [c for c in label_cols if c != present_name]\n        else:\n            label_cols = [present_name] + label_cols\n    else:\n        df[present_name] = (df[label_cols].sum(axis=1) > 0).astype(int)\n        label_cols = [present_name] + label_cols\n\n    return id_col, label_cols\n\nID_COL, LABEL_COLS = _load_labels_from_train()\nCLASS_WEIGHTS = torch.tensor([13.0] + [1.0] * (len(LABEL_COLS)-1), dtype=torch.float32)\n'''\nsrc = src + \"\\n\" + hook\n\npath.write_text(src, encoding=\"utf-8\")\nprint(\"✅ Patched tempate_optimized.py to use train.csv labels.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T01:46:31.018685Z","iopub.execute_input":"2025-09-23T01:46:31.019359Z","iopub.status.idle":"2025-09-23T01:46:31.026056Z","shell.execute_reply.started":"2025-09-23T01:46:31.019335Z","shell.execute_reply":"2025-09-23T01:46:31.02535Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import importlib, os, random, json\nimport tempate_optimized as T\nimportlib.reload(T)\n\n# 随机挑 1 个 test series，确认能前向\nroot = T.CFG.TEST_IMG_ROOT\nsid = random.choice([d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))])\nprint(\"Try:\", sid)\ny = T.predict(os.path.join(root, sid))\nprint(\"Sample preds:\", json.dumps({k: round(v,4) for k,v in list(y.items())[:5]}, ensure_ascii=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T01:57:57.508169Z","iopub.execute_input":"2025-09-23T01:57:57.508479Z","iopub.status.idle":"2025-09-23T01:57:57.532143Z","shell.execute_reply.started":"2025-09-23T01:57:57.508458Z","shell.execute_reply":"2025-09-23T01:57:57.531095Z"}},"outputs":[],"execution_count":null}]}