{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport pydicom\nimport joblib","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-08-12T09:15:45.936233Z","iopub.execute_input":"2023-08-12T09:15:45.936663Z","iopub.status.idle":"2023-08-12T09:15:46.246263Z","shell.execute_reply.started":"2023-08-12T09:15:45.93663Z","shell.execute_reply":"2023-08-12T09:15:46.244435Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import nibabel as nib\nimport ipywidgets as widgets","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:17:00.770088Z","iopub.execute_input":"2023-08-12T09:17:00.770541Z","iopub.status.idle":"2023-08-12T09:17:01.180081Z","shell.execute_reply.started":"2023-08-12T09:17:00.770505Z","shell.execute_reply":"2023-08-12T09:17:01.179151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dicom_tag_columns = [\n    'Columns',\n    'ImageOrientationPatient',\n    'ImagePositionPatient',\n    'InstanceNumber',\n    'PatientID',\n    'PatientPosition',\n    'PixelSpacing',\n    'RescaleIntercept',\n    'RescaleSlope',\n    'Rows',\n    'SeriesNumber',\n    'SliceThickness',\n    'path',\n    'WindowCenter',\n    'WindowWidth'\n]\nROOT_DIR = '/kaggle/input/rsna-2023-abdominal-trauma-detection/'\nVECTOR_DTYPE = np.float32\ndef process_dicom_tags(df, copy=True):\n    df_out = df.copy() if copy else df\n    df_out['SeriesID'] = df_out.path.str.split('/').str.get(2).astype(int)\n    # Cast strings as arrays.\n    for c in ['ImageOrientationPatient', 'ImagePositionPatient', 'PixelSpacing']:\n        df_out[c] = df_out[c].str.strip('[]').str.encode('utf-8').apply(lambda x: np.fromstring(x, sep=',', dtype=VECTOR_DTYPE))\n    # Make `path` absolute path.\n    df_out['path'] = df_out.path.apply(lambda x: os.path.join(ROOT_DIR, x))\n    df_out.sort_values(['SeriesID', 'InstanceNumber'], inplace=True)\n    return df_out","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:22:31.77216Z","iopub.execute_input":"2023-08-12T09:22:31.772567Z","iopub.status.idle":"2023-08-12T09:22:31.782799Z","shell.execute_reply.started":"2023-08-12T09:22:31.772534Z","shell.execute_reply":"2023-08-12T09:22:31.781369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_meta = pd.read_csv(\"/kaggle/input/rsna-2023-abdominal-trauma-detection/train_series_meta.csv\")\ntest_meta = pd.read_csv(\"/kaggle/input/rsna-2023-abdominal-trauma-detection/test_series_meta.csv\")","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:25:26.564289Z","iopub.execute_input":"2023-08-12T09:25:26.564684Z","iopub.status.idle":"2023-08-12T09:25:26.600213Z","shell.execute_reply.started":"2023-08-12T09:25:26.564652Z","shell.execute_reply":"2023-08-12T09:25:26.599015Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dicom_tags = pd.read_parquet(\"/kaggle/input/rsna-2023-abdominal-trauma-detection/train_dicom_tags.parquet\", columns=dicom_tag_columns)\ntest_dicom_tags = pd.read_parquet(\"/kaggle/input/rsna-2023-abdominal-trauma-detection/test_dicom_tags.parquet\", columns=dicom_tag_columns)\ntrain_dicom_tags = process_dicom_tags(train_dicom_tags, copy=False)\ntest_dicom_tags = process_dicom_tags(test_dicom_tags, copy=False)","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:31:21.872576Z","iopub.execute_input":"2023-08-12T09:31:21.872985Z","iopub.status.idle":"2023-08-12T09:31:58.0926Z","shell.execute_reply.started":"2023-08-12T09:31:21.872951Z","shell.execute_reply":"2023-08-12T09:31:58.091452Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define widgets.\noutput = widgets.Output()\n\ndataset_widget = widgets.Dropdown(\n    options=['Train', 'Test'],\n    value=None,\n    description='Dataset',\n    disabled=False\n)\nfilter_series_id_without_segmentation_enable_widget = widgets.Checkbox(\n    value=False,\n    description='Filter series ID without segmentations',\n    disabled=False,\n    indent=False\n)\nseries_id_widget = widgets.Dropdown(\n    options=[''],\n    description='Series ID',\n    disabled=False\n)\nscan_id_widget = widgets.IntSlider(\n    value=0,\n    min=0,\n    max=1,\n    step=1,\n    description='Scan ID',\n    disabled=False,\n    continuous_update=False,\n    orientation='horizontal',\n    readout=True,\n    readout_format='d'\n)\nsegmentation_enable_widget = widgets.Checkbox(\n    value=False,\n    description='Apply Segmentation',\n    disabled=True,\n    indent=False\n)\nwindow_range_widget = widgets.FloatRangeSlider(\n    value=[-1024.0, 1024.0],\n    min=-1024.0,\n    max=1024.0,\n    step=0.1,\n    description='Window Range (HU):',\n    disabled=False,\n    continuous_update=False,\n    orientation='horizontal',\n    readout=True,\n    readout_format='.1f',\n)","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:33:13.048709Z","iopub.execute_input":"2023-08-12T09:33:13.049108Z","iopub.status.idle":"2023-08-12T09:33:13.086295Z","shell.execute_reply.started":"2023-08-12T09:33:13.049073Z","shell.execute_reply":"2023-08-12T09:33:13.084831Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define TotalSegmentation codes.\ntotal_segmentator_codes = {\n    1: 'liver',\n    2: 'spleen',\n    3: 'kidney_left',\n    4: 'kidney_right',\n    5: 'bowel',\n}","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:33:34.767679Z","iopub.execute_input":"2023-08-12T09:33:34.768071Z","iopub.status.idle":"2023-08-12T09:33:34.774284Z","shell.execute_reply.started":"2023-08-12T09:33:34.768039Z","shell.execute_reply":"2023-08-12T09:33:34.772703Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def calibrate_img(img, m, c):\n    # Transform into HU.\n    img *= m\n    img += c\n    #img[img < hu_min] = hu_min # Clip pixels outside the FOV. \n    return img\n\n\ndef get_patient_id(series_id, use_train_set):\n    df = train_meta if use_train_set else test_meta\n    return int(df[df.series_id == series_id].iloc[0].patient_id)\n\n\ndef get_scan_files(patient_id, series_id, use_train_set):\n    dataset_dir = 'train_images' if use_train_set else 'test_images'\n    fnames = glob.glob(f'/kaggle/input/rsna-2023-abdominal-trauma-detection/{dataset_dir}/{patient_id}/{series_id}/*.dcm')\n    return list(sorted(fnames, key=lambda f: int(os.path.splitext(os.path.basename(f))[0])))\n\ndef load_series(series_id, use_train_set):\n    df = train_dicom_tags if use_train_set else test_dicom_tags\n    sub_df = df[df.SeriesID == series_id]\n\n    @joblib.delayed\n    def load_dcm(path):\n        dcm = pydicom.dcmread(path)\n        return dcm.pixel_array.astype(np.float32)\n\n    img = joblib.Parallel(n_jobs=-1)(load_dcm(path) for path in sub_df.path.values)\n    img = np.dstack(img)\n    \n    # Make sure images are ordered correctly (superior is +z).\n    m, c, iop = sub_df.iloc[0][['RescaleSlope', 'RescaleIntercept', 'ImageOrientationPatient']]\n    img = calibrate_img(img, m, c)\n    imaging_axis = np.cross(iop[:3], iop[3:])\n    distance_projection = np.dot(np.vstack(sub_df.ImagePositionPatient.values), imaging_axis)\n    img = img[:, :, np.argsort(distance_projection)]\n    img = img.transpose((1, 0, 2))\n    return img\ndef get_segmentation_paths():\n    paths = glob.glob(f'/kaggle/input/rsna-2023-abdominal-trauma-detection/segmentations/*.nii')\n    return list(sorted(paths, key=lambda f: int(os.path.splitext(os.path.basename(f))[0])))\n\n\ndef get_series_ids_with_segmentations():\n    return [int(os.path.splitext(os.path.basename(f))[0]) for f in get_segmentation_paths()]\n    \n\ndef get_segmentation_path(series_id):\n    return f'/kaggle/input/rsna-2023-abdominal-trauma-detection/segmentations/{series_id}.nii'\n\n\ndef load_segmentation(x):\n    if not isinstance(x, str):\n        x = get_segmentation_path(int(x))\n    out = nib.load(x)\n    return nib.as_closest_canonical(out) # Ensures output is RAS, DICOM is LPS.\n\n\ndef get_segmentation_image(seg):\n    if not isinstance(seg, nib.nifti1.Nifti1Image):\n        seg = load_segmentation(seg)\n    return seg.get_fdata().astype(int)[::-1, ::-1, :]","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:34:33.43373Z","iopub.execute_input":"2023-08-12T09:34:33.434138Z","iopub.status.idle":"2023-08-12T09:34:33.452907Z","shell.execute_reply.started":"2023-08-12T09:34:33.434092Z","shell.execute_reply":"2023-08-12T09:34:33.451597Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from matplotlib.patches import Patch\nfrom matplotlib.lines import Line2D\n\n\nCURRENT_SCAN_DATA = None\nCURRENT_SEGMENTATOR_DATA = None\nIM = None\nSEG_IM = None\nfig, ax = plt.subplots()\nplt.close(fig)\n\nseries_ids_with_segmentations = get_series_ids_with_segmentations()\n\nsegmentator_cmap = plt.get_cmap('rainbow')\nsegmentator_palette = np.array([[np.nan] * 4] + [segmentator_cmap(x) for x in np.linspace(0, 1, 6)])\ndef handle_dataset_update(change):\n    df = train_meta if change['new'] == 'Train' else test_meta\n    series_id_widget.options = sorted(df.series_id.values)\n    series_id_widget.value = None\n\n    \n#@output.capture()\ndef handle_filter_series_id(change):\n    df = train_meta if dataset_widget.value == 'Train' else test_meta\n    options = df.series_id.values\n    if change['new']:\n        options = [series_id for series_id in options if series_id in series_ids_with_segmentations]\n    series_id_widget.options = list(sorted(options))\n#@output.capture()\ndef handle_series_id_update(change):\n    global CURRENT_SCAN_DATA, CURRENT_SEGMENTATOR_DATA, SEGMENTATOR_DATA_PATH\n\n    # Load scans and update bounds on scan ID widget.\n    series_id = change['new']\n    if not series_id:\n        return\n    CURRENT_SCAN_DATA = load_series(series_id, dataset_widget.value == 'Train')\n    \n    scan_id_widget.value = 0\n    scan_id_widget.max = CURRENT_SCAN_DATA.shape[-1] - 1\n    \n    # Load segmentator data.\n    if series_id in series_ids_with_segmentations:\n        CURRENT_SEGMENTATOR_DATA = get_segmentation_image(series_id)\n        segmentation_enable_widget.disabled = False\n        segmentation_enable_widget.description = 'Show Segmentations'\n    else:\n        segmentation_enable_widget.disabled = True\n        segmentation_enable_widget.description = 'Segmentations not available.'\n#@output.capture()\ndef update_scan(dataset, filter_series_id_without_segmentation, series_id, scan_id, show_segmentations, window_range):\n    global CURRENT_SCAN_DATA, CURRENT_SEGMENTATOR_DATA, IM, SEG_IM\n    if series_id is None or scan_id is None or CURRENT_SCAN_DATA is None:\n        return\n    data = CURRENT_SCAN_DATA[:, :, scan_id].T\n    data = np.clip(data, a_min=window_range[0], a_max=window_range[1])\n\n    if IM is None:\n        IM = ax.imshow(data, cmap=plt.cm.bone)\n    else:\n        IM.set_data(data)\n        IM.set_clim(data.min(), data.max())\n\n    if show_segmentations:\n        segmentator_slice = CURRENT_SEGMENTATOR_DATA[:, :, scan_id].T\n        if SEG_IM is None:\n            SEG_IM = ax.imshow(segmentator_palette[segmentator_slice], alpha=0.5)\n        else:\n            SEG_IM.set_data(segmentator_palette[segmentator_slice])\n        mask_levels = [x for x in np.unique(segmentator_slice) if x != 0]\n        legend_elements = [\n            Patch(facecolor=segmentator_palette[x], edgecolor=segmentator_palette[x], label=total_segmentator_codes[x]) for x in mask_levels\n        ]\n        if ax.get_legend() is not None:\n            ax.get_legend().remove()\n        ax.legend(handles=legend_elements, loc='upper right')\n    display(fig)\n\n\ndataset_widget.observe(handle_dataset_update, names='value')\nfilter_series_id_without_segmentation_enable_widget.observe(handle_filter_series_id, names='value')\nseries_id_widget.observe(handle_series_id_update, names='value')\n\nw = widgets.interactive(\n    update_scan,\n    dataset=dataset_widget,\n    filter_series_id_without_segmentation=filter_series_id_without_segmentation_enable_widget,\n    series_id=series_id_widget,\n    scan_id=scan_id_widget,\n    show_segmentations=segmentation_enable_widget,\n    window_range=window_range_widget,\n)\nw.children[-1].layout.height = '450px'\ndisplay(w)","metadata":{"execution":{"iopub.status.busy":"2023-08-12T09:40:01.137162Z","iopub.execute_input":"2023-08-12T09:40:01.137813Z","iopub.status.idle":"2023-08-12T09:40:01.269521Z","shell.execute_reply.started":"2023-08-12T09:40:01.13776Z","shell.execute_reply":"2023-08-12T09:40:01.26824Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}