{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":12638707,"sourceType":"datasetVersion","datasetId":2725849},{"sourceId":255036297,"sourceType":"kernelVersion"},{"sourceId":507313,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":402614,"modelId":420561}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Strip Skull from Images\n\nSkull can get in the way, so the [SynthStrip](https://synthstrip.io) tool will remove it. Using window and level to remove skull is problematic and this tool is much more precise.\n\n- save volumes as nifti using dcm2niix as per @evancalabrese\n- use gdcmconv on failures and retry dcm2niix Thank you to @antonsibilev\n- run SynthStrip to get mask\n- (@antonsibilev recommends dilate and pad. The sample MRI image needed(15,15) dilation to fill in carotid)\n- apply mask\n\nAdd notebook [SynthStripHelper](https://www.kaggle.com/code/anoukstein/synthstriphelper?scriptVersionId=255009494)\nand model [SynthStripModel](https://www.kaggle.com/models/anoukstein/synthstrip/pyTorch/default/1)\nand dataset [read-dicom-set](https://www.kaggle.com/datasets/stpeteishii/read-dicom-set) by @stpete_ishii\n\nCitation:\nSynthStrip: Skull-Stripping for Any Brain Image\nA Hoopes, JS Mora, AV Dalca, B Fischl, M Hoffmann\nNeuroImage 206 (2022), 119474\nhttps://doi.org/10.1016/j.neuroimage.2022.119474\n\nWebsite: https://synthstrip.io\n\nPotential problems highlighted by @antonsibilev: \nI used -i y on dcm2niix\n\nDERIVED / MPR vs “ignore derived”\n-i y can throw away everything; -i n lets MPRs through.\nFix: run with -i n, then filter post-hoc: keep isotropic, long volumes (e.g., Nz≥100, dz≤1.2 mm) even if DERIVED; send true slabs to a 2D/2.5D branch.","metadata":{}},{"cell_type":"markdown","source":"## Grab one filename for each Modality to test","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport os\n\n#Train csv to dataframe\ndf = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\n\n#List all Modalities\nmodalities = list(df.Modality.unique())\n\n#Root folder for data\nDATA_ROOT = \"/kaggle/input/rsna-intracranial-aneurysm-detection/series/\"\n\n#Dictionary\nfns = {}\n\n#Get one foldername for each type\nfor modality in modalities:\n    series_id = df[df.Modality==modality].iloc[0].SeriesInstanceUID\n    fns[modality] = os.path.join(DATA_ROOT,series_id)\nfns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:04.487214Z","iopub.execute_input":"2025-08-10T21:30:04.487549Z","iopub.status.idle":"2025-08-10T21:30:04.518638Z","shell.execute_reply.started":"2025-08-10T21:30:04.487512Z","shell.execute_reply":"2025-08-10T21:30:04.517718Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Libraries \n\nThank you to @stpete_ishii (read-dicom-set)","metadata":{}},{"cell_type":"code","source":"!pip install python-gdcm --no-index --find-links=file:///kaggle/input/read-dicom-set -q\n!pip install dicomsdl --no-index --find-links=file:///kaggle/input/read-dicom-set -q\n!pip install /kaggle/input/synthstriphelper/freesurfer/offline_packages/surfa-0.6.1-cp311-cp311-linux_x86_64.whl --no-index -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:05.39451Z","iopub.execute_input":"2025-08-10T21:30:05.394792Z","iopub.status.idle":"2025-08-10T21:30:16.913973Z","shell.execute_reply.started":"2025-08-10T21:30:05.394772Z","shell.execute_reply":"2025-08-10T21:30:16.912689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python -m pip install dcm2niix -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:16.916286Z","iopub.execute_input":"2025-08-10T21:30:16.916585Z","iopub.status.idle":"2025-08-10T21:30:20.769162Z","shell.execute_reply.started":"2025-08-10T21:30:16.916555Z","shell.execute_reply":"2025-08-10T21:30:20.767945Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load all dicom images in folder and save as Nifti","metadata":{}},{"cell_type":"code","source":"import subprocess\nfrom pathlib import Path\nimport shutil\n\ndef run_cmd(cmd):\n    \"\"\"Run a shell command and return (success, output).\"\"\"\n    try:\n        output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)\n        return True, output.decode()\n    except subprocess.CalledProcessError as e:\n        return False, e.output.decode()\n\ndef convert_dicom_folder(folder_path, nifti_outdir, tmp_dir, verbose=True):\n    folder = Path(folder_path)\n    series_id = folder.name  # get last part of the folder path\n\n    if verbose:\n        print(f\"Trying dcm2niix on {folder}...\")\n    # Run dcm2niix on the original folder\n    dcm2niix_cmd = [\n        'dcm2niix',\n        '-5', '-b', 'y', '-i', 'y', '-z', 'y',\n        '-f', '%f',            # filename prefix based on folder name\n        '-o', str(nifti_outdir),\n        str(folder)\n    ]\n    success, output = run_cmd(dcm2niix_cmd)\n\n    if success:\n        if verbose:\n            print(f\"dcm2niix succeeded on {folder}\")\n        return True\n    else:\n        if verbose:\n            print(f\"dcm2niix failed on {folder}, error:\")\n            print(output)\n            print(\"Trying gdcmconv to decompress and rerun dcm2niix...\")\n\n        # Use gdcmconv --raw to decompress each DICOM in folder to tmp_folder\n        tmp_folder = Path(tmp_dir) / folder.name\n        tmp_folder.mkdir(parents=True, exist_ok=True)\n        \n        for dicom_file in folder.glob('*.dcm'):\n            out_file = tmp_folder / dicom_file.name\n            gdcmconv_cmd = ['gdcmconv', '--raw', str(dicom_file), str(out_file)]\n            success_gdcm, output_gdcm = run_cmd(gdcmconv_cmd)\n            if not success_gdcm:\n                print(f\"gdcmconv failed on {dicom_file}, error:\")\n                print(output_gdcm)\n                return False\n\n        # Run dcm2niix again on decompressed tmp folder\n        dcm2niix_cmd_tmp = [\n            'dcm2niix',\n            '-5', '-b', 'y', '-i', 'y', '-z', 'y',\n            '-f', '%f',        \n            '-o', str(nifti_outdir),\n            str(tmp_folder)\n        ]\n        success_tmp, output_tmp = run_cmd(dcm2niix_cmd_tmp)\n        #cleanup\n        # if tmp_folder.exists():\n        #     shutil.rmtree(tmp_folder)\n            \n        if success_tmp:\n            if verbose:\n                print(f\"dcm2niix succeeded on decompressed files for {folder}\")\n            return True\n        else:\n            if verbose:\n                print(f\"dcm2niix failed on decompressed files for {folder}, error:\")\n                print(output_tmp)\n            return False\n\ndef batch_convert_folders(folder_list, nifti_outdir, tmp_dir='/kaggle/temp/converted_dicoms'):\n    nifti_outdir = Path(nifti_outdir)\n    nifti_outdir.mkdir(parents=True, exist_ok=True)\n    Path(tmp_dir).mkdir(parents=True, exist_ok=True)\n\n    for folder in folder_list:\n        success = convert_dicom_folder(folder, nifti_outdir, tmp_dir)\n        if not success:\n            print(f\"Conversion failed for folder: {folder}\")\n        print(\"=\"*60)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:20.770565Z","iopub.execute_input":"2025-08-10T21:30:20.770962Z","iopub.status.idle":"2025-08-10T21:30:20.784785Z","shell.execute_reply.started":"2025-08-10T21:30:20.770918Z","shell.execute_reply":"2025-08-10T21:30:20.78378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/temp/converted_dicoms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:20.787109Z","iopub.execute_input":"2025-08-10T21:30:20.787408Z","iopub.status.idle":"2025-08-10T21:30:20.936371Z","shell.execute_reply.started":"2025-08-10T21:30:20.787386Z","shell.execute_reply":"2025-08-10T21:30:20.935216Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Run SynthStrip on nifti","metadata":{}},{"cell_type":"code","source":"import subprocess\nimport os\n\nMODEL_PATH = \"/kaggle/input/synthstrip/pytorch/default/1/synthstrip.1.pt\"\nROOT = \"/kaggle/input/synthstriphelper/freesurfer\"\n\ndef run_synthstrip(input_path, output_path, model_path=MODEL_PATH, root=ROOT, verbose=True):\n    \"\"\"\n    Run the mri_synthstrip command with given paths.\n    \n    Args:\n        input_path (str): Path to the input file.\n        output_path (str): Path to the output file.\n        model_path (str): Path to the model file.\n        root (str): Path for FREESURFER_HOME environment variable.\n    \"\"\"\n    env = os.environ.copy()\n    env[\"FREESURFER_HOME\"] = root\n\n    cmd = [\n        \"python\",\n        os.path.join(root, \"mri_synthstrip/mri_synthstrip\"),\n        \"-i\", input_path,\n        \"-o\", output_path,\n        \"--model\", model_path\n    ]\n\n    result = subprocess.run(cmd, env=env, capture_output=True, text=True)\n\n    if verbose:\n        print(\"STDOUT:\\n\", result.stdout)\n        print(\"STDERR:\\n\", result.stderr)\n    if result.returncode != 0:\n        raise RuntimeError(f\"Command failed with exit code {result.returncode}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:20.93764Z","iopub.execute_input":"2025-08-10T21:30:20.938009Z","iopub.status.idle":"2025-08-10T21:30:20.946134Z","shell.execute_reply.started":"2025-08-10T21:30:20.937971Z","shell.execute_reply":"2025-08-10T21:30:20.94514Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Display","metadata":{}},{"cell_type":"code","source":"import nibabel as nib\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom scipy.ndimage import binary_dilation\n\ndef apply_window(img, center, width):\n    \"\"\"Apply window level to image for display purposes.\"\"\"\n    lower = center - width / 2\n    upper = center + width / 2\n    img = np.clip(img, lower, upper)\n    img = (img - lower) / (upper - lower)  # normalize to [0,1]\n    return img\n\ndef show_stripped(orig_path, mask_path, window_center=96, window_width=150, dilation=3):\n\n    # Load the original and stripped images\n    orig = nib.load(orig_path).get_fdata()\n    stripped = nib.load(mask_path).get_fdata()\n    \n    # Pick a slice (adjust based on orientation)\n    slice_idx = orig.shape[2] // 2  # Middle axial slice\n    \n    orig_slice = apply_window(orig[:, :, slice_idx], window_center, window_width)\n    mask_slice = stripped[:, :, slice_idx] > 0\n\n    if dilation > 0:\n        #dilate mask\n        dilator = np.ones((dilation, dilation))\n        mask_slice = binary_dilation(mask_slice,dilator)\n    \n    # Prepare figure\n    plt.figure(figsize=(12, 5))\n    \n    # Show original\n    plt.subplot(1, 3, 1)\n    plt.imshow(orig_slice, cmap='gray')\n    plt.title('Original')\n    plt.axis('off')\n    \n    # Show masked result (brain only)\n    plt.subplot(1, 3, 2)\n    # Mask the original image to show only brain\n    brain_only = np.where(mask_slice, orig_slice, 0)\n\n    plt.imshow(brain_only, cmap='gray')\n    plt.title('Stripped (brain only)')\n    plt.axis('off')\n    \n    # Show overlay (brain as transparent over original)\n    plt.subplot(1, 3, 3)\n    plt.imshow(orig_slice, cmap='gray')\n    # Overlay: show where stripped image > 0\n    plt.imshow(mask_slice, cmap='autumn', alpha=0.3)  # semi-transparent red\n    plt.title('Overlay')\n    plt.axis('off')\n    \n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:42:46.757407Z","iopub.execute_input":"2025-08-10T21:42:46.757993Z","iopub.status.idle":"2025-08-10T21:42:46.770399Z","shell.execute_reply.started":"2025-08-10T21:42:46.757961Z","shell.execute_reply":"2025-08-10T21:42:46.769554Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Process folder","metadata":{}},{"cell_type":"code","source":"def process_folder(folder):\n    nifti_folder = '/kaggle/working/nifti'\n    #convert to nifti\n    batch_convert_folders([folder], nifti_folder)\n\n    series_id = folder.split('/')[-1]\n    nifti_path = os.path.join(nifti_folder,f'{series_id}.nii.gz')\n    mask_path = os.path.join(nifti_folder,f'mask_{series_id}.nii.gz')\n    \n    #run synthstrip\n    print(\"Running...\")\n    run_synthstrip(nifti_path, mask_path)\n    return nifti_path, mask_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:20.970971Z","iopub.execute_input":"2025-08-10T21:30:20.971241Z","iopub.status.idle":"2025-08-10T21:30:20.992094Z","shell.execute_reply.started":"2025-08-10T21:30:20.971218Z","shell.execute_reply":"2025-08-10T21:30:20.991077Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Examples","metadata":{}},{"cell_type":"markdown","source":"## MRA","metadata":{}},{"cell_type":"code","source":"i = 0\nfolder = fns[modalities[i]]\n\nnifti_path_1, mask_path_1 = process_folder(folder)\n\nprint(\"Show...\")\n#Original mask\nshow_stripped(nifti_path_1, mask_path_1, dilation=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:30:20.993558Z","iopub.execute_input":"2025-08-10T21:30:20.993832Z","iopub.status.idle":"2025-08-10T21:31:41.605685Z","shell.execute_reply.started":"2025-08-10T21:30:20.993803Z","shell.execute_reply":"2025-08-10T21:31:41.604119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#mask after dilation to fill in vessel and then pad \nshow_stripped(nifti_path_1, mask_path_1, dilation=15)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T21:06:05.719904Z","iopub.execute_input":"2025-08-10T21:06:05.721653Z","iopub.status.idle":"2025-08-10T21:06:09.788245Z","shell.execute_reply.started":"2025-08-10T21:06:05.721621Z","shell.execute_reply":"2025-08-10T21:06:09.787172Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## CTA","metadata":{}},{"cell_type":"code","source":"i = 1\nfolder = fns[modalities[i]]\n\nnifti_path_2, mask_path_2 = process_folder(folder)\n\nprint(\"Show...\")\nshow_stripped(nifti_path_2, mask_path_2, dilation=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T18:34:31.510446Z","iopub.status.idle":"2025-08-10T18:34:31.510792Z","shell.execute_reply.started":"2025-08-10T18:34:31.510614Z","shell.execute_reply":"2025-08-10T18:34:31.510627Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MRI T2","metadata":{}},{"cell_type":"code","source":"i = 2\nfolder = fns[modalities[i]]\n\nnifti_path_3, mask_path_3 = process_folder(folder)\n\nprint(\"Show...\")\nshow_stripped(nifti_path_3, mask_path_3, window_center=300, window_width=1200, dilation=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T18:34:31.5125Z","iopub.status.idle":"2025-08-10T18:34:31.512887Z","shell.execute_reply.started":"2025-08-10T18:34:31.512684Z","shell.execute_reply":"2025-08-10T18:34:31.512704Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MRI T1post","metadata":{}},{"cell_type":"code","source":"i = 3\nfolder = fns[modalities[i]]\n\nnifti_path_4, mask_path_4 = process_folder(folder)\n\nprint(\"Show...\")\nshow_stripped(nifti_path_4, mask_path_4, window_center=300, window_width=1200, dilation=3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T18:34:31.515381Z","iopub.status.idle":"2025-08-10T18:34:31.515801Z","shell.execute_reply.started":"2025-08-10T18:34:31.515574Z","shell.execute_reply":"2025-08-10T18:34:31.515591Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Look at one json created by dcm2niix","metadata":{}},{"cell_type":"code","source":"import json\n\njsonfn = '/kaggle/working/nifti/1.2.826.0.1.3680043.8.498.10030804647049037739144303822498146901.json'\nwith open(jsonfn, 'r') as file:\n    data = json.load(file)\n\ndata","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T18:34:31.513678Z","iopub.status.idle":"2025-08-10T18:34:31.513975Z","shell.execute_reply.started":"2025-08-10T18:34:31.513843Z","shell.execute_reply":"2025-08-10T18:34:31.513856Z"}},"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}]}