{"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":"markdown","source":"(V9: Fixed data-leakage in train-test split - now using GroupKFold)","metadata":{}},{"cell_type":"markdown","source":"**Idea:**","metadata":{}},{"cell_type":"markdown","source":"* Extract vertebrae C1 to C7 through **unique values** in **segmentation masks**. \n* Not all images have segmentation masks though.\n* So train a **Random Forest classifier** to predict the vertebrae for all the other images without segmentation masks via the metadata (Slice, PatientPosition etc).\n* This baseline achieves **88% average accuracy**.","metadata":{}},{"cell_type":"markdown","source":"**Links**\n\n1. [Exploratory Data Analysis (EDA) notebook](https://www.kaggle.com/code/samuelcortinhas/rsna-fracture-detection-in-depth-eda)\n2. [Dataset containing extracted metadata](https://www.kaggle.com/datasets/samuelcortinhas/rsna-2022-spine-fracture-detection-metadata)","metadata":{}},{"cell_type":"markdown","source":"**Libraries**","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport matplotlib.patches as patches\nimport seaborn as sns\nsns.set(style='darkgrid', font_scale=1.6)\nimport cv2\nimport os\nfrom os import listdir\nimport re\nimport gc\nimport sys\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nfrom tqdm import tqdm\nfrom pprint import pprint\nfrom time import time\nimport itertools\nfrom skimage import measure \nfrom mpl_toolkits.mplot3d.art3d import Poly3DCollection\nimport nibabel as nib\nfrom glob import glob\n\nfrom sklearn.model_selection import train_test_split, StratifiedGroupKFold, GroupKFold\nfrom sklearn.metrics import confusion_matrix, accuracy_score\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras import callbacks\n\n# Models\nfrom sklearn.linear_model import LinearRegression, LogisticRegression\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.svm import SVC\nfrom sklearn.tree import DecisionTreeClassifier\nfrom sklearn.ensemble import RandomForestClassifier\nfrom xgboost import XGBClassifier\nfrom lightgbm import LGBMClassifier\nfrom catboost import CatBoostClassifier\nfrom sklearn.naive_bayes import GaussianNB","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:06.150143Z","iopub.execute_input":"2022-08-25T09:08:06.150581Z","iopub.status.idle":"2022-08-25T09:08:06.167689Z","shell.execute_reply.started":"2022-08-25T09:08:06.150548Z","shell.execute_reply":"2022-08-25T09:08:06.166564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Load data**","metadata":{}},{"cell_type":"code","source":"# Load dataframes\ntrain_df = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/train.csv\")\ntrain_bbox = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/train_bounding_boxes.csv\")\ntest_df = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/test.csv\")\nss = pd.read_csv(\"../input/rsna-2022-cervical-spine-fracture-detection/sample_submission.csv\")\n\n# Print dataframe shapes\nprint('train shape:', train_df.shape)\nprint('train bbox shape:', train_bbox.shape)\nprint('test shape:', test_df.shape)\nprint('ss shape:', ss.shape)\nprint('')\n\n# Show first few entries\ntrain_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:06.169472Z","iopub.execute_input":"2022-08-25T09:08:06.170418Z","iopub.status.idle":"2022-08-25T09:08:06.219874Z","shell.execute_reply.started":"2022-08-25T09:08:06.170382Z","shell.execute_reply":"2022-08-25T09:08:06.21867Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Create segmentation df**","metadata":{}},{"cell_type":"code","source":"# Store segmentation paths in a dataframe\nbase_path = \"../input/rsna-2022-cervical-spine-fracture-detection\"\nseg_paths = glob(f\"{base_path}/segmentations/*\")\nseg_df = pd.DataFrame({'path': seg_paths})\nseg_df['StudyInstanceUID'] = seg_df['path'].apply(lambda x:x.split('/')[-1][:-4])\nseg_df = seg_df[['StudyInstanceUID','path']]\nprint('seg_df shape:', seg_df.shape)\nseg_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:06.22258Z","iopub.execute_input":"2022-08-25T09:08:06.223075Z","iopub.status.idle":"2022-08-25T09:08:06.243005Z","shell.execute_reply.started":"2022-08-25T09:08:06.223031Z","shell.execute_reply":"2022-08-25T09:08:06.241383Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Get metadata for segmentations**","metadata":{}},{"cell_type":"code","source":"# Metadata was extracted previously (check out my RSNA dataset)\nmeta_train = pd.read_csv(\"../input/rsna-2022-spine-fracture-detection-metadata/meta_train_clean.csv\")\n\n# Only select patients with segmentations\nmeta_seg = meta_train[meta_train['StudyInstanceUID'].isin(seg_df['StudyInstanceUID'])].reset_index(drop=True)\nprint('meta_seg shape:', meta_seg.shape)\nmeta_seg.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:06.244689Z","iopub.execute_input":"2022-08-25T09:08:06.245088Z","iopub.status.idle":"2022-08-25T09:08:06.79491Z","shell.execute_reply.started":"2022-08-25T09:08:06.245054Z","shell.execute_reply":"2022-08-25T09:08:06.793798Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Example**","metadata":{}},{"cell_type":"code","source":"# Example\nex_path = \"../input/rsna-2022-cervical-spine-fracture-detection/segmentations/1.2.826.0.1.3680043.12281.nii\"\nexample = nib.load(ex_path)\nexample = example.get_fdata()  # convert to numpy array\nexample = example[:, ::-1, ::-1].transpose(2, 1, 0)  # align orientation with train image\nnp.unique(example[119])","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:06.798169Z","iopub.execute_input":"2022-08-25T09:08:06.798613Z","iopub.status.idle":"2022-08-25T09:08:07.014869Z","shell.execute_reply.started":"2022-08-25T09:08:06.798576Z","shell.execute_reply":"2022-08-25T09:08:07.014148Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Interpretation:\n* 0 ---> background \n* 2 ---> C2","metadata":{}},{"cell_type":"code","source":"plt.figure()\nplt.imshow(example[119])\nplt.title('Segmentation example')\nplt.colorbar()\nplt.axis('off')\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:07.016084Z","iopub.execute_input":"2022-08-25T09:08:07.016382Z","iopub.status.idle":"2022-08-25T09:08:07.265225Z","shell.execute_reply.started":"2022-08-25T09:08:07.016355Z","shell.execute_reply":"2022-08-25T09:08:07.264076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Extract vertebrae from segmentations**","metadata":{}},{"cell_type":"code","source":"# Initialise targets\ntargets = ['C1','C2','C3','C4','C5','C6','C7']\nmeta_seg[targets]=0","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.266717Z","iopub.execute_input":"2022-08-25T09:08:07.267025Z","iopub.status.idle":"2022-08-25T09:08:07.275653Z","shell.execute_reply.started":"2022-08-25T09:08:07.266998Z","shell.execute_reply":"2022-08-25T09:08:07.274646Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"'''\nk=0\n# Loop over 87 patients with segmentations\nfor path, UID in zip(seg_df['path'], seg_df['StudyInstanceUID']):\n    # Get segmentations for patient\n    seg_nib = nib.load(path)\n    seg = seg_nib.get_fdata()\n    seg = seg[:, ::-1, ::-1].transpose(2, 1, 0) # Align orientation with train images\n    num_slices, _, _ = seg.shape\n    \n    # Loop over slices\n    for i in range(num_slices):\n        mask = seg[i]\n        unique_vals = np.unique(mask)\n        \n        # Loop over unique values (except 0)\n        for j in unique_vals[1:]:\n            \n            # Ignore thoratic spine etc\n            if j <= 7:   \n                meta_seg.loc[(meta_seg['StudyInstanceUID']==UID)&(meta_seg['Slice']==i),f'C{int(j)}'] = 1\n                \n    # Iteration tracker\n    if (k%10)==0:\n        print(f'Iteration:{k}')\n    k+=1\n\n# Save extracted targets\nmeta_seg.to_csv(\"meta_segmentation.csv\", index=False)\n'''","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:07.277129Z","iopub.execute_input":"2022-08-25T09:08:07.278311Z","iopub.status.idle":"2022-08-25T09:08:07.286033Z","shell.execute_reply.started":"2022-08-25T09:08:07.278265Z","shell.execute_reply":"2022-08-25T09:08:07.285172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"meta_seg = pd.read_csv('../input/rsna-2022-spine-fracture-detection-metadata/meta_segmentation.csv')\nmeta_seg.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.287744Z","iopub.execute_input":"2022-08-25T09:08:07.288401Z","iopub.status.idle":"2022-08-25T09:08:07.346233Z","shell.execute_reply.started":"2022-08-25T09:08:07.288359Z","shell.execute_reply":"2022-08-25T09:08:07.345392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Examples**","metadata":{}},{"cell_type":"code","source":"# Example\nmeta_seg[['StudyInstanceUID','Slice']+targets].iloc[199:204,:]","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.347668Z","iopub.execute_input":"2022-08-25T09:08:07.348284Z","iopub.status.idle":"2022-08-25T09:08:07.363704Z","shell.execute_reply.started":"2022-08-25T09:08:07.348251Z","shell.execute_reply":"2022-08-25T09:08:07.362915Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Print example of extracted vertebrae\nprint('UID:', meta_seg['StudyInstanceUID'].unique()[0])\npd.set_option('display.max_rows', 500)\nmeta_seg[meta_seg['StudyInstanceUID']==meta_seg['StudyInstanceUID'].unique()[0]].loc[110:340,targets]","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:07.366746Z","iopub.execute_input":"2022-08-25T09:08:07.367751Z","iopub.status.idle":"2022-08-25T09:08:07.419998Z","shell.execute_reply.started":"2022-08-25T09:08:07.367718Z","shell.execute_reply":"2022-08-25T09:08:07.419219Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Notice how the targets are **monotonic** (C1->C2->C3...) and sometimes **overlap** (e.g. C1 and C2 can appear in the same image). ","metadata":{}},{"cell_type":"markdown","source":"Thoughts:\n* We can use this df to train a model to **identify targets** for the **rest of the train images**. \n* We can **first** try building a model using the **metadata only** (this will be faster) as a baseline.\n* Imposing **monotonicity** will be tricky (maybe we can try to predict the slices where the vertebrae **appear** and **disappear** as opposed trying to predict which vertebrae is in every image).\n* Then we can then try to use the **image data** as well for the highest accuracy (this will be more time comsuming). ","metadata":{}},{"cell_type":"markdown","source":"<hr>","metadata":{}},{"cell_type":"markdown","source":"**Build baseline supervised model**","metadata":{}},{"cell_type":"code","source":"# Calculate slice ratio (to generalise better)\nslice_max_seg = meta_seg.groupby('StudyInstanceUID')['Slice'].max().to_dict()\nmeta_seg['SliceRatio'] = 0\nmeta_seg['SliceRatio'] = meta_seg['Slice']/meta_seg['StudyInstanceUID'].map(slice_max_seg)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.421427Z","iopub.execute_input":"2022-08-25T09:08:07.422006Z","iopub.status.idle":"2022-08-25T09:08:07.436135Z","shell.execute_reply.started":"2022-08-25T09:08:07.421975Z","shell.execute_reply":"2022-08-25T09:08:07.434861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"features = ['SliceRatio','SliceThickness','ImagePositionPatient_x','ImagePositionPatient_y','ImagePositionPatient_z']\n\n# Features and targets\nX = meta_seg[['StudyInstanceUID']+features]\ny = meta_seg[targets]","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.437872Z","iopub.execute_input":"2022-08-25T09:08:07.438252Z","iopub.status.idle":"2022-08-25T09:08:07.446647Z","shell.execute_reply.started":"2022-08-25T09:08:07.438219Z","shell.execute_reply":"2022-08-25T09:08:07.445719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train-valid split, grouped by patient (80/20 split)\ngkf = GroupKFold(n_splits=5)\n(train_idx, valid_idx) = next(gkf.split(X, y, groups = X['StudyInstanceUID']))\n\n# Train set\nX_train, y_train = X.iloc[train_idx,:], y.iloc[train_idx,:]\n\n# Validation set\nX_valid, y_valid = X.iloc[valid_idx,:], y.iloc[valid_idx,:]\n\n# Drop patient id\nX_train = X_train.drop('StudyInstanceUID', axis=1)\nX_valid = X_valid.drop('StudyInstanceUID', axis=1)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.448412Z","iopub.execute_input":"2022-08-25T09:08:07.449209Z","iopub.status.idle":"2022-08-25T09:08:07.478771Z","shell.execute_reply.started":"2022-08-25T09:08:07.449167Z","shell.execute_reply":"2022-08-25T09:08:07.477782Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Train classifier\nclf = RandomForestClassifier()\nclf.fit(X_train, y_train)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:07.48026Z","iopub.execute_input":"2022-08-25T09:08:07.48116Z","iopub.status.idle":"2022-08-25T09:08:14.126915Z","shell.execute_reply.started":"2022-08-25T09:08:07.481128Z","shell.execute_reply":"2022-08-25T09:08:14.125756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Evaluate model\ny_preds = clf.predict(X_valid)\n\ntotal_acc = 0 \nfor i in range(7):\n    acc = (y_valid[f'C{i+1}']==y_preds[:,i]).sum()/len(y_preds[:,i])\n    total_acc+=acc/7\n    print(f'Accuracy of C{i+1}: {acc} %')\n\nprint('')\nprint(f'Overall accuracy: {total_acc} %')","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:14.128362Z","iopub.execute_input":"2022-08-25T09:08:14.129201Z","iopub.status.idle":"2022-08-25T09:08:14.515616Z","shell.execute_reply.started":"2022-08-25T09:08:14.129155Z","shell.execute_reply":"2022-08-25T09:08:14.51429Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Feature importances\npd.DataFrame({'Feature':features, 'Importance':clf.feature_importances_}).sort_values(by='Importance', ascending=False)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:14.517252Z","iopub.execute_input":"2022-08-25T09:08:14.517702Z","iopub.status.idle":"2022-08-25T09:08:14.547017Z","shell.execute_reply.started":"2022-08-25T09:08:14.51766Z","shell.execute_reply":"2022-08-25T09:08:14.54602Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"preds = clf.predict(X_valid)\n\n# Confusion matrices\nfig = plt.figure(figsize=(20,10))\nfor i in range(7):\n    cm = confusion_matrix(preds[:,i], y_valid.values[:,i])\n    plt.subplot(2,4,i+1)\n    CBAR=False\n    if (i==3) or (i==6):\n        CBAR=True\n    sns.heatmap(cm, annot=True, fmt='d', cbar=CBAR, cmap='Blues')\n    plt.xlabel('Predicted label')\n    plt.ylabel('True label')\n    plt.title(f'C{i+1}')\nfig.tight_layout()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:14.548488Z","iopub.execute_input":"2022-08-25T09:08:14.548806Z","iopub.status.idle":"2022-08-25T09:08:16.262636Z","shell.execute_reply.started":"2022-08-25T09:08:14.548778Z","shell.execute_reply":"2022-08-25T09:08:16.261512Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Example of predictions**","metadata":{}},{"cell_type":"code","source":"np.set_printoptions(threshold=np.inf)\nclf.predict(X.drop('StudyInstanceUID',axis=1))[110:250,:]","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-08-25T09:08:16.264056Z","iopub.execute_input":"2022-08-25T09:08:16.264929Z","iopub.status.idle":"2022-08-25T09:08:17.999567Z","shell.execute_reply.started":"2022-08-25T09:08:16.264895Z","shell.execute_reply":"2022-08-25T09:08:17.998377Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It seems to be preserving monotonicity quite well.","metadata":{}},{"cell_type":"markdown","source":"**Predict vertebrae numbers on entire train set**","metadata":{}},{"cell_type":"code","source":"# Read in metadata for entire train set\nmeta_train = pd.read_csv('../input/rsna-2022-spine-fracture-detection-metadata/meta_train_clean.csv')\n\n# Calculate slice ratio (to generalise better)\nslice_max_train = meta_train.groupby('StudyInstanceUID')['Slice'].max().to_dict()\nmeta_train['SliceRatio'] = 0\nmeta_train['SliceRatio'] = meta_train['Slice']/meta_train['StudyInstanceUID'].map(slice_max_train)\n\n# Initialise targets\nmeta_train[targets]=0\n\n# Predict targets for entire train set\nmeta_train[targets] = clf.predict(meta_train[features])\n\n# We know images with segmentations have 100% accurate targets so put these back in\nmeta_train.loc[meta_train['StudyInstanceUID'].isin(meta_seg['StudyInstanceUID']),targets] = meta_seg[targets].values\n\n# Save to csv\nmeta_train.to_csv('meta_train_with_vertebrae.csv', index=False)\n\n# Preview\nmeta_train.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-08-25T09:08:18.001109Z","iopub.execute_input":"2022-08-25T09:08:18.002325Z","iopub.status.idle":"2022-08-25T09:09:15.494159Z","shell.execute_reply.started":"2022-08-25T09:08:18.002291Z","shell.execute_reply":"2022-08-25T09:09:15.492886Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Plot distributions**","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize=(20,16))\nfor i, Cx in enumerate(targets):\n    plt.subplot(4,2,i+1)\n    sns.histplot(meta_train.groupby('StudyInstanceUID')[Cx].sum())\n    plt.title(f'Number of slices: {Cx}')\nfig.tight_layout()","metadata":{"_kg_hide-input":false,"execution":{"iopub.status.busy":"2022-08-25T09:09:15.495845Z","iopub.execute_input":"2022-08-25T09:09:15.496833Z","iopub.status.idle":"2022-08-25T09:09:18.076923Z","shell.execute_reply.started":"2022-08-25T09:09:15.496792Z","shell.execute_reply":"2022-08-25T09:09:18.075791Z"},"trusted":true},"execution_count":null,"outputs":[]}]}