{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13747926,"sourceType":"competition"},{"sourceId":12925487,"sourceType":"datasetVersion","datasetId":8178911},{"sourceId":12934565,"sourceType":"datasetVersion","datasetId":8184998},{"sourceId":13080675,"sourceType":"datasetVersion","datasetId":7979194},{"sourceId":226368929,"sourceType":"kernelVersion"},{"sourceId":259333705,"sourceType":"kernelVersion"}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install ultralytics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:37:49.542289Z","iopub.execute_input":"2025-09-16T19:37:49.542632Z","iopub.status.idle":"2025-09-16T19:37:57.672717Z","shell.execute_reply.started":"2025-09-16T19:37:49.542605Z","shell.execute_reply":"2025-09-16T19:37:57.671382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, math, random\nimport ast\nfrom tqdm import tqdm\nfrom pathlib import Path\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image, ImageDraw, ImageFont\nimport pandas as pd\nimport torch\nimport pydicom\nfrom scipy.ndimage import zoom\nfrom ultralytics import YOLO","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:37:57.674471Z","iopub.execute_input":"2025-09-16T19:37:57.67492Z","iopub.status.idle":"2025-09-16T19:38:04.870916Z","shell.execute_reply.started":"2025-09-16T19:37:57.674873Z","shell.execute_reply":"2025-09-16T19:38:04.869686Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ========== 小物ユーティリティ ==========\ndef _iou_xyxy(a, b):\n    # a,b: [x1,y1,x2,y2]\n    x1 = max(a[0], b[0]); y1 = max(a[1], b[1])\n    x2 = min(a[2], b[2]); y2 = min(a[3], b[3])\n    iw = max(0.0, x2 - x1); ih = max(0.0, y2 - y1)\n    inter = iw * ih\n    if inter <= 0: return 0.0\n    area_a = max(0.0, (a[2]-a[0]) * (a[3]-a[1]))\n    area_b = max(0.0, (b[2]-b[0]) * (b[3]-b[1]))\n    union = area_a + area_b - inter + 1e-9\n    return inter / union\n\ndef _ensure_dir(p):\n    Path(p).mkdir(parents=True, exist_ok=True)\n\n# ========== 簡易 Weighted Boxes Fusion（クラス別） ==========\ndef weighted_boxes_fusion(\n    boxes, scores, labels,\n    iou_thr=0.55,\n    conf_type=\"avg\"   # \"avg\" か \"one_minus_prod\"\n):\n    \"\"\"\n    複数モデルの出力をマージ。boxes/scores/labels はすべて結合済み1配列（同一画像）。\n    - boxes: (N,4) xyxy\n    - scores: (N,)\n    - labels: (N,)\n    返り値: fused_boxes, fused_scores, fused_labels\n    \"\"\"\n    if len(boxes) == 0:\n        return boxes, scores, labels\n\n    order = np.argsort(-scores)\n    boxes = boxes[order]; scores = scores[order]; labels = labels[order]\n\n    clusters = []  # list of dict: {'box': np.array(4), 'score_sum': float, 'scores': [..], 'label': int, 'weight_sum': float, 'members': int}\n    for b, s, c in zip(boxes, scores, labels):\n        matched = False\n        for cl in clusters:\n            if cl['label'] != c:\n                continue\n            if _iou_xyxy(b, cl['box']) >= iou_thr:\n                # 既存クラスタにマージ（座標はスコア重みで移動平均）\n                w_old = cl['weight_sum']; w_new = s\n                cl['box'] = (cl['box'] * w_old + b * w_new) / (w_old + w_new + 1e-9)\n                cl['weight_sum'] += w_new\n                cl['scores'].append(s)\n                cl['members'] += 1\n                matched = True\n                break\n        if not matched:\n            clusters.append({\n                'box': b.copy(),\n                'label': int(c),\n                'weight_sum': float(s),\n                'scores': [float(s)],\n                'members': 1,\n            })\n\n    fused_boxes, fused_scores, fused_labels = [], [], []\n    for cl in clusters:\n        fused_boxes.append(cl['box'])\n        if conf_type == \"one_minus_prod\":\n            # 1 - ∏(1-s) で統合信頼度\n            p = 1.0\n            for s in cl['scores']:\n                p *= (1.0 - s)\n            sc = 1.0 - p\n        else:\n            sc = float(np.mean(cl['scores']))\n        fused_scores.append(sc)\n        fused_labels.append(cl['label'])\n\n    return np.vstack(fused_boxes), np.array(fused_scores), np.array(fused_labels, dtype=int)\n\n# ========== 1画像に対する fold アンサンブル推論 ==========\ndef predict_ensemble_single_image(\n    models,\n    image,\n    conf=0.2,\n    iou=0.7,\n    imgsz=320,\n    per_model_max_det=1,      # 各モデルが返す最大検出数\n    ensemble_iou=0.55,          # WBF のクラスタ閾値\n    only_one=True,             # True で最終1件に絞る\n    agnostic=False,             # True でクラス無視の後段NMSをしたい場合（今回はWBFなので通常Falseのまま推奨）\n):\n    \"\"\"\n    各 fold の best.pt を読み、単一画像に対して推論→WBFで統合。\n    戻り値: dict {'boxes': (K,4), 'scores': (K,), 'labels': (K,)}\n    \"\"\"\n    # 遅延ロードを避けたい場合は、外で [YOLO(p) for p in model_paths] を渡す実装に変えてもOK\n    boxes_all = []\n    scores_all = []\n    labels_all = []\n\n    for model in models:\n        model = YOLO(mp)\n        res = model.predict(\n            source=image,\n            conf=conf, iou=iou, imgsz=imgsz, max_det=per_model_max_det, verbose=False\n        )[0]\n        if res.boxes is None or len(res.boxes) == 0:\n            continue\n        xyxy = res.boxes.xyxy.cpu().numpy()\n        sco  = res.boxes.conf.cpu().numpy()\n        lab  = (res.boxes.cls.cpu().numpy().astype(int)\n                if res.boxes.cls is not None else np.zeros(len(xyxy), dtype=int))\n        boxes_all.append(xyxy); scores_all.append(sco); labels_all.append(lab)\n\n    if len(boxes_all) == 0:\n        return {'boxes': np.zeros((0,4), dtype=np.float32),\n                'scores': np.zeros((0,), dtype=np.float32),\n                'labels': np.zeros((0,), dtype=np.int32)}\n\n    boxes = np.vstack(boxes_all)\n    scores = np.hstack(scores_all)\n    labels = np.hstack(labels_all)\n\n    # 簡易 WBF\n    f_boxes, f_scores, f_labels = weighted_boxes_fusion(\n        boxes, scores, labels, iou_thr=ensemble_iou, conf_type=\"one_minus_prod\"\n    )\n\n    # 1件に絞る（スコア最大）\n    if only_one and len(f_scores) > 0:\n        idx = int(np.argmax(f_scores))\n        f_boxes = f_boxes[idx:idx+1]\n        f_scores = f_scores[idx:idx+1]\n        f_labels = f_labels[idx:idx+1]\n\n    return {'boxes': f_boxes, 'scores': f_scores, 'labels': f_labels}\n\n# ========== ディレクトリ一括推論 & オーバーレイ保存 & CSV ==========\ndef ensemble_infer_dir(\n    model_paths,\n    images_root,\n    out_dir=\"/kaggle/working/ens_out\",\n    conf=0.2,\n    iou=0.7,\n    imgsz=640,\n    per_model_max_det=100,\n    ensemble_iou=0.55,\n    only_one=False,\n    draw_score=True,\n):\n    \"\"\"\n    images_root 配下の *.png / *.jpg を再帰探索して、fold アンサンブル推論。\n    - オーバーレイ画像を out_dir/overlays 下に保存（元のサブフォルダ構造を維持）\n    - 予測座標を CSV に保存（out_dir/ensemble_predictions.csv）\n    \"\"\"\n    images_root = Path(images_root)\n    out_dir = Path(out_dir)\n    overlays_dir = out_dir / \"overlays\"\n    _ensure_dir(overlays_dir)\n\n    img_paths = (list(images_root.rglob(\"*.png\")) +\n                 list(images_root.rglob(\"*.jpg\")) +\n                 list(images_root.rglob(\"*.jpeg\")))\n    img_paths.sort()\n    if not img_paths:\n        print(f\"No images found under: {images_root}\")\n        return None\n\n    rows = []\n    # フォント（環境によっては使えないことがあるので None でフォールバック）\n    try:\n        font = ImageFont.truetype(\"DejaVuSans.ttf\", size=14)\n    except:\n        font = None\n\n    for img_path in img_paths:\n        pred = predict_ensemble_single_image(\n            model_paths, str(img_path),\n            conf=conf, iou=iou, imgsz=imgsz,\n            per_model_max_det=per_model_max_det,\n            ensemble_iou=ensemble_iou,\n            only_one=only_one,\n        )\n\n        # CSV 行を作成\n        if len(pred['scores']) == 0:\n            rows.append({\n                \"image_path\": str(img_path),\n                \"x1\": np.nan, \"y1\": np.nan, \"x2\": np.nan, \"y2\": np.nan,\n                \"score\": np.nan, \"class\": np.nan,\n                \"cx\": np.nan, \"cy\": np.nan, \"w\": np.nan, \"h\": np.nan\n            })\n        else:\n            for (x1,y1,x2,y2), sc, cls in zip(pred['boxes'], pred['scores'], pred['labels']):\n                w = x2 - x1; h = y2 - y1; cx = x1 + w/2; cy = y1 + h/2\n                rows.append({\n                    \"image_path\": str(img_path),\n                    \"x1\": float(x1), \"y1\": float(y1), \"x2\": float(x2), \"y2\": float(y2),\n                    \"score\": float(sc), \"class\": int(cls),\n                    \"cx\": float(cx), \"cy\": float(cy), \"w\": float(w), \"h\": float(h)\n                })\n\n        # オーバーレイ保存\n        img = Image.open(img_path).convert(\"RGB\")\n        draw = ImageDraw.Draw(img)\n        for (x1,y1,x2,y2), sc, cls in zip(pred['boxes'], pred['scores'], pred['labels']):\n            draw.rectangle([x1,y1,x2,y2], outline=(255,0,0), width=2)\n            if draw_score:\n                txt = f\"{int(cls)}:{sc:.2f}\"\n                tw, th = draw.textlength(txt, font=font) if hasattr(draw, \"textlength\") else (len(txt)*8, 12)\n                draw.rectangle([x1, max(0,y1-th-2), x1+tw+4, y1], fill=(255,0,0))\n                draw.text((x1+2, max(0,y1-th-2)), txt, fill=(255,255,255), font=font)\n\n        rel = img_path.relative_to(images_root)\n        out_img_path = overlays_dir / rel\n        _ensure_dir(out_img_path.parent)\n        img.save(out_img_path)\n\n    # CSV 保存\n    df = pd.DataFrame(rows)\n    csv_path = out_dir / \"ensemble_predictions.csv\"\n    _ensure_dir(csv_path.parent)\n    df.to_csv(csv_path, index=False)\n    print(f\"Saved overlays to: {overlays_dir}\")\n    print(f\"Saved CSV to:      {csv_path}\")\n    return {\"csv\": str(csv_path), \"overlays_dir\": str(overlays_dir), \"df\": df}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:04.873531Z","iopub.execute_input":"2025-09-16T19:38:04.874111Z","iopub.status.idle":"2025-09-16T19:38:04.907694Z","shell.execute_reply.started":"2025-09-16T19:38:04.874066Z","shell.execute_reply":"2025-09-16T19:38:04.906429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/rsna2025-extra/train_add_metadata_v4.csv')\ndf['sorted_files'] = df['sorted_files'].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else x)\ndf['PixelSpacing'] = df['PixelSpacing'].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:04.909328Z","iopub.execute_input":"2025-09-16T19:38:04.909668Z","iopub.status.idle":"2025-09-16T19:38:12.386742Z","shell.execute_reply.started":"2025-09-16T19:38:04.909638Z","shell.execute_reply":"2025-09-16T19:38:12.385713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def pct_normalize(img, p_min, p_max):\n    vmin = np.percentile(img, p_min)\n    vmax = np.percentile(img, p_max)\n    img = np.clip(img, vmin, vmax)\n    img = (img - vmin) / (vmax - vmin + 1e-6)\n    img = (img*255).astype(np.uint8)\n    return img\n\ndef value_normalize(img, vmin, vmax):\n    img = np.clip(img, vmin, vmax)\n    img = (img - vmin) / (vmax - vmin + 1e-6)\n    img = (img*255).astype(np.uint8)\n    return img\n    \ndef resample_zyx(arr: np.ndarray,\n                 spacing: tuple[float, float, float],\n                 new_spacing: tuple[float, float, float],\n                 order: int) -> np.ndarray:\n    \"\"\"\n    (Z,Y,X) を spacing -> new_spacing へリサンプリング。\n    order=1: 画像（線形）, order=0: マスク（最近傍）\n    \"\"\"\n    sz, sy, sx = spacing\n    nsz, nsy, nsx = new_spacing\n    zz, zy, zx = sz / nsz, sy / nsy, sx / nsx\n    mode = \"nearest\" if order == 0 else \"constant\"\n    return zoom(arr, zoom=(zz, zy, zx), order=order, mode=mode)\n    \ndef load_dicom_array_2d(paths):\n    imgs = []\n    for path in paths:\n        dcm = pydicom.dcmread(path)\n        imgs.append(dcm.pixel_array)\n    imgs = np.array(imgs)\n    return imgs\n\ndef load_dicom_array_3d(path):\n    dcm = pydicom.dcmread(path)\n    imgs = dcm.pixel_array\n    return imgs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.387899Z","iopub.execute_input":"2025-09-16T19:38:12.388297Z","iopub.status.idle":"2025-09-16T19:38:12.397866Z","shell.execute_reply.started":"2025-09-16T19:38:12.388248Z","shell.execute_reply":"2025-09-16T19:38:12.396416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[df['SeriesInstanceUID']=='1.2.826.0.1.3680043.8.498.10012790035410518400400834395242853657']['num_slice']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.398955Z","iopub.execute_input":"2025-09-16T19:38:12.399298Z","iopub.status.idle":"2025-09-16T19:38:12.4492Z","shell.execute_reply.started":"2025-09-16T19:38:12.399268Z","shell.execute_reply":"2025-09-16T19:38:12.447946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from concurrent.futures import ProcessPoolExecutor\nfrom functools import partial\nimport os\nimport traceback\n\ndef _ensure_dir_if_available(p: \"Path\"):\n    \"\"\"_ensure_dir が無い場合でも安全に作成\"\"\"\n    try:\n        _ensure_dir(p)\n    except NameError:\n        p.mkdir(parents=True, exist_ok=True)\n\ndef _save_png(arr, path: \"Path\"):\n    # 期待は uint8 グレースケール。万一 dtype が違う場合は安全側で変換\n    import numpy as np\n    from PIL import Image\n    if arr.dtype != np.uint8:\n        arr = arr.astype(np.uint8)\n    Image.fromarray(arr, mode=\"L\").save(path)\n\ndef process_one(record: dict, out_root_str: str):\n    \"\"\"1レコード処理。例外は文字列で返す（map中の落ちを防ぐ）\"\"\"\n    #try:\n    out_root_local = Path(out_root_str)\n\n    img_files = record['sorted_files']\n    sid       = record['SeriesInstanceUID']\n    modality  = record['Modality']\n    plane     = record['OrientationLabel']\n\n    # 画像読み込み\n    if len(img_files)==1:\n        # 3D（シリーズディレクトリ指定）\n        series_dir = Path(f\"/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sid}\")\n        cand = list(series_dir.glob(\"*\"))\n        if len(cand) == 0:\n            return (\"error\", sid, f\"No files under {series_dir}\")\n        imgs = load_dicom_array_3d(cand[0])\n    else:\n        # 2D stack\n        imgs = load_dicom_array_2d(img_files)\n    y_spacing, x_spacing = record['PixelSpacing']\n    z_spacing = record['z_spacing']\n    imgs = resample_zyx(\n        imgs,\n        spacing=(z_spacing, y_spacing, x_spacing),\n        new_spacing=(1., 1., 1.),\n        order=1\n    )\n\n    # 正規化\n    if modality == 'CTA':\n        imgs = value_normalize(imgs, -100, 600)\n    else:\n        imgs = pct_normalize(imgs, 1, 99)\n\n    Z, H, W = imgs.shape\n    z_mid, h_mid, w_mid = Z // 2, H // 2, W // 2  # ← x_mid は未定義だったので修正\n\n    # 各平面で 2方向ずつ保存\n    if plane == 'AXIAL':\n        # coronal (axis1)\n        img = imgs[:, h_mid, :]\n        base = out_root_local / modality / plane / \"axis1\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n        # sagittal (axis2)\n        img = imgs[:, :, w_mid]\n        base = out_root_local / modality / plane / \"axis2\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n    elif plane == 'CORONAL':\n        # coronal (axis0)\n        img = imgs[z_mid, :, :]\n        base = out_root_local / modality / plane / \"axis0\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n        # sagittal (axis2)  ※ x_mid→w_mid に修正\n        img = imgs[:, :, w_mid]\n        base = out_root_local / modality / plane / \"axis2\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n    elif plane == 'SAGITTAL':\n        # sagittal (axis0)\n        img = imgs[z_mid, :, :]\n        base = out_root_local / modality / plane / \"axis0\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n        # coronal (axis2)  ※ x_mid→w_mid に修正\n        img = imgs[:, :, w_mid]\n        base = out_root_local / modality / plane / \"axis2\"\n        p_img = base / \"images\"\n        _ensure_dir_if_available(p_img)\n        _save_png(img, p_img / f\"{sid}.png\")\n\n    else:\n        return (\"skip\", sid, f\"Unknown plane: {plane}\")\n\n    return (\"ok\", sid, \"\")\n\n    # except Exception as e:\n    #     return (\"error\", record.get(\"SeriesInstanceUID\", \"UNKNOWN\"), f\"{e}\\n{traceback.format_exc()}\")\n\ndef run_parallel(df, out_root_arg=None, max_workers=None):\n    \"\"\"df 全行を並列処理。進捗は tqdm。\"\"\"\n    from tqdm import tqdm\n\n    if out_root_arg is None:\n        # 既存の out_root を使いたい場合\n        out_root_arg = str(out_root)  # 既存変数を文字列化して子プロセスへ渡す\n    else:\n        out_root_arg = str(out_root_arg)\n\n    records = df.to_dict(\"records\")\n    worker = partial(process_one, out_root_str=out_root_arg)\n\n    ok_count = err_count = skip_count = 0\n\n    # CPU重めなのでプロセス並列が基本。メモリ圧迫するなら max_workers を絞ってください。\n    if max_workers is None:\n        max_workers = os.cpu_count() or 4\n\n    with ProcessPoolExecutor(max_workers=max_workers) as ex:\n        for status, sid, msg in tqdm(ex.map(worker, records), total=len(records)):\n            if status == \"ok\":\n                ok_count += 1\n            elif status == \"skip\":\n                skip_count += 1\n            else:\n                err_count += 1\n                # 必要ならログ出力\n                # print(f\"[ERROR] {sid}: {msg}\")\n\n    return {\"ok\": ok_count, \"skip\": skip_count, \"error\": err_count}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.450693Z","iopub.execute_input":"2025-09-16T19:38:12.451028Z","iopub.status.idle":"2025-09-16T19:38:12.477422Z","shell.execute_reply.started":"2025-09-16T19:38:12.450996Z","shell.execute_reply":"2025-09-16T19:38:12.475751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.iloc[4]['OrientationLabel']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.480691Z","iopub.execute_input":"2025-09-16T19:38:12.481019Z","iopub.status.idle":"2025-09-16T19:38:12.492882Z","shell.execute_reply.started":"2025-09-16T19:38:12.480993Z","shell.execute_reply":"2025-09-16T19:38:12.491785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.iloc[4]['SeriesInstanceUID']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.494369Z","iopub.execute_input":"2025-09-16T19:38:12.494689Z","iopub.status.idle":"2025-09-16T19:38:12.513482Z","shell.execute_reply.started":"2025-09-16T19:38:12.494663Z","shell.execute_reply":"2025-09-16T19:38:12.512277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"paths = list(Path('/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10012790035410518400400834395242853657').glob('*'))\npaths","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.514573Z","iopub.execute_input":"2025-09-16T19:38:12.51491Z","iopub.status.idle":"2025-09-16T19:38:12.540474Z","shell.execute_reply.started":"2025-09-16T19:38:12.514885Z","shell.execute_reply":"2025-09-16T19:38:12.539325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv('/kaggle/input/rsna2025-extra/train_add_metadata_v5.csv')\ndf[\"sorted_files\"] = df[\"sorted_files\"].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else x)\ndf['PixelSpacing'] = df['PixelSpacing'].apply(lambda x: ast.literal_eval(x) if pd.notna(x) else x)\n\nout_root = './series'\nstats = run_parallel(df, out_root_arg=out_root, max_workers=4)\nprint(stats)\n# records = df.to_dict(\"records\")\n# for record in tqdm(records, total=len(records)):\n#     process_one(record, out_root)\n#     img_files = row['sorted_files']\n#     sid = row['SeriesInstanceUID']\n#     modality = row['Modality']\n#     plane = row['OrientationLabel']\n#     if not isinstance(img_files, list):\n#         img_file = list(Path(f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sid}').glob('*'))[0]\n#         imgs = load_dicom_array_3d(img_file)\n#     else:\n#         imgs = load_dicom_array_2d(img_files)\n#         y_spacing, x_spacing = row['PixelSpacing']\n#         z_spacing = row['z_spacing']\n#         imgs = resample_zyx(imgs,\n#                             spacing=(z_spacing, y_spacing, x_spacing),\n#                             new_spacing=(1., 1., 1.),\n#                             order=1\n#                            )\n#     if modality=='CTA':\n#         imgs = value_normalize(imgs, -100, 600)\n#     else:\n#         imgs = pct_normalize(imgs, 1, 99)\n#     Z, H, W = imgs.shape\n#     z_mid, h_mid, w_mid = Z//2, H//2, W//2\n    \n#     if plane=='AXIAL':\n#         # coronal\n#         img = imgs[:,h_mid,:]\n#         base = Path(out_root) / modality / plane / f\"axis1\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n\n#         # sagittal\n#         img = imgs[:,:,w_mid]\n#         base = Path(out_root) / modality / plane / f\"axis2\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n#     elif plane=='CORONAL':\n#         # coronal\n#         img = imgs[z_mid,:,:]\n#         base = Path(out_root) / modality / plane / f\"axis0\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n\n#         # sagittal\n#         img = imgs[:,:,w_mid]\n#         base = Path(out_root) / modality / plane / f\"axis2\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n#     elif plane=='SAGITTAL':\n#         # sagittal\n#         img = imgs[z_mid,:,:]\n#         base = Path(out_root) / modality / plane / f\"axis0\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n        \n#         # coronal\n#         img = imgs[:,:,w_mid]\n#         base = Path(out_root) / modality / plane / f\"axis2\"\n#         p_img   = base / \"images\"\n#         _ensure_dir(p_img)\n#         Image.fromarray(img, mode=\"L\").save(p_img / f\"{sid}.png\")\n\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-16T19:38:12.541687Z","iopub.execute_input":"2025-09-16T19:38:12.542076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_plane = df[df['OrientationLabel']=='CORONAL']\nrow = df_plane.iloc[0]\nimg_files = row['sorted_files']\nsid = row['SeriesInstanceUID']\nmodality = row['Modality']\nplane = row['OrientationLabel']\nif not isinstance(img_files, list):\n    img_file = list(Path(f'/kaggle/input/rsna-intracranial-aneurysm-detection/series/{sid}').glob('*'))[0]\n    imgs = load_dicom_array_3d(img_file)\nelse:\n    imgs = load_dicom_array_2d(img_files)\n    y_spacing, x_spacing = row['PixelSpacing']\n    z_spacing = row['z_spacing']\n    imgs = resample_zyx(imgs,\n                        spacing=(z_spacing, y_spacing, x_spacing),\n                        new_spacing=(1., 1., 1.),\n                        order=1\n                       )\nif modality=='CTA':\n    imgs = value_normalize(imgs, -100, 600)\nelse:\n    imgs = pct_normalize(imgs, 1, 99)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"row['z_spacing']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Z, H, W = imgs.shape\nz_mid, h_mid, w_mid = Z//2, H//2, W//2\nplt.imshow(imgs[:,:,w_mid])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"row['PixelSpacing']","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}