{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":52254,"databundleVersionId":9674523},{"sourceType":"datasetVersion","sourceId":14785973,"datasetId":9452485,"databundleVersionId":15639569}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install dicomsdl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:25.311111Z","iopub.execute_input":"2026-02-27T13:31:25.311409Z","iopub.status.idle":"2026-02-27T13:31:30.927864Z","shell.execute_reply.started":"2026-02-27T13:31:25.311385Z","shell.execute_reply":"2026-02-27T13:31:30.926753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport os\nfrom glob import glob\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport pydicom\nimport SimpleITK as sitk\nimport matplotlib.patches as patches\nimport numpy.ma as ma\nfrom ipywidgets import interact, IntSlider, HBox, Button, Output , Dropdown, VBox\nfrom IPython.display import FileLink, display\nimport uuid\nimport pandas as pd\nimport dicomsdl\nimport nibabel as nib\nfrom scipy.ndimage import zoom\n\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n\"\"\"\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\"\"\"\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:30.929857Z","iopub.execute_input":"2026-02-27T13:31:30.930199Z","iopub.status.idle":"2026-02-27T13:31:32.961116Z","shell.execute_reply.started":"2026-02-27T13:31:30.930145Z","shell.execute_reply":"2026-02-27T13:31:32.960375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ROOT_PATH = \"/kaggle/input/rsna-2023-abdominal-trauma-detection\"\nMASKS_PATH = \"/kaggle/input/datasets/bmohamedelamine/rsna-2023-spleen-masks\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:32.962796Z","iopub.execute_input":"2026-02-27T13:31:32.963202Z","iopub.status.idle":"2026-02-27T13:31:32.969377Z","shell.execute_reply.started":"2026-02-27T13:31:32.963177Z","shell.execute_reply":"2026-02-27T13:31:32.968864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_dicom_series(series_dir):\n    files = glob(os.path.join(series_dir, \"*.dcm\"))\n    if not files:\n        raise FileNotFoundError(\"No DICOM files found\")\n\n    slices = []\n    zs = []\n\n    for f in files:\n        ds = pydicom.dcmread(f)\n        img = ds.pixel_array.astype(np.float32)\n\n        slope = float(getattr(ds, \"RescaleSlope\", 1.0))\n        intercept = float(getattr(ds, \"RescaleIntercept\", 0.0))\n        img = img * slope + intercept\n\n        ipp = getattr(ds, \"ImagePositionPatient\", None)\n        if ipp is not None and len(ipp) >= 3:\n            z = float(ipp[2])\n        else:\n            z = float(getattr(ds, \"InstanceNumber\", 0))\n\n        slices.append(img)\n        zs.append(z)\n\n    order = np.argsort(np.array(zs))\n    volume = np.stack([slices[i] for i in order])\n\n    center, width = wc, ww\n    low, high = center - width/2, center + width/2\n    volume = np.clip(volume, low, high)\n    volume = (volume - low) / (high - low + 1e-6)\n\n    return volume\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:32.969959Z","iopub.execute_input":"2026-02-27T13:31:32.970129Z","iopub.status.idle":"2026-02-27T13:31:33.010314Z","shell.execute_reply.started":"2026-02-27T13:31:32.970111Z","shell.execute_reply":"2026-02-27T13:31:33.009438Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef __dataset__to_numpy_image(self, index=0):\n    info = self.getPixelDataInfo()\n    dtype = info['dtype']\n\n    if info['SamplesPerPixel'] != 1:\n        raise RuntimeError('SamplesPerPixel != 1')\n    else:\n        shape = [info['Rows'], info['Cols']]\n\n    arr = np.empty(shape, dtype=dtype)\n    self.copyFrameData(index, arr)\n    return arr\n\n# only patch if not already there\nif not hasattr(dicomsdl._dicomsdl.DataSet, \"to_numpy_image\"):\n    dicomsdl._dicomsdl.DataSet.to_numpy_image = __dataset__to_numpy_image\n    \ndef fast_glob_sorted(path):\n    # RSNA filenames: 1.dcm, 2.dcm ... → numeric sort is enough and cheap\n    return sorted(glob(path), key=lambda x: int(os.path.basename(x).split('.')[0]))\n\n\ndef fast_window(img):\n    WL,WW = wc,ww\n    low  = WL - WW/2\n    high = WL + WW/2\n    img = np.clip(img, low, high)\n    img = (img - low) / (high - low + 1e-6)\n    return img\n\n\ndef load_dicom_series_fast(series_dir):\n    paths = fast_glob_sorted(os.path.join(series_dir, \"*.dcm\"))\n    if not paths:\n        raise FileNotFoundError(\"No DICOM files found\")\n\n    volume = []\n\n    for p in paths:\n        dcm = dicomsdl.open(p)\n\n        img = dcm.to_numpy_image().astype(np.float32)\n\n        slope = getattr(dcm, \"RescaleSlope\", 1.0)\n        intercept = getattr(dcm, \"RescaleIntercept\", 0.0)\n        img = img * slope + intercept\n\n        img = fast_window(img)\n\n        volume.append(img)\n\n    return np.stack(volume).astype(np.float32)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:33.011731Z","iopub.execute_input":"2026-02-27T13:31:33.011919Z","iopub.status.idle":"2026-02-27T13:31:33.036671Z","shell.execute_reply.started":"2026-02-27T13:31:33.011902Z","shell.execute_reply":"2026-02-27T13:31:33.035652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_mask(mask_path, volume_shape):\n    \"\"\"Load a .nii_gz mask, fix orientation, and resize to match DICOM volume.\"\"\"\n    import shutil\n    tmp = os.path.join(\"/kaggle/working\", \"tmp_mask.nii.gz\")\n    shutil.copy(mask_path, tmp)\n\n    mask_nii = nib.load(tmp)\n    mask_data = mask_nii.get_fdata()\n\n    # NIfTI stores as (X, Y, Z) in RAS; DICOM volume is stacked as (Z, Y, X)\n    mask_data = np.transpose(mask_data, (2, 1, 0))  # → (Z, Y, X)\n    mask_data = mask_data[::-1, :, :]             # flip \n\n    # Resize to volume dims\n    factors = np.array(volume_shape) / np.array(mask_data.shape)\n    resized = zoom(mask_data, factors, order=0)\n    return (resized > 0.5).astype(np.uint8)\n\n\ndef rsna_plain_viewer(ROOT_PATH, MASKS_PATH):\n    csv_path = os.path.join(ROOT_PATH, \"train_2024.csv\")\n    df = pd.read_csv(csv_path)\n\n    # ---------------- LABEL FILTER DROPDOWNS ----------------\n    spleen_dd = Dropdown(description=\"Spleen\", options=[\"Any\",\"Healthy\",\"Low\",\"High\"], value=\"Any\", layout={'width':'180px'})\n    liver_dd  = Dropdown(description=\"Liver\",  options=[\"Any\",\"Healthy\",\"Low\",\"High\"], value=\"Any\", layout={'width':'180px'})\n    kidney_dd = Dropdown(description=\"Kidney\", options=[\"Any\",\"Healthy\",\"Low\",\"High\"], value=\"Any\", layout={'width':'190px'})\n    bowel_dd  = Dropdown(description=\"Bowel\",  options=[\"Any\",\"Healthy\",\"Injury\"],     value=\"Any\", layout={'width':'180px'})\n    extra_dd  = Dropdown(description=\"Extravasation\", options=[\"Any\",\"Healthy\",\"Injury\"], value=\"Any\", layout={'width':'220px'})\n\n    patient_dd = Dropdown(description=\"Patient:\")\n    series_dd  = Dropdown(description=\"Series:\")\n    slider     = IntSlider(description=\"Slice\")\n    save_btn   = Button(description=\"Save Slice\", button_style=\"success\")\n\n    labels_out = Output()\n    img_out    = Output()\n    save_out   = Output()\n\n    # ---------------- FILTERING LOGIC ----------------\n    def apply_filter():\n        temp = df.copy()\n        if spleen_dd.value == \"Low\":       temp = temp[temp.spleen_low == 1]\n        elif spleen_dd.value == \"High\":    temp = temp[temp.spleen_high == 1]\n        elif spleen_dd.value == \"Healthy\": temp = temp[(temp.spleen_low == 0) & (temp.spleen_high == 0)]\n\n        if liver_dd.value == \"Low\":        temp = temp[temp.liver_low == 1]\n        elif liver_dd.value == \"High\":     temp = temp[temp.liver_high == 1]\n        elif liver_dd.value == \"Healthy\":  temp = temp[(temp.liver_low == 0) & (temp.liver_high == 0)]\n\n        if kidney_dd.value == \"Low\":       temp = temp[temp.kidney_low == 1]\n        elif kidney_dd.value == \"High\":    temp = temp[temp.kidney_high == 1]\n        elif kidney_dd.value == \"Healthy\": temp = temp[(temp.kidney_low == 0) & (temp.kidney_high == 0)]\n\n        if bowel_dd.value == \"Injury\":     temp = temp[temp.bowel_injury == 1]\n        elif bowel_dd.value == \"Healthy\":  temp = temp[temp.bowel_injury == 0]\n\n        if extra_dd.value == \"Injury\":     temp = temp[temp.extravasation_injury == 1]\n        elif extra_dd.value == \"Healthy\":  temp = temp[temp.extravasation_injury == 0]\n        return temp\n\n    def update_patients(change):\n        filtered = apply_filter()\n        if filtered.empty:\n            patient_dd.options = []\n            with labels_out:\n                labels_out.clear_output()\n                print(\"⚠️ No matching patients\")\n            return\n        pts = filtered.patient_id.astype(str).tolist()\n        patient_dd.options = pts\n        patient_dd.value = pts[0]\n\n    def show_labels(pid):\n        with labels_out:\n            labels_out.clear_output()\n            row = df[df.patient_id == int(pid)]\n            if row.empty:\n                print(\"No label entry found.\")\n                return\n            row = row.iloc[0]\n            print(f\"Patient {pid}\")\n            print(\"Labels:\")\n            for col in row.index:\n                if col != \"patient_id\" and row[col] == 1:\n                    print(\"  -\", col)\n\n    def update_series(change):\n        pid = patient_dd.value\n        patient_dir = os.path.join(ROOT_PATH, \"train_images\", pid)\n        series = sorted(os.listdir(patient_dir))\n        series_dd.options = series\n        if series:\n            series_dd.value = series[0]\n        show_labels(pid)\n        load_series(None)\n\n    # ---------------- LOAD + VIEW (with mask) ----------------\n    def load_series(change):\n        pid = patient_dd.value\n        sid = series_dd.value\n        if pid is None or sid is None:\n            return\n\n        series_dir = os.path.join(ROOT_PATH, \"train_images\", pid, sid)\n        volume = load_dicom_series_fast(series_dir)\n\n        # Try to load the spleen mask\n        mask = None\n        mask_dir = os.path.join(MASKS_PATH, str(pid), str(sid))\n        if os.path.isdir(mask_dir):\n            mask_files = [f for f in os.listdir(mask_dir) if f.endswith(\"nii_gz\") or f.endswith(\".nii.gz\")]\n            if mask_files:\n                mask_path = os.path.join(mask_dir, mask_files[0])\n                try:\n                    mask = load_mask(mask_path, volume.shape)\n                except Exception as e:\n                    print(f\"⚠️ Mask load failed: {e}\")\n                    mask = None\n\n        slider.min = 0\n        slider.max = volume.shape[0] - 1\n        slider.value = volume.shape[0] // 2\n\n        def _draw_overlay(ax, ct_slice, mask_slice):\n            \"\"\"Draw CT with green mask fill + lime contour on given axes.\"\"\"\n            ax.imshow(ct_slice, cmap=\"gray\")\n            if mask_slice is not None and mask_slice.any():\n                # Semi-transparent green fill via RGBA overlay\n                rgba = np.zeros((*mask_slice.shape, 4), dtype=np.float32)\n                rgba[mask_slice == 1] = [0.0, 1.0, 0.2, 0.35]\n                ax.imshow(rgba)\n                # Sharp lime contour\n                ax.contour(mask_slice, levels=[0.5], colors='lime', linewidths=1.5)\n\n        def update_slice(change):\n            z = slider.value\n            with img_out:\n                img_out.clear_output(wait=True)\n                has_mask = mask is not None\n\n                fig, axes = plt.subplots(1, 2 if has_mask else 1,\n                                         figsize=(12 if has_mask else 6, 6))\n\n                if has_mask:\n                    ax_ct, ax_overlay = axes\n\n                    ax_ct.imshow(volume[z], cmap=\"gray\")\n                    ax_ct.set_title(f\"CT — Slice {z+1}/{volume.shape[0]}\")\n                    ax_ct.axis(\"off\")\n\n                    _draw_overlay(ax_overlay, volume[z], mask[z])\n                    ax_overlay.set_title(\"Spleen Mask Overlay\")\n                    ax_overlay.axis(\"off\")\n                else:\n                    axes.imshow(volume[z], cmap=\"gray\")\n                    axes.set_title(f\"CT — Slice {z+1}/{volume.shape[0]} (no mask)\")\n                    axes.axis(\"off\")\n\n                fig.suptitle(f\"Patient {pid} | Series {sid}\", fontsize=13)\n                plt.tight_layout()\n                plt.show()\n\n        slider.observe(update_slice, names=\"value\")\n        update_slice(None)\n\n        def save_slice(b):\n            z = slider.value\n            filename = f\"{pid}_{sid}_slice{z+1}_{uuid.uuid4().hex[:6]}.png\"\n            fig, ax = plt.subplots(figsize=(6, 6))\n            _draw_overlay(ax, volume[z], mask[z] if mask is not None else None)\n            ax.axis(\"off\")\n            fig.savefig(filename, bbox_inches=\"tight\", pad_inches=0)\n            plt.close(fig)\n            with save_out:\n                save_out.clear_output()\n                print(\"Saved:\")\n                display(FileLink(filename))\n\n        save_btn._click_handlers.callbacks = []\n        save_btn.on_click(save_slice)\n\n    # ---------------- HOOK EVENTS ----------------\n    for w in [spleen_dd, liver_dd, kidney_dd, bowel_dd, extra_dd]:\n        w.observe(update_patients, names=\"value\")\n    patient_dd.observe(update_series, names=\"value\")\n    series_dd.observe(load_series, names=\"value\")\n    update_patients(None)\n\n    display(VBox([\n        HBox([spleen_dd, liver_dd, kidney_dd, bowel_dd, extra_dd]),\n        patient_dd, series_dd, labels_out,\n        HBox([slider, save_btn]),\n        img_out, save_out\n    ]))\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:33.038213Z","iopub.execute_input":"2026-02-27T13:31:33.038585Z","iopub.status.idle":"2026-02-27T13:31:33.066201Z","shell.execute_reply.started":"2026-02-27T13:31:33.038549Z","shell.execute_reply":"2026-02-27T13:31:33.065362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wc, ww = 50, 400 \nrsna_plain_viewer(ROOT_PATH, MASKS_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-27T13:31:33.06712Z","iopub.execute_input":"2026-02-27T13:31:33.067372Z"}},"outputs":[],"execution_count":null}]}