{"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":"gpu","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13130191,"sourceType":"datasetVersion","datasetId":8009945},{"sourceId":13190707,"sourceType":"datasetVersion","datasetId":8299879},{"sourceId":13201731,"sourceType":"datasetVersion","datasetId":8325535},{"sourceId":227202990,"sourceType":"kernelVersion"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Standard library\nimport gc\nimport json\nimport os\nimport queue\nimport shutil\nimport sys\nimport threading\nimport time\nimport warnings\nfrom collections import OrderedDict\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nfrom pathlib import Path\nfrom typing import Dict, List, Optional, Tuple\nimport ast\n\n# Third-party: general\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport pydicom\nfrom scipy import ndimage\nfrom skimage.filters import frangi\nimport matplotlib.pyplot as plt\n\n# Third-party: ML/DL\nimport cupy as cp\nfrom cupyx.scipy.ndimage import zoom\nimport timm\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.cuda.amp import autocast\nsys.path.insert(0, \"/kaggle/input/ultralytcs-timm-rsna/ultralytics-timm\")\n# YOLO\nfrom ultralytics import YOLO\n\n# Transformations\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Competition API\nimport kaggle_evaluation.rsna_inference_server\n\n# Warnings config\nwarnings.filterwarnings(\"ignore\")\n\n# Device setup\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Optimization settings\ntorch.set_float32_matmul_precision(\"medium\")\ntorch.backends.cudnn.benchmark = True\ntorch.backends.cudnn.deterministic = False\ntorch.backends.cuda.matmul.allow_tf32 = True\ntorch.backends.cudnn.allow_tf32 = True","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:12.176716Z","iopub.execute_input":"2025-10-01T05:03:12.177387Z","iopub.status.idle":"2025-10-01T05:03:27.121681Z","shell.execute_reply.started":"2025-10-01T05:03:12.177363Z","shell.execute_reply":"2025-10-01T05:03:27.120901Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## YOLO Predictions","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Competition constants\n# ====================================================\nID_COL = 'SeriesInstanceUID'\nLABEL_COLS = [\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation',\n    'Aneurysm Present',\n]\n\n# YOLO label mappings\nYOLO_LABELS_TO_IDX = {\n    'Anterior Communicating Artery': 0,\n    'Basilar Tip': 1,\n    'Left Anterior Cerebral Artery': 2,\n    'Left Infraclinoid Internal Carotid Artery': 3,\n    'Left Middle Cerebral Artery': 4,\n    'Left Posterior Communicating Artery': 5,\n    'Left Supraclinoid Internal Carotid Artery': 6,\n    'Other Posterior Circulation': 7,\n    'Right Anterior Cerebral Artery': 8,\n    'Right Infraclinoid Internal Carotid Artery': 9,\n    'Right Middle Cerebral Artery': 10,\n    'Right Posterior Communicating Artery': 11,\n    'Right Supraclinoid Internal Carotid Artery': 12\n}\n\nYOLO_LABELS = sorted(list(YOLO_LABELS_TO_IDX.keys()))\n\n\nEFF_LABELS_TO_IDX = {\n    'Aneurysm Present': 0,\n    'Anterior Communicating Artery': 1,\n    'Basilar Tip': 2,\n    'Left Anterior Cerebral Artery': 3,\n    'Left Infraclinoid Internal Carotid Artery': 4,\n    'Left Middle Cerebral Artery': 5,\n    'Left Posterior Communicating Artery': 6,\n    'Left Supraclinoid Internal Carotid Artery': 7,\n    'Other Posterior Circulation': 8,\n    'Right Anterior Cerebral Artery': 9,\n    'Right Infraclinoid Internal Carotid Artery': 10,\n    'Right Middle Cerebral Artery': 11,\n    'Right Posterior Communicating Artery': 12,\n    'Right Supraclinoid Internal Carotid Artery': 13\n}\n\nEFF_LABELS = sorted(list(EFF_LABELS_TO_IDX.keys()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:27.122889Z","iopub.execute_input":"2025-10-01T05:03:27.123183Z","iopub.status.idle":"2025-10-01T05:03:27.129199Z","shell.execute_reply.started":"2025-10-01T05:03:27.123164Z","shell.execute_reply":"2025-10-01T05:03:27.128323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# YOLO Configuration\n# ====================================================\nIMG_SIZE = 512\nBATCH_SIZE = int(os.getenv(\"YOLO_BATCH_SIZE\", \"32\"))\nMAX_WORKERS = 4\n\nYOLO_MODEL_CONFIGS = [\n    {\n        \"path\": \"/kaggle/input/rsna-yolo-models/cv_y11m_more_negatives_fold02/weights/best.pt\",\n        \"fold\": \"0\",\n        \"weight\": 1.0,\n        \"name\": \"YOLOv11m_fold0\"\n    },\n    {\n        \"path\": \"/kaggle/input/rsna-yolo-models/cv_y11m_more_negatives_fold3/weights/best.pt\",\n        \"fold\": \"3\",\n        \"weight\": 1.0,\n        \"name\": \"YOLOv11m_fold1\"\n    }\n]\n\n\ndef load_yolo_models():\n    \"\"\"Load all YOLO models\"\"\"\n    models = []\n    for config in YOLO_MODEL_CONFIGS:\n        model = YOLO(config[\"path\"])\n        model.to(device)\n        \n        model_dict = {\n            \"model\": model,\n            \"weight\": config[\"weight\"],\n            \"name\": config[\"name\"],\n            \"fold\": config[\"fold\"]\n        }\n        models.append(model_dict)\n    return models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:27.130335Z","iopub.execute_input":"2025-10-01T05:03:27.130572Z","iopub.status.idle":"2025-10-01T05:03:27.152434Z","shell.execute_reply.started":"2025-10-01T05:03:27.130544Z","shell.execute_reply":"2025-10-01T05:03:27.151919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"YOLO_MODELS = load_yolo_models()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:27.154054Z","iopub.execute_input":"2025-10-01T05:03:27.154464Z","iopub.status.idle":"2025-10-01T05:03:28.455799Z","shell.execute_reply.started":"2025-10-01T05:03:27.154447Z","shell.execute_reply":"2025-10-01T05:03:28.455256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_dicom_frames_hu(path: Path) -> List[Tuple[float, np.ndarray]]:\n    \"\"\"Read DICOM file and return list of (slice_position, HU frame)\"\"\"\n    ds = pydicom.dcmread(str(path), force=True)\n    pix = ds.pixel_array\n    slope = float(getattr(ds, 'RescaleSlope', 1.0))\n    intercept = float(getattr(ds, 'RescaleIntercept', 0.0))\n\n    # Compute slice location using orientation + position\n    try:\n        orientation = np.array(ds.ImageOrientationPatient).reshape(2, 3)\n        row_cos, col_cos = orientation\n        normal = np.cross(row_cos, col_cos)  # slice normal vector\n        position = np.array(ds.ImagePositionPatient)\n        slice_loc = float(np.dot(position, normal))  # projection along normal\n    except Exception:\n        # Fallback: SliceLocation / InstanceNumber\n        slice_loc = float(getattr(ds, \"SliceLocation\", getattr(ds, \"InstanceNumber\", 0.0)))\n\n    frames: List[Tuple[float, np.ndarray]] = []\n\n    if pix.ndim == 2:\n        img = pix.astype(np.float32)\n        frames.append((slice_loc, img * slope + intercept))\n    elif pix.ndim == 3:\n        # RGB or multi-frame\n        if pix.shape[-1] == 3 and pix.shape[0] != 3:\n            try:\n                gray = cv2.cvtColor(pix.astype(np.uint8), cv2.COLOR_BGR2GRAY).astype(np.float32)\n            except Exception:\n                gray = pix[..., 0].astype(np.float32)\n            frames.append((slice_loc, gray * slope + intercept))\n        else:\n            for i in range(pix.shape[0]):\n                frm = pix[i].astype(np.float32)\n                # tiny offset ensures consistent ordering for multi-frame\n                frames.append((slice_loc + i * 1e-3, frm * slope + intercept))\n    return frames\n\n\ndef min_max_normalize(img: np.ndarray) -> np.ndarray:\n    \"\"\"Min-max normalization to 0-255 with optional flipping\"\"\"\n    mn, mx = float(img.min()), float(img.max())\n    if mx - mn < 1e-6:\n        norm = np.zeros_like(img, dtype=np.uint8)\n    else:\n        norm = (img - mn) / (mx - mn)\n        norm = (norm * 255.0).clip(0, 255).astype(np.uint8)\n    return norm\n\n\ndef process_dicom_file(dcm_path: Path) -> List[Tuple[float, np.ndarray]]:\n    \"\"\"Process single DICOM file -> list of (slice_loc, image) tuples\"\"\"\n    try:\n        frames = read_dicom_frames_hu(dcm_path)\n        processed_slices = []\n        for loc, f in frames:\n            img_u8 = min_max_normalize(f)\n            if img_u8.ndim == 2:\n                img_u8 = cv2.cvtColor(img_u8, cv2.COLOR_GRAY2BGR)\n            processed_slices.append((loc, img_u8))\n        return processed_slices\n    except Exception as e:\n        print(f\"Failed processing {dcm_path.name}: {e}\")\n        return []\n\n\ndef collect_series_slices(series_dir: Path) -> List[Path]:\n    \"\"\"Collect all DICOM files in a series directory (recursively).\"\"\"\n    dcm_paths: List[Path] = []\n    try:\n        for root, _, files in os.walk(series_dir):\n            for f in files:\n                if f.lower().endswith('.dcm'):\n                    dcm_paths.append(Path(root) / f)\n    except Exception as e:\n        print(f\"Failed to walk series dir {series_dir}: {e}\")\n    return dcm_paths\n\n\ndef slice_sort_key(path: Path) -> float:\n    \"\"\"Compute a robust slice sort key (orientation + position) for a single DICOM file\"\"\"\n    try:\n        ds = pydicom.dcmread(str(path), stop_before_pixels=True, force=True)\n        orientation = np.array(ds.ImageOrientationPatient).reshape(2, 3)\n        row_cos, col_cos = orientation\n        normal = np.cross(row_cos, col_cos)\n        position = np.array(ds.ImagePositionPatient)\n        return float(np.dot(position, normal))\n    except Exception:\n        # fallback\n        try:\n            return float(getattr(ds, \"SliceLocation\", getattr(ds, \"InstanceNumber\", 0.0)))\n        except:\n            return 0.0\n\ndef process_dicom_for_yolo(series_path):\n    series_path = Path(series_path)\n    dicom_files = collect_series_slices(series_path)\n    \n    # Sort DICOM files by orientation+position before processing\n    dicom_files.sort(key=slice_sort_key)\n    \n    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:\n        results = list(executor.map(process_dicom_file, dicom_files))\n    \n    # Flatten into (loc, img)\n    all_slices_with_loc = [item for sublist in results for item in sublist]\n    \n    # Already sorted by dicom_files order, but double-check (safe)\n    all_slices_with_loc.sort(key=lambda x: x[0])\n    \n    # Extract just the images\n    all_slices = [img for _, img in all_slices_with_loc]\n    \n    # Now dicom_files matches the sorted slices\n    dcm_list = [f.stem for f in dicom_files]\n    return all_slices","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.456502Z","iopub.execute_input":"2025-10-01T05:03:28.45669Z","iopub.status.idle":"2025-10-01T05:03:28.470926Z","shell.execute_reply.started":"2025-10-01T05:03:28.456676Z","shell.execute_reply":"2025-10-01T05:03:28.470155Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def nms_3d_points(points, iou_thresh=2.0):\n    \"\"\"\n    Simple 3D NMS for point detections.\n    points: np.array of shape [N, 5] or [N,6], columns=[z,y,x,prob,class,...]\n    iou_thresh: minimum distance (in voxels) to suppress a point\n    Returns: indices of points to keep\n    \"\"\"\n    if len(points) == 0:\n        return []\n\n    keep = []\n    pts = points[:, :3]  # z,y,x\n    scores = points[:, 3]\n    order = scores.argsort()[::-1]\n\n    suppressed = np.zeros(len(points), dtype=bool)\n\n    for idx in order:\n        if suppressed[idx]:\n            continue\n        keep.append(idx)\n        dists = np.linalg.norm(pts - pts[idx], axis=1)\n        suppressed[dists < iou_thresh] = True\n        suppressed[idx] = False  # keep current\n    return keep\n\n\n@torch.no_grad()\ndef predict_yolo_ensemble(slices, conf_yolo, iou_thresh=2.0, k = 3):\n    if not slices:\n        return 0.1, np.ones(len(YOLO_LABELS)) * 0.1\n\n    location_preds = {f'MODEL{i}': [] for i in range(len(YOLO_MODELS))}\n\n    for model_idx, model_dict in enumerate(YOLO_MODELS):\n        model = model_dict[\"model\"]\n        weight = model_dict[\"weight\"]\n\n        for i in range(0, len(slices), BATCH_SIZE):\n            batch_slices = slices[i:i+BATCH_SIZE]\n            z_idxes = [i + batch_idx for batch_idx in range(len(batch_slices))]\n            results = model.predict(\n                batch_slices,\n                verbose=False,\n                batch=len(batch_slices),\n                device=\"cuda:0\",\n                conf=conf_yolo\n            )\n\n            for z_idx, r in enumerate(results):\n                if r is None or r.boxes is None or r.boxes.conf is None or len(r.boxes) == 0:\n                    continue\n                confs = r.boxes.conf.cpu().numpy()\n                clses = r.boxes.cls.cpu().numpy()\n                xyxy = r.boxes.xyxy.cpu().numpy()\n\n                for j in range(len(confs)):\n                    x1, y1, x2, y2 = xyxy[j]\n                    x_center = (x1 + x2)/2\n                    y_center = (y1 + y2)/2\n                    point = np.array([z_idxes[z_idx], y_center, x_center, confs[j], clses[j], model_idx])\n                    location_preds[f'MODEL{model_idx}'].append(point)\n\n    # Apply NMS per model and per class\n    final_preds = {f'MODEL{i}': [] for i in range(len(YOLO_MODELS))}\n    for model_key, points in location_preds.items():\n        points = np.array(points)\n        if points.shape[0] == 0:\n            continue\n        for cls in np.unique(points[:,4]):\n            cls_points = points[points[:,4]==cls]\n            keep_idx = nms_3d_points(cls_points, iou_thresh=iou_thresh)\n            final_preds[model_key].extend(cls_points[keep_idx])\n        model_preds = torch.tensor(final_preds[model_key])\n        values, indices = model_preds[:, -3].topk(k)   # get top-k values & indices\n        final_preds[model_key] = model_preds[indices].numpy()\n    return final_preds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.471647Z","iopub.execute_input":"2025-10-01T05:03:28.472183Z","iopub.status.idle":"2025-10-01T05:03:28.490253Z","shell.execute_reply.started":"2025-10-01T05:03:28.472158Z","shell.execute_reply":"2025-10-01T05:03:28.489649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_loc_labels(root: Path) -> pd.DataFrame:\n    label_df = pd.read_csv(root / \"train_localizers.csv\")\n    if \"x\" not in label_df.columns or \"y\" not in label_df.columns:\n        label_df[\"x\"] = label_df[\"coordinates\"].map(lambda s: ast.literal_eval(s)[\"x\"])  # type: ignore[arg-type]\n        label_df[\"y\"] = label_df[\"coordinates\"].map(lambda s: ast.literal_eval(s)[\"y\"])  # type: ignore[arg-type]\n    # Standardize dtypes\n    label_df[\"SeriesInstanceUID\"] = label_df[\"SeriesInstanceUID\"].astype(str)\n    label_df[\"SOPInstanceUID\"] = label_df[\"SOPInstanceUID\"].astype(str)\n    return label_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.490906Z","iopub.execute_input":"2025-10-01T05:03:28.491069Z","iopub.status.idle":"2025-10-01T05:03:28.50736Z","shell.execute_reply.started":"2025-10-01T05:03:28.491055Z","shell.execute_reply":"2025-10-01T05:03:28.506635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_path = Path('/kaggle/input/rsna-intracranial-aneurysm-detection')\ndf = pd.read_csv(data_path/'train.csv')\ndf_loc = load_loc_labels(data_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.508134Z","iopub.execute_input":"2025-10-01T05:03:28.508362Z","iopub.status.idle":"2025-10-01T05:03:28.622377Z","shell.execute_reply.started":"2025-10-01T05:03:28.508336Z","shell.execute_reply":"2025-10-01T05:03:28.621815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gt_loc = df_loc[['x', 'y']]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.623049Z","iopub.execute_input":"2025-10-01T05:03:28.623223Z","iopub.status.idle":"2025-10-01T05:03:28.633638Z","shell.execute_reply.started":"2025-10-01T05:03:28.623209Z","shell.execute_reply":"2025-10-01T05:03:28.633061Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"uid = df_loc.iloc[32].SeriesInstanceUID\t\nrow  = df[df.SeriesInstanceUID==uid]\nrow_loc = df_loc[df_loc.SeriesInstanceUID==uid]\ngt_loc = row_loc[['x', 'y']].values.astype('int32')\nseries_path = data_path/f'series/{uid}'\nrow","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.636273Z","iopub.execute_input":"2025-10-01T05:03:28.636723Z","iopub.status.idle":"2025-10-01T05:03:28.668582Z","shell.execute_reply.started":"2025-10-01T05:03:28.636702Z","shell.execute_reply":"2025-10-01T05:03:28.66799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_slices = process_dicom_for_yolo(series_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:28.669149Z","iopub.execute_input":"2025-10-01T05:03:28.6694Z","iopub.status.idle":"2025-10-01T05:03:30.772133Z","shell.execute_reply.started":"2025-10-01T05:03:28.669384Z","shell.execute_reply":"2025-10-01T05:03:30.771597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"location_preds = predict_yolo_ensemble(all_slices, conf_yolo = 0.01, iou_thresh=5.0, k = 3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:30.772854Z","iopub.execute_input":"2025-10-01T05:03:30.773048Z","iopub.status.idle":"2025-10-01T05:03:41.581742Z","shell.execute_reply.started":"2025-10-01T05:03:30.773032Z","shell.execute_reply":"2025-10-01T05:03:41.581115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"location_preds['MODEL0'].shape, location_preds['MODEL1'].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:41.58241Z","iopub.execute_input":"2025-10-01T05:03:41.58261Z","iopub.status.idle":"2025-10-01T05:03:41.588126Z","shell.execute_reply.started":"2025-10-01T05:03:41.582594Z","shell.execute_reply":"2025-10-01T05:03:41.58731Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Analysis Yolo Locations\n\n* Procedure: sample a lot of patches, select top K confident patches, concat then to form a K-channel tensor\n* Build: using this K channel tensor to build a model for classifying aneurysm","metadata":{}},{"cell_type":"code","source":"def load_dicom_series(series_dir: Path):\n    \"\"\"\n    Load a DICOM series into a 3D HU volume with affine matrix.\n\n    Args:\n        series_dir (Path): directory containing DICOM files\n\n    Returns:\n        volume (np.ndarray): 3D array (Z, Y, X) in HU\n        affine (np.ndarray): 4x4 voxel-to-world affine\n    \"\"\"\n    # Collect DICOMs\n    dcm_paths = [Path(series_dir) / f for f in os.listdir(series_dir) if f.lower().endswith(\".dcm\")]\n    if not dcm_paths:\n        raise FileNotFoundError(f\"No DICOM files found in {series_dir}\")\n\n    slices = [pydicom.dcmread(str(p), force=True) for p in dcm_paths]\n\n    # --- Orientation ---\n    orientation = slices[0].get(\"ImageOrientationPatient\", [1, 0, 0, 0, 1, 0])\n    orientation = np.array(orientation, dtype=np.float32).reshape(2, 3)\n    row_cos, col_cos = orientation\n    normal = np.cross(row_cos, col_cos)\n\n    # --- Sorting ---\n    if hasattr(slices[0], \"ImagePositionPatient\"):\n        slices.sort(key=lambda ds: np.dot(ds.get(\"ImagePositionPatient\", [0, 0, 0]), normal))\n    else:\n        # fallback: sort by InstanceNumber if available\n        slices.sort(key=lambda ds: getattr(ds, \"InstanceNumber\", 0))\n\n    # --- HU scaling ---\n    slope = float(getattr(slices[0], \"RescaleSlope\", 1.0))\n    intercept = float(getattr(slices[0], \"RescaleIntercept\", 0.0))\n    volume = np.stack([ds.pixel_array for ds in slices]).astype(np.float32)\n    volume = volume * slope + intercept\n\n    if volume.ndim == 4 and volume.shape[0] == 1:\n        volume = volume[0]  # remove extra batch dimension\n\n    slice_names = [Path(ds.filename).name for ds in slices]\n    return volume, slice_names\n\n\ndef normalize_vol(vol):\n    p2, p98 = np.percentile(vol, (2, 98))\n    mask = (vol >= p2) & (vol <= p98)\n    mean = np.mean(vol[mask])\n    std = np.std(vol[mask]) + 1e-6\n\n    vol = (vol - mean) / std\n    vol = np.clip((vol - vol.min()) / (vol.max() - vol.min() + 1e-6), 0, 1)\n    return vol","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:41.588939Z","iopub.execute_input":"2025-10-01T05:03:41.589222Z","iopub.status.idle":"2025-10-01T05:03:41.633841Z","shell.execute_reply.started":"2025-10-01T05:03:41.589206Z","shell.execute_reply":"2025-10-01T05:03:41.633273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AneurysmVolumeProcessor3Planes:\n    def __init__(self, N=96, K_axial=5, K_sagittal=15, K_coronal=15,\n                 Nr=64, Ntheta=128, augment=False, device='cpu', n_workers=4):\n        self.N = N\n        self.K_axial = K_axial\n        self.K_sagittal = K_sagittal\n        self.K_coronal = K_coronal\n        self.Nr = Nr\n        self.Ntheta = Ntheta\n        self.augment = augment\n        self.device = device\n        self.n_workers = n_workers\n\n    def __call__(self, volume, yolo_points):\n        def process_point(point):\n            x, y, z = map(int, map(round, point))\n            planes = {}\n\n            # --- Axial ---\n            K = self.K_axial\n            z_min, z_max = max(0, z-K//2), min(volume.shape[0], z+K//2+1)\n            y_min, y_max = max(0, y-self.N//2), min(volume.shape[1], y+self.N//2)\n            x_min, x_max = max(0, x-self.N//2), min(volume.shape[2], x+self.N//2)\n            axial_patch = volume[z_min:z_max, y_min:y_max, x_min:x_max].copy()\n            axial_patch = self._pad_patch(axial_patch, (K, self.N, self.N))\n            planes['axial'] = axial_patch\n\n            # --- Sagittal ---\n            K = self.K_sagittal\n            x_min, x_max = max(0, x-K//2), min(volume.shape[2], x+K//2+1)\n            z_min, z_max = max(0, z-self.N//2), min(volume.shape[0], z+self.N//2)\n            y_min, y_max = max(0, y-self.N//2), min(volume.shape[1], y+self.N//2)\n            sag_patch = volume[z_min:z_max, y_min:y_max, x_min:x_max].copy()\n            sag_patch = np.transpose(sag_patch, (2, 0, 1))  # (x, z, y)\n            sag_patch = self._pad_patch(sag_patch, (K, self.N, self.N))\n            planes['sagittal'] = sag_patch\n\n            # --- Coronal ---\n            K = self.K_coronal\n            y_min, y_max = max(0, y-K//2), min(volume.shape[1], y+K//2+1)\n            z_min, z_max = max(0, z-self.N//2), min(volume.shape[0], z+self.N//2)\n            x_min, x_max = max(0, x-self.N//2), min(volume.shape[2], x+self.N//2)\n            cor_patch = volume[z_min:z_max, y_min:y_max, x_min:x_max].copy()\n            cor_patch = np.transpose(cor_patch, (1, 0, 2))  # (y, z, x)\n            cor_patch = self._pad_patch(cor_patch, (K, self.N, self.N))\n            planes['coronal'] = cor_patch\n\n            # --- Cartesian & Log-Polar features ---\n            cartesian_channels, logpolar_channels = [], []\n            for plane_name, patch in planes.items():\n                center_slice = patch[patch.shape[0] // 2]\n                mip = np.max(patch, axis=0)\n\n                # stack [center_slice, mip] only\n                cartesian_channels.append(np.stack([center_slice, mip], axis=0))\n\n                cx, cy = self.N / 2, self.N / 2\n                logpolar_channels.append(np.stack([\n                    self._logpolar(center_slice, cx, cy),\n                    self._logpolar(mip, cx, cy)\n                ], axis=0))\n\n            return {\n                'cartesian': torch.from_numpy(np.stack(cartesian_channels, axis=0)).float(),\n                'logpolar': torch.from_numpy(np.stack(logpolar_channels, axis=0)).float(),\n                'axial': torch.from_numpy(planes['axial']).float(),\n                'sagittal': torch.from_numpy(planes['sagittal']).float(),\n                'coronal': torch.from_numpy(planes['coronal']).float()\n            }\n\n        # ✅ order preserved\n        with ThreadPoolExecutor(max_workers=self.n_workers) as executor:\n            outputs = list(executor.map(process_point, yolo_points))\n\n        return outputs\n        \n    def _pad_patch(self, patch, shape):\n        K, N, _ = shape\n        padded = np.zeros(shape, dtype=patch.dtype)\n        dz, dy, dx = patch.shape\n        padded[:dz, :dy, :dx] = patch\n        return padded\n\n    def _logpolar(self, img, cx, cy):\n        img = img.astype(np.float32)\n        max_radius = np.sqrt(\n            max(cx, img.shape[1]-cx)**2 + \n            max(cy, img.shape[0]-cy)**2\n        )\n        logpolar_img = cv2.logPolar(\n            img, center=(cx, cy),\n            M=self.Nr / np.log(max_radius + 1e-6),\n            flags=cv2.INTER_LINEAR + cv2.WARP_FILL_OUTLIERS\n        )\n        return cv2.resize(logpolar_img, (self.Ntheta, self.Nr))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:41.634711Z","iopub.execute_input":"2025-10-01T05:03:41.634954Z","iopub.status.idle":"2025-10-01T05:03:41.652089Z","shell.execute_reply.started":"2025-10-01T05:03:41.634934Z","shell.execute_reply":"2025-10-01T05:03:41.651566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vol, slice_names = load_dicom_series(series_path)","metadata":{"trusted":true,"_kg_hide-output":true,"_kg_hide-input":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:41.652755Z","iopub.execute_input":"2025-10-01T05:03:41.652937Z","iopub.status.idle":"2025-10-01T05:03:42.754952Z","shell.execute_reply.started":"2025-10-01T05:03:41.652923Z","shell.execute_reply":"2025-10-01T05:03:42.753969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gt_loc_z = np.array([[slice_names.index(f'{row_loc.SOPInstanceUID.values[i]}.dcm')] for i in range(row_loc.SOPInstanceUID.values.shape[0])])\ngt_loc_xy = row_loc[['x', 'y']].values.astype('int32')\ngt_loc_3d = np.concatenate([gt_loc_xy, gt_loc_z], axis=-1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:42.755872Z","iopub.execute_input":"2025-10-01T05:03:42.756087Z","iopub.status.idle":"2025-10-01T05:03:42.762747Z","shell.execute_reply.started":"2025-10-01T05:03:42.756072Z","shell.execute_reply":"2025-10-01T05:03:42.761495Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gt_loc_3d","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:42.763726Z","iopub.execute_input":"2025-10-01T05:03:42.76403Z","iopub.status.idle":"2025-10-01T05:03:42.797494Z","shell.execute_reply.started":"2025-10-01T05:03:42.764003Z","shell.execute_reply":"2025-10-01T05:03:42.796839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vol_norm = normalize_vol(vol)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:42.79823Z","iopub.execute_input":"2025-10-01T05:03:42.798483Z","iopub.status.idle":"2025-10-01T05:03:43.264415Z","shell.execute_reply.started":"2025-10-01T05:03:42.798467Z","shell.execute_reply":"2025-10-01T05:03:43.263768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vol_norm.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.265068Z","iopub.execute_input":"2025-10-01T05:03:43.265819Z","iopub.status.idle":"2025-10-01T05:03:43.27046Z","shell.execute_reply.started":"2025-10-01T05:03:43.265794Z","shell.execute_reply":"2025-10-01T05:03:43.269879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N=64","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.271038Z","iopub.execute_input":"2025-10-01T05:03:43.271288Z","iopub.status.idle":"2025-10-01T05:03:43.28482Z","shell.execute_reply.started":"2025-10-01T05:03:43.271259Z","shell.execute_reply":"2025-10-01T05:03:43.284287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"proc = AneurysmVolumeProcessor3Planes(N=N, K_axial=15, K_sagittal=15, K_coronal=15,\n                 Nr=N, Ntheta=N, augment=False, device='cpu')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.285569Z","iopub.execute_input":"2025-10-01T05:03:43.285999Z","iopub.status.idle":"2025-10-01T05:03:43.298143Z","shell.execute_reply.started":"2025-10-01T05:03:43.285982Z","shell.execute_reply":"2025-10-01T05:03:43.297516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"yolo_points_0 = location_preds['MODEL0'][:, [2, 1, 0]].astype('int32') #x, y, z\nyolo_points_1 = location_preds['MODEL1'][:, [2, 1, 0]].astype('int32')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.29883Z","iopub.execute_input":"2025-10-01T05:03:43.299015Z","iopub.status.idle":"2025-10-01T05:03:43.309298Z","shell.execute_reply.started":"2025-10-01T05:03:43.299002Z","shell.execute_reply":"2025-10-01T05:03:43.308696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"output_0 = proc(vol_norm, yolo_points_0)\noutput_1 = proc(vol_norm, yolo_points_1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.309885Z","iopub.execute_input":"2025-10-01T05:03:43.31016Z","iopub.status.idle":"2025-10-01T05:03:43.336298Z","shell.execute_reply.started":"2025-10-01T05:03:43.310143Z","shell.execute_reply":"2025-10-01T05:03:43.335637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_features_with_center(outputs, yolo_points, gt_points=None, N=96):\n    \"\"\"\n    Visualize Cartesian and Log-Polar channels with YOLO and GT markers.\n    Handles cropped patches (local coordinate transform).\n    \n    outputs: list of dicts with keys:\n        'cartesian' [3 planes, 3 channels, N, N], \n        'logpolar' [3 planes, 3 channels, Nr, Ntheta]\n    yolo_points: list of YOLO (x, y, z) in full volume coordinates\n    gt_points: optional list of GT (x, y, z) in full volume coordinates\n    N: patch size\n    \"\"\"\n    plane_titles = ['Axial', 'Sagittal', 'Coronal']\n    channel_titles = ['Center Slice', 'MIP', 'Vesselness MIP']\n\n    for idx, patch in enumerate(outputs):\n        x, y, z = map(int, map(round, yolo_points[idx]))\n        print(f'Patch {idx} at YOLO point {yolo_points[idx]}')\n\n        cartesian = patch['cartesian'].numpy()   # [3, 3, N, N]\n        logpolar = patch['logpolar'].numpy()     # [3, 3, Nr, Ntheta]\n\n        N = cartesian.shape[-1]\n        Nr, Ntheta = logpolar.shape[-2:]\n\n        cx_cart, cy_cart = N//2, N//2\n        cx_log, cy_log = Ntheta//2, Nr//2\n\n        # --- compute GT local coords (Axial only for now) ---\n        gx_local, gy_local = None, None\n        if gt_points is not None:\n            gx, gy = gt_points[:, 0], gt_points[:, 1]\n            gx_local = gx - (x - N//2)\n            gy_local = gy - (y - N//2)\n\n        for p_idx, plane in enumerate(plane_titles):\n            plt.figure(figsize=(12, 6))\n            plt.suptitle(f'{plane} Plane - Patch {idx}', fontsize=16)\n\n            # Cartesian channels\n            for c_idx, ch in enumerate(channel_titles):\n                plt.subplot(2, 3, c_idx+1)\n                plt.imshow(cartesian[p_idx, c_idx], cmap='gray')\n\n                # YOLO predicted center\n                plt.scatter(cx_cart, cy_cart, s=40, c='blue', marker='x', label='YOLO center')\n\n                # Ground truth center (only for Axial plane, if inside crop)\n                if plane == 'Axial' and gx_local is not None:\n                    if 0 <= gx_local < N and 0 <= gy_local < N:\n                        plt.scatter(gx_local, gy_local, s=40, c='red', marker='o', label='GT center')\n\n                plt.title(f'{ch} - Cartesian')\n                plt.axis('off')\n\n                if c_idx == 0 and plane == 'Axial' and gx_local is not None:\n                    plt.legend()\n\n            # Log-polar channels\n            for c_idx, ch in enumerate(channel_titles):\n                plt.subplot(2, 3, c_idx+4)\n                plt.imshow(logpolar[p_idx, c_idx], cmap='gray', origin='lower')\n                plt.scatter(cx_log, cy_log, s=30, c='blue', marker='x')\n                plt.title(f'{ch} - Log-Polar')\n                plt.xlabel('Theta / Azimuth')\n                plt.ylabel('Radius (log)')\n                plt.axis('on')\n\n            plt.tight_layout(rect=[0, 0, 1, 0.95])\n            plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.338948Z","iopub.execute_input":"2025-10-01T05:03:43.339364Z","iopub.status.idle":"2025-10-01T05:03:43.34871Z","shell.execute_reply.started":"2025-10-01T05:03:43.339344Z","shell.execute_reply":"2025-10-01T05:03:43.348056Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"output_0[0]['cartesian'].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.349354Z","iopub.execute_input":"2025-10-01T05:03:43.349557Z","iopub.status.idle":"2025-10-01T05:03:43.364211Z","shell.execute_reply.started":"2025-10-01T05:03:43.349542Z","shell.execute_reply":"2025-10-01T05:03:43.36371Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for name in ['axial', 'sagittal', 'coronal']:\n    print(name)\n    print(output_0[0][name].shape)\n    print(output_1[0][name].shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.36481Z","iopub.execute_input":"2025-10-01T05:03:43.365105Z","iopub.status.idle":"2025-10-01T05:03:43.377453Z","shell.execute_reply.started":"2025-10-01T05:03:43.36509Z","shell.execute_reply":"2025-10-01T05:03:43.376865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# show_features_with_center(output_0, yolo_points_0, gt_loc, N)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.378055Z","iopub.execute_input":"2025-10-01T05:03:43.378321Z","iopub.status.idle":"2025-10-01T05:03:43.391818Z","shell.execute_reply.started":"2025-10-01T05:03:43.378301Z","shell.execute_reply":"2025-10-01T05:03:43.39113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# show_features_with_center(output_1, yolo_points_1, gt_loc, N)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.392468Z","iopub.execute_input":"2025-10-01T05:03:43.392687Z","iopub.status.idle":"2025-10-01T05:03:43.403104Z","shell.execute_reply.started":"2025-10-01T05:03:43.392668Z","shell.execute_reply":"2025-10-01T05:03:43.402417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Wavelet Analysis","metadata":{}},{"cell_type":"code","source":"import pywt\nimport plotly.graph_objects as go","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.403768Z","iopub.execute_input":"2025-10-01T05:03:43.40396Z","iopub.status.idle":"2025-10-01T05:03:43.430867Z","shell.execute_reply.started":"2025-10-01T05:03:43.403946Z","shell.execute_reply":"2025-10-01T05:03:43.430192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_wavelet_and_gt_loc(points, output, idx, view):\n    gt_patch = np.array([\n        gt_loc_3d[0, 2] - points[idx][2],  # depth (z)\n        gt_loc_3d[0, 1] - points[idx][1],  # row   (y)\n        gt_loc_3d[0, 0] - points[idx][0]   # col   (x)\n    ])/2\n    \n    x =  output[0][view]\n    x_np = x.numpy()\n    \n    # 3D DWT\n    coeffs = pywt.dwtn(x_np, wavelet='bior3.5', axes=(0,1,2))  # 3D Haar\n    normalized_coeffs = {}\n    for band, band_data in coeffs.items():\n        normalized_coeffs[band] = (band_data - np.mean(band_data)) / (np.std(band_data) + 1e-10)\n    return normalized_coeffs, gt_patch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.431691Z","iopub.execute_input":"2025-10-01T05:03:43.432078Z","iopub.status.idle":"2025-10-01T05:03:43.436857Z","shell.execute_reply.started":"2025-10-01T05:03:43.43206Z","shell.execute_reply":"2025-10-01T05:03:43.436388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"coeffs_ax, gt_patch_ax = get_wavelet_and_gt_loc(yolo_points_0, output_0, 0, 'axial')\n\ncoeffs_sag, gt_patch_sag = get_wavelet_and_gt_loc(yolo_points_0, output_0, 0, 'sagittal')\n\ncoeffs_cor, gt_patch_cor = get_wavelet_and_gt_loc(yolo_points_0, output_0, 0, 'coronal')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:43.437503Z","iopub.execute_input":"2025-10-01T05:03:43.437721Z","iopub.status.idle":"2025-10-01T05:03:43.472611Z","shell.execute_reply.started":"2025-10-01T05:03:43.437706Z","shell.execute_reply":"2025-10-01T05:03:43.472097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"aneurysm_map = np.sqrt(\n    sum(np.square(coeffs_ax[band]) \n        for band in coeffs_ax if band != 'aaa')  # 'aaa' = low-freq band\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:03:50.959455Z","iopub.execute_input":"2025-10-01T05:03:50.959694Z","iopub.status.idle":"2025-10-01T05:03:50.973684Z","shell.execute_reply.started":"2025-10-01T05:03:50.959679Z","shell.execute_reply":"2025-10-01T05:03:50.973158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig = go.Figure(data=go.Volume(\n    x=np.arange(aneurysm_map.shape[0]).repeat(aneurysm_map.shape[1]*aneurysm_map.shape[2]),\n    y=np.tile(np.arange(aneurysm_map.shape[1]).repeat(aneurysm_map.shape[2]), aneurysm_map.shape[0]),\n    z=np.tile(np.arange(aneurysm_map.shape[2]), aneurysm_map.shape[0]*aneurysm_map.shape[1]),\n    value=aneurysm_map.flatten(),\n    isomin=np.percentile(aneurysm_map, 95),  # show top 5% anomalies\n    isomax=aneurysm_map.max(),\n    opacity=0.1,  # lower for more transparency\n    surface_count=20,  # number of layers\n    colorscale=\"Hot\"\n))\n\nfig.update_layout(scene=dict(\n    xaxis=dict(visible=False),\n    yaxis=dict(visible=False),\n    zaxis=dict(visible=False)\n))\n\nfig.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:05:35.28233Z","iopub.execute_input":"2025-10-01T05:05:35.283181Z","iopub.status.idle":"2025-10-01T05:05:35.306265Z","shell.execute_reply.started":"2025-10-01T05:05:35.283151Z","shell.execute_reply":"2025-10-01T05:05:35.305554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_wavelet3d_all(coeffs, gt_point=None, mode='slices'):\n    bands = list(coeffs.keys())\n\n    if mode == 'slices':\n        n_bands = len(bands)\n        fig, axes = plt.subplots(n_bands, 3, figsize=(10, 3*n_bands))\n\n        if n_bands == 1:\n            axes = np.expand_dims(axes, 0)\n\n        for i, band in enumerate(bands):\n            vol = np.array(coeffs[band])\n            d, h, w = vol.shape\n            dz, dy, dx = d//2, h//2, w//2\n\n            # Axial (Z fixed)\n            axes[i, 0].imshow(vol[dz, :, :], cmap='gray')\n            axes[i, 0].set_title(f\"{band} - Axial\")\n            if gt_point is not None:\n                axes[i, 0].scatter(gt_point[2] + dx, gt_point[1] + dy, c='r', s=40, marker='x')\n\n            # Coronal (Y fixed)\n            axes[i, 1].imshow(vol[:, dy, :], cmap='gray')\n            axes[i, 1].set_title(f\"{band} - Coronal\")\n            if gt_point is not None:\n                axes[i, 1].scatter(gt_point[2] + dx, gt_point[0] + dz, c='r', s=40, marker='x')\n\n            # Sagittal (X fixed)\n            axes[i, 2].imshow(vol[:, :, dx], cmap='gray')\n            axes[i, 2].set_title(f\"{band} - Sagittal\")\n            if gt_point is not None:\n                axes[i, 2].scatter(gt_point[1] + dy, gt_point[0] + dz, c='r', s=40, marker='x')\n\n        plt.tight_layout()\n        plt.show()\n\n    elif mode == 'volume':\n        for i, band in enumerate(bands):\n            vol = np.array(coeffs[band])\n            d, h, w = vol.shape\n\n            fig = go.Figure()\n\n            # Volume rendering\n            fig.add_trace(go.Volume(\n                x=np.arange(d).repeat(h*w),\n                y=np.tile(np.arange(h).repeat(w), d),\n                z=np.tile(np.arange(w), d*h),\n                value=vol.flatten(),\n                opacity=0.1,\n                surface_count=15,\n                colorscale='jet',\n            ))\n\n            # Add GT point as red sphere with offsets\n            if gt_point is not None:\n                dz, dy, dx = d//2, h//2, w//2\n                fig.add_trace(go.Scatter3d(\n                    x=[gt_point[0] + dz],\n                    y=[gt_point[1] + dy],\n                    z=[gt_point[2] + dx],\n                    mode='markers',\n                    marker=dict(size=10, color='red'),\n                    name='GT Point'\n                ))\n\n            fig.update_layout(\n                title=f\"3D Wavelet Volume - {band}\",\n                scene=dict(\n                    xaxis_title=\"Depth\",\n                    yaxis_title=\"Height\",\n                    zaxis_title=\"Width\"\n                )\n            )\n            fig.show()\n    else:\n        raise ValueError(\"mode must be 'slices' or 'volume'\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:04:13.562987Z","iopub.execute_input":"2025-10-01T05:04:13.563295Z","iopub.status.idle":"2025-10-01T05:04:13.573768Z","shell.execute_reply.started":"2025-10-01T05:04:13.563267Z","shell.execute_reply":"2025-10-01T05:04:13.573209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_ax, gt_patch_ax, mode='volume')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T05:04:33.647487Z","iopub.execute_input":"2025-10-01T05:04:33.647737Z","iopub.status.idle":"2025-10-01T05:04:34.179111Z","shell.execute_reply.started":"2025-10-01T05:04:33.647719Z","shell.execute_reply":"2025-10-01T05:04:34.178349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"coeffs_ax['aaa'].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:12.686517Z","iopub.execute_input":"2025-10-01T04:55:12.686698Z","iopub.status.idle":"2025-10-01T04:55:12.703291Z","shell.execute_reply.started":"2025-10-01T04:55:12.686683Z","shell.execute_reply":"2025-10-01T04:55:12.702714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_ax, gt_patch_ax, mode='slices')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:12.703917Z","iopub.execute_input":"2025-10-01T04:55:12.704092Z","iopub.status.idle":"2025-10-01T04:55:15.469892Z","shell.execute_reply.started":"2025-10-01T04:55:12.704079Z","shell.execute_reply":"2025-10-01T04:55:15.469051Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_ax, gt_patch_ax, mode='volume')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:15.470684Z","iopub.execute_input":"2025-10-01T04:55:15.470886Z","iopub.status.idle":"2025-10-01T04:55:15.557007Z","shell.execute_reply.started":"2025-10-01T04:55:15.470868Z","shell.execute_reply":"2025-10-01T04:55:15.556304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_sag, gt_patch_sag, mode='slices')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:15.557729Z","iopub.execute_input":"2025-10-01T04:55:15.557923Z","iopub.status.idle":"2025-10-01T04:55:18.697622Z","shell.execute_reply.started":"2025-10-01T04:55:15.557907Z","shell.execute_reply":"2025-10-01T04:55:18.696836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_sag, gt_patch_sag, mode='volume')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:18.698358Z","iopub.execute_input":"2025-10-01T04:55:18.698563Z","iopub.status.idle":"2025-10-01T04:55:18.785676Z","shell.execute_reply.started":"2025-10-01T04:55:18.698546Z","shell.execute_reply":"2025-10-01T04:55:18.785034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_cor, gt_patch_sag, mode='slices')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:18.786494Z","iopub.execute_input":"2025-10-01T04:55:18.786734Z","iopub.status.idle":"2025-10-01T04:55:21.588658Z","shell.execute_reply.started":"2025-10-01T04:55:18.786715Z","shell.execute_reply":"2025-10-01T04:55:21.587807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"visualize_wavelet3d_all(coeffs_cor, gt_patch_sag, mode='volume')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T04:55:21.589491Z","iopub.execute_input":"2025-10-01T04:55:21.590004Z","iopub.status.idle":"2025-10-01T04:55:21.683783Z","shell.execute_reply.started":"2025-10-01T04:55:21.589979Z","shell.execute_reply":"2025-10-01T04:55:21.683031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}