{"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-10-14T06:49:58.497076Z","iopub.execute_input":"2022-10-14T06:49:58.497913Z","iopub.status.idle":"2022-10-14T06:50:08.102924Z","shell.execute_reply.started":"2022-10-14T06:49:58.497824Z","shell.execute_reply":"2022-10-14T06:50:08.101982Z"},"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-10-14T06:50:08.104794Z","iopub.execute_input":"2022-10-14T06:50:08.105196Z","iopub.status.idle":"2022-10-14T06:50:08.169438Z","shell.execute_reply.started":"2022-10-14T06:50:08.105159Z","shell.execute_reply":"2022-10-14T06:50:08.168557Z"},"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-10-14T06:50:08.170678Z","iopub.execute_input":"2022-10-14T06:50:08.171032Z","iopub.status.idle":"2022-10-14T06:50:08.201624Z","shell.execute_reply.started":"2022-10-14T06:50:08.170997Z","shell.execute_reply":"2022-10-14T06:50:08.200457Z"},"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-10-14T06:50:08.20424Z","iopub.execute_input":"2022-10-14T06:50:08.204491Z","iopub.status.idle":"2022-10-14T06:50:09.151075Z","shell.execute_reply.started":"2022-10-14T06:50:08.204467Z","shell.execute_reply":"2022-10-14T06:50:09.150044Z"},"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-10-14T06:50:09.152652Z","iopub.execute_input":"2022-10-14T06:50:09.153277Z","iopub.status.idle":"2022-10-14T06:50:10.348003Z","shell.execute_reply.started":"2022-10-14T06:50:09.15324Z","shell.execute_reply":"2022-10-14T06:50:10.347006Z"},"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-10-14T06:50:10.351272Z","iopub.execute_input":"2022-10-14T06:50:10.351815Z","iopub.status.idle":"2022-10-14T06:50:10.607898Z","shell.execute_reply.started":"2022-10-14T06:50:10.351782Z","shell.execute_reply":"2022-10-14T06:50:10.60695Z"},"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-10-14T06:50:10.609566Z","iopub.execute_input":"2022-10-14T06:50:10.610156Z","iopub.status.idle":"2022-10-14T06:50:10.618553Z","shell.execute_reply.started":"2022-10-14T06:50:10.610117Z","shell.execute_reply":"2022-10-14T06:50:10.61756Z"},"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-10-14T06:50:10.620163Z","iopub.execute_input":"2022-10-14T06:50:10.62065Z","iopub.status.idle":"2022-10-14T06:50:10.630295Z","shell.execute_reply.started":"2022-10-14T06:50:10.620593Z","shell.execute_reply":"2022-10-14T06:50:10.629259Z"},"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-10-14T06:50:10.631647Z","iopub.execute_input":"2022-10-14T06:50:10.632262Z","iopub.status.idle":"2022-10-14T06:50:10.724654Z","shell.execute_reply.started":"2022-10-14T06:50:10.632226Z","shell.execute_reply":"2022-10-14T06:50:10.723586Z"},"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-10-14T06:50:10.728827Z","iopub.execute_input":"2022-10-14T06:50:10.731135Z","iopub.status.idle":"2022-10-14T06:50:10.746577Z","shell.execute_reply.started":"2022-10-14T06:50:10.731104Z","shell.execute_reply":"2022-10-14T06:50:10.745389Z"},"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-10-14T06:50:10.748279Z","iopub.execute_input":"2022-10-14T06:50:10.748941Z","iopub.status.idle":"2022-10-14T06:50:10.796178Z","shell.execute_reply.started":"2022-10-14T06:50:10.748898Z","shell.execute_reply":"2022-10-14T06:50:10.795214Z"},"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-10-14T06:50:10.797477Z","iopub.execute_input":"2022-10-14T06:50:10.798497Z","iopub.status.idle":"2022-10-14T06:50:10.814443Z","shell.execute_reply.started":"2022-10-14T06:50:10.798459Z","shell.execute_reply":"2022-10-14T06:50:10.813431Z"},"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-10-14T06:50:10.815973Z","iopub.execute_input":"2022-10-14T06:50:10.816577Z","iopub.status.idle":"2022-10-14T06:50:10.828082Z","shell.execute_reply.started":"2022-10-14T06:50:10.81651Z","shell.execute_reply":"2022-10-14T06:50:10.826939Z"},"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-10-14T06:50:10.831309Z","iopub.execute_input":"2022-10-14T06:50:10.832778Z","iopub.status.idle":"2022-10-14T06:50:10.85629Z","shell.execute_reply.started":"2022-10-14T06:50:10.832736Z","shell.execute_reply":"2022-10-14T06:50:10.855321Z"},"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-10-14T06:50:10.859472Z","iopub.execute_input":"2022-10-14T06:50:10.859767Z","iopub.status.idle":"2022-10-14T06:50:16.279651Z","shell.execute_reply.started":"2022-10-14T06:50:10.859741Z","shell.execute_reply":"2022-10-14T06:50:16.278574Z"},"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-10-14T06:50:16.281057Z","iopub.execute_input":"2022-10-14T06:50:16.281751Z","iopub.status.idle":"2022-10-14T06:50:16.612451Z","shell.execute_reply.started":"2022-10-14T06:50:16.281713Z","shell.execute_reply":"2022-10-14T06:50:16.611574Z"},"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-10-14T06:50:16.613784Z","iopub.execute_input":"2022-10-14T06:50:16.614148Z","iopub.status.idle":"2022-10-14T06:50:16.64182Z","shell.execute_reply.started":"2022-10-14T06:50:16.614115Z","shell.execute_reply":"2022-10-14T06:50:16.640794Z"},"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-10-14T06:50:16.643471Z","iopub.execute_input":"2022-10-14T06:50:16.643981Z","iopub.status.idle":"2022-10-14T06:50:18.236804Z","shell.execute_reply.started":"2022-10-14T06:50:16.643946Z","shell.execute_reply":"2022-10-14T06:50:18.235853Z"},"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-10-14T06:50:18.237772Z","iopub.execute_input":"2022-10-14T06:50:18.238087Z","iopub.status.idle":"2022-10-14T06:50:19.769484Z","shell.execute_reply.started":"2022-10-14T06:50:18.238056Z","shell.execute_reply":"2022-10-14T06:50:19.76849Z"},"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-10-14T06:50:19.770815Z","iopub.execute_input":"2022-10-14T06:50:19.771472Z","iopub.status.idle":"2022-10-14T06:51:12.713226Z","shell.execute_reply.started":"2022-10-14T06:50:19.771434Z","shell.execute_reply":"2022-10-14T06:51:12.71226Z"},"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-10-14T06:51:12.714702Z","iopub.execute_input":"2022-10-14T06:51:12.715049Z","iopub.status.idle":"2022-10-14T06:51:15.062799Z","shell.execute_reply.started":"2022-10-14T06:51:12.715015Z","shell.execute_reply":"2022-10-14T06:51:15.061741Z"},"trusted":true},"execution_count":null,"outputs":[]}]}