{"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":"none","dataSources":[{"sourceId":99552,"databundleVersionId":13190393,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**Relative location plotting as part of the EDA pipeline**:\n- extract_coordinates: parse pixel coords\n- compute_physical_and_relative: compute x_mm, y_mm, x_rel, y_rel using DICOM metadata cache\n- plot_global: global relative scatter by location\n- plot_per_location: per-location relative scatter & boxplots","metadata":{}},{"cell_type":"code","source":"# --- libraries ---\n\nimport os\nfrom pathlib import Path\nimport ast\nfrom typing import Dict, Any\n\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.132431Z","iopub.execute_input":"2025-08-02T17:12:28.1327Z","iopub.status.idle":"2025-08-02T17:12:28.148522Z","shell.execute_reply.started":"2025-08-02T17:12:28.132674Z","shell.execute_reply":"2025-08-02T17:12:28.147613Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"First lets build a utility to extract the coordinates from the train_localizers.csv file. The coordinates are stored in dictionary in the coordinates column.","metadata":{}},{"cell_type":"code","source":"\n# --- Utility: Extract pixel coords ---\ndef extract_coordinates(\n    df: pd.DataFrame,\n    coord_col: str = 'coordinates',\n    x_col: str = 'x_pix',\n    y_col: str = 'y_pix'\n) -> pd.DataFrame:\n    \"\"\"\n    Parse a coordinate field (dict or literal str) into integer pixel columns.\n    \"\"\"\n    df = df.copy()\n    def _to_dict(v):\n        if isinstance(v, dict):\n            return v\n        return ast.literal_eval(v)\n\n    coords = df[coord_col].apply(_to_dict).tolist()\n    pix = pd.DataFrame(coords, index=df.index)[['x','y']].astype(int)\n    df[x_col], df[y_col] = pix['x'], pix['y']\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.149467Z","iopub.execute_input":"2025-08-02T17:12:28.149787Z","iopub.status.idle":"2025-08-02T17:12:28.167202Z","shell.execute_reply.started":"2025-08-02T17:12:28.149757Z","shell.execute_reply":"2025-08-02T17:12:28.166336Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now lets get some of the metadata from the dicom files themselves. ","metadata":{}},{"cell_type":"code","source":"# --- Utility: DICOM geometry ---\ndef get_image_geometry(ds: pydicom.Dataset) -> Dict[str, Any]:\n    \"\"\"Return rows, columns, spacings, cosines, and origin.\"\"\"\n    rows = int(ds.get('Rows', 512)); cols = int(ds.get('Columns', 512))\n    ps = ds.get('PixelSpacing', [1.0,1.0])\n    rs, cs = float(ps[0]), float(ps[1])\n    origin = np.array(ds.get('ImagePositionPatient', [0,0,0]), float)\n    iop = list(map(float, ds.get('ImageOrientationPatient', [1,0,0,0,1,0])))\n    row_cos, col_cos = np.array(iop[:3]), np.array(iop[3:])\n    return dict(\n        rows=rows, columns=cols,\n        row_spacing_mm=rs, col_spacing_mm=cs,\n        origin_xyz=origin,\n        row_cosine=row_cos, col_cosine=col_cos\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.168135Z","iopub.execute_input":"2025-08-02T17:12:28.168483Z","iopub.status.idle":"2025-08-02T17:12:28.188274Z","shell.execute_reply.started":"2025-08-02T17:12:28.168463Z","shell.execute_reply":"2025-08-02T17:12:28.187235Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now, everyone's favorite math section!","metadata":{}},{"cell_type":"code","source":"# --- Compute physical & relative coords ---\ndef compute_physical_and_relative(\n    df: pd.DataFrame,\n    sop_col: str,\n    x_pix: str,\n    y_pix: str,\n    dicom_index: Dict[str, Path]\n) -> pd.DataFrame:\n    \"\"\"\n    For each row, load/cached DICOM geometry and compute:\n      - x_mm, y_mm (world patient coords)\n      - x_rel, y_rel (normalized within image)\n    Also adds 'rows' and 'columns'.\n    \"\"\"\n    out = df.copy()\n    cache: Dict[str, Dict[str, Any]] = {}\n    # accumulator lists\n    x_mm, y_mm, rows_list, cols_list, x_rel, y_rel = ([] for _ in range(6))\n\n    for _, row in out.iterrows():\n        uid = str(row[sop_col]).upper()\n        px, py = int(row[x_pix]), int(row[y_pix])\n        if uid not in cache:\n            path = dicom_index.get(uid)\n            ds = pydicom.dcmread(str(path), stop_before_pixels=True) if path and path.is_file() else pydicom.Dataset()\n            cache[uid] = get_image_geometry(ds)\n        geom = cache[uid]\n        origin = geom['origin_xyz']\n        rs, cs = geom['row_spacing_mm'], geom['col_spacing_mm']\n        row_cos, col_cos = geom['row_cosine'], geom['col_cosine']\n        rows, cols = geom['rows'], geom['columns']\n        # world coords\n        world = origin + px*cs*col_cos + py*rs*row_cos\n        x_mm.append(world[0]); y_mm.append(world[1])\n        # relative coords\n        rows_list.append(rows); cols_list.append(cols)\n        x_rel.append(px/cols); y_rel.append(py/rows)\n\n    out['x_mm'], out['y_mm'] = x_mm, y_mm\n    out['rows'], out['columns'] = rows_list, cols_list\n    out['x_rel'], out['y_rel'] = x_rel, y_rel\n    \n    return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.190377Z","iopub.execute_input":"2025-08-02T17:12:28.190711Z","iopub.status.idle":"2025-08-02T17:12:28.20935Z","shell.execute_reply.started":"2025-08-02T17:12:28.190679Z","shell.execute_reply":"2025-08-02T17:12:28.208325Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Lets plot some things to see what we find.","metadata":{}},{"cell_type":"code","source":"# --- Plotting ---\ndef plot_global(\n    df: pd.DataFrame,\n    loc_col: str = 'location'\n) -> None:\n    \"\"\"Global scatter of relative coords by location.\"\"\"\n    fig, ax = plt.subplots(figsize=(12,6))\n    for loc, grp in df.groupby(loc_col):\n        ax.scatter(grp['x_rel'], grp['y_rel'], alpha=0.6, label=loc)\n    ax.set(xlabel='x_rel', ylabel='y_rel', title='Global Relative Scatter')\n    ax.legend(bbox_to_anchor=(1.05,1), loc='upper left')\n    plt.tight_layout(); plt.show()\n\n\ndef plot_per_location(\n    df: pd.DataFrame,\n    location: str\n) -> None:\n    \"\"\"Per-location scatter and boxplots on relative coords.\"\"\"\n    sub = df[df['location']==location]\n    fig, (ax1, ax2) = plt.subplots(1,2,figsize=(10,4))\n    ax1.scatter(sub['x_rel'], sub['y_rel'], alpha=0.6)\n    ax1.set(title=f\"{location} Relative Scatter\", xlabel='x_rel', ylabel='y_rel')\n    ax2.boxplot([sub['x_rel'], sub['y_rel']], labels=['x_rel','y_rel'])\n    ax2.set(title=f\"{location} Relative Boxplots\")\n    plt.tight_layout(); plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.210333Z","iopub.execute_input":"2025-08-02T17:12:28.211273Z","iopub.status.idle":"2025-08-02T17:12:28.23438Z","shell.execute_reply.started":"2025-08-02T17:12:28.211236Z","shell.execute_reply":"2025-08-02T17:12:28.233406Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Need a faster way to get all the files, there are over 1 million of them.","metadata":{}},{"cell_type":"code","source":"# --- #Utility: Improved Indexing ---\ndef index_dicoms(root_dir: str) -> dict[str, str]:\n    dicom_index = {\n        f[:-4].upper(): Path(root, f)\n        for root, _, files in os.walk(\"/kaggle/input/rsna-intracranial-aneurysm-detection/\")\n        for f in files\n        if f.lower().endswith(\".dcm\")\n    }\n    return dicom_index","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.235323Z","iopub.execute_input":"2025-08-02T17:12:28.235571Z","iopub.status.idle":"2025-08-02T17:12:28.25792Z","shell.execute_reply.started":"2025-08-02T17:12:28.23555Z","shell.execute_reply":"2025-08-02T17:12:28.256954Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Put it all together.","metadata":{}},{"cell_type":"code","source":"# --- Main ---\ndef main():\n    print('reading csv file')\n    df = pd.read_csv(Path('/kaggle/input/rsna-intracranial-aneurysm-detection/train_localizers.csv'))\n\n    print('extracting given coordinates')\n    df = extract_coordinates(df)\n\n    print('building dicom index')\n    #dicom_index = {p.stem.upper(): p for p in Path('/kaggle/input/rsna-intracranial-aneurysm-detection/').rglob('*.dcm')}\n    #dicom_index = {p.stem.upper(): p for p in tqdm(Path(\"/kaggle/input/rsna-intracranial-aneurysm-detection/\").rglob(\"*.dcm\"), desc=\"Indexing DICOM\", unit=\"file\")} #2min to run\n    dicom_index = index_dicoms(\"/kaggle/input/rsna-intracranial-aneurysm-detection/\")\n    print('dicom index built')\n\n    print('computing physical and relative values')\n    df = compute_physical_and_relative(\n        df, sop_col='SOPInstanceUID', x_pix='x_pix', y_pix='y_pix', dicom_index=dicom_index\n    )\n    print('finished computing physical and relative values')\n    \n    plot_global(df)\n    \n    for loc in df['location'].unique():\n        plot_per_location(df, loc)\n\nif __name__=='__main__':\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T17:12:28.259589Z","iopub.execute_input":"2025-08-02T17:12:28.259878Z","iopub.status.idle":"2025-08-02T17:14:30.23527Z","shell.execute_reply.started":"2025-08-02T17:12:28.259856Z","shell.execute_reply":"2025-08-02T17:14:30.234372Z"}},"outputs":[],"execution_count":null}]}