{"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 numpy as np\nimport matplotlib.pyplot as plt\nimport pydicom as pdi\nimport pandas as pd\nimport tensorflow as tf\nimport cv2\nimport os\nimport sys\nimport keras_cv\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, GlobalAveragePooling2D, Dropout, BatchNormalization\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.metrics import Precision, Recall\nimport pyarrow.parquet as parq\nfrom tqdm import tqdm\nfrom sklearn.model_selection import train_test_split","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:35.523942Z","iopub.execute_input":"2023-10-02T18:20:35.524812Z","iopub.status.idle":"2023-10-02T18:20:48.943248Z","shell.execute_reply.started":"2023-10-02T18:20:35.524778Z","shell.execute_reply":"2023-10-02T18:20:48.942189Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Configuration Parameters:","metadata":{}},{"cell_type":"code","source":"class Config:\n    BASE_PATH = \"/kaggle/input/rsna-2023-abdominal-trauma-detection\"\n    DATA_PATH = \"/data\"\n    TEST_PATH = \"/test\"\n    SEED = 42\n    # Define the number of classes for each label\n    NUM_CLASSES = {\n        'bowel': 2,\n        'extravasation': 2,\n        'kidney': 3,\n        'liver': 3,\n        'spleen': 3\n    }\n    TARGET_COLS  = [\n        \"bowel_healthy\", \"bowel_injury\",\n        \"extravasation_healthy\", \"extravasation_injury\",\n        \"kidney_healthy\", \"kidney_low\", \"kidney_high\",\n        \"liver_healthy\", \"liver_low\", \"liver_high\",\n        \"spleen_healthy\", \"spleen_low\", \"spleen_high\",\n    ]\n    BATCH_SIZE = 32\n    IMAGE_SIZE = (256, 256)\n    AUTOTUNE = tf.data.AUTOTUNE\n    NUM_EPOCHS = 2\n    FRACTION_NO_INJURY = 0.05\n\nconfig = Config()","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:48.94518Z","iopub.execute_input":"2023-10-02T18:20:48.946381Z","iopub.status.idle":"2023-10-02T18:20:48.954829Z","shell.execute_reply.started":"2023-10-02T18:20:48.946344Z","shell.execute_reply":"2023-10-02T18:20:48.953558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Read the data:","metadata":{}},{"cell_type":"code","source":"train_output = pd.read_csv(f\"{config.BASE_PATH}/train.csv\")\nnum_rows_to_sample_no_injury = int(config.FRACTION_NO_INJURY * len(train_output[train_output['any_injury'] == 0]))\n\ntrain_output_with_injury = train_output[train_output['any_injury'] == 1]\ntrain_output_with_no_injury = train_output[train_output['any_injury'] == 0].sample(n=num_rows_to_sample_no_injury , random_state=config.SEED)\ntrain_output_final = pd.concat([train_output_with_injury, train_output_with_no_injury]).sample(frac=1, random_state=config.SEED)\n\nprint(\"Total Patients:\", len(train_output_final))","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:48.956519Z","iopub.execute_input":"2023-10-02T18:20:48.957226Z","iopub.status.idle":"2023-10-02T18:20:49.002653Z","shell.execute_reply.started":"2023-10-02T18:20:48.957183Z","shell.execute_reply":"2023-10-02T18:20:49.001797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dicom_tags = parq.read_table('/kaggle/input/rsna-2023-abdominal-trauma-detection/train_dicom_tags.parquet').to_pandas()\ntrain_dicom_tags['PatientID'] = train_dicom_tags['PatientID'].map(lambda x: int(x))\ntrain_dicom_tags['SeriesInstanceUID'] = train_dicom_tags['SeriesInstanceUID'].map(lambda x: int( x.split('.')[-1]))\nimage_identifiers = train_dicom_tags[\n    ['InstanceNumber',\n    'PatientID',\n    'SeriesInstanceUID',\n    'path']\n].rename(\n    columns={\n        'PatientID': 'patient_id',\n        'SeriesInstanceUID': 'series_id',\n        'InstanceNumber': 'instance_number'\n    }\n)\nall_available_data = train_output_final.merge(image_identifiers, on='patient_id', how='inner')\nall_available_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:49.004869Z","iopub.execute_input":"2023-10-02T18:20:49.00571Z","iopub.status.idle":"2023-10-02T18:20:57.04277Z","shell.execute_reply.started":"2023-10-02T18:20:49.005679Z","shell.execute_reply":"2023-10-02T18:20:57.041808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare the dataset for training:","metadata":{}},{"cell_type":"markdown","source":"### Function for splitting data into two parts:","metadata":{}},{"cell_type":"code","source":"def split_group(group, train_size):\n    if len(group) == 1:\n        return (group, pd.DataFrame()) if np.random.rand() < train_size else (pd.DataFrame(), group)\n    else:\n        return train_test_split(group, test_size=1 - train_size, random_state=config.SEED)\n    \ndef split_data(dataframe, train_size):\n    # Initialize the train and validation datasets\n    train_data = pd.DataFrame()\n    val_data = pd.DataFrame()\n\n    # Iterate through the groups and split them, handling single-sample groups\n    for _, group in dataframe.groupby(config.TARGET_COLS):\n        train_group, val_group = split_group(group, train_size)\n        train_data = pd.concat([train_data, train_group], ignore_index=True)\n        val_data = pd.concat([val_data, val_group], ignore_index=True)\n    \n    return train_data, val_data","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:57.044024Z","iopub.execute_input":"2023-10-02T18:20:57.044966Z","iopub.status.idle":"2023-10-02T18:20:57.054243Z","shell.execute_reply.started":"2023-10-02T18:20:57.044933Z","shell.execute_reply":"2023-10-02T18:20:57.053355Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Take 25% of data (i.e, around 1,19,000) for training and validation due to resource constraints","metadata":{}},{"cell_type":"code","source":"final_data, _ = split_data(all_available_data, 0.25)\n\nprint(\"Total data available: \", len(final_data))","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:57.056541Z","iopub.execute_input":"2023-10-02T18:20:57.057225Z","iopub.status.idle":"2023-10-02T18:20:57.690884Z","shell.execute_reply.started":"2023-10-02T18:20:57.057189Z","shell.execute_reply":"2023-10-02T18:20:57.689949Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Remove Invalid Values:","metadata":{}},{"cell_type":"code","source":"def remove_invalid_paths(dataframe):\n    path_array = dataframe['path']\n    mask = [os.path.exists(f\"{config.BASE_PATH}/{path}\") for path in path_array]\n    return dataframe[mask]\nfinal_data = remove_invalid_paths(final_data)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:20:57.692247Z","iopub.execute_input":"2023-10-02T18:20:57.693095Z","iopub.status.idle":"2023-10-02T18:23:24.967787Z","shell.execute_reply.started":"2023-10-02T18:20:57.69306Z","shell.execute_reply":"2023-10-02T18:23:24.966788Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Convert Dicom to PNG:","metadata":{}},{"cell_type":"code","source":"def standardize_pixel_array(dcm):\n    # Correct DICOM pixel_array if PixelRepresentation == 1.\n    pixel_array = dcm.pixel_array\n    if dcm.PixelRepresentation == 1:\n        bit_shift = dcm.BitsAllocated - dcm.BitsStored\n        dtype = pixel_array.dtype \n        new_array = (pixel_array << bit_shift).astype(dtype) >>  bit_shift\n        pixel_array = pdi.pixel_data_handlers.util.apply_modality_lut(new_array, dcm)\n    return pixel_array\n\ndef convert_dicom_to_png(file_path, isTest = False ,fix_monochrome=True):\n    input_file_path = f\"{config.BASE_PATH}/{file_path}\"\n    if isTest:\n        output_file_path = f\"{config.TEST_PATH}/{os.path.splitext(file_path)[0]}.png\"\n    else:\n        output_file_path = f\"{config.DATA_PATH}/{os.path.splitext(file_path)[0]}.png\"\n    \n    dicom = pdi.dcmread(input_file_path)\n    data = standardize_pixel_array(dicom)\n    data = data - np.min(data)\n    data = data / (np.max(data) + 1e-5)\n    if fix_monochrome and dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = 1.0 - data\n        \n    image =  (data * 255).astype('uint8')\n    image = cv2.resize(image, config.IMAGE_SIZE, interpolation=cv2.INTER_LINEAR)\n    \n    directory_path = os.path.dirname(output_file_path)\n    if not os.path.exists(directory_path):\n        os.makedirs(directory_path)\n    success = cv2.imwrite(output_file_path, image)\n    if not success:\n        print(\"Failed to convert dicom...\")\n        sys.exit()\n        \n    return","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:23:24.96911Z","iopub.execute_input":"2023-10-02T18:23:24.969662Z","iopub.status.idle":"2023-10-02T18:23:24.979044Z","shell.execute_reply.started":"2023-10-02T18:23:24.969629Z","shell.execute_reply":"2023-10-02T18:23:24.978052Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Clear previous data\n!rm -r /data\ninput_dicom_paths = final_data[\"path\"].tolist()\nfor file_path in tqdm(input_dicom_paths, desc=\"Converting DICOM to PNG\"):\n    convert_dicom_to_png(file_path)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:23:24.980651Z","iopub.execute_input":"2023-10-02T18:23:24.981423Z","iopub.status.idle":"2023-10-02T18:57:52.683555Z","shell.execute_reply.started":"2023-10-02T18:23:24.981389Z","shell.execute_reply":"2023-10-02T18:57:52.681234Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create the dataset:","metadata":{}},{"cell_type":"code","source":"def load_image_and_label(input_file_path, label):\n    file_bytes = tf.io.read_file(input_file_path)\n    image = tf.io.decode_png(file_bytes, channels=3, dtype=tf.uint8)\n    image = tf.cast(image, tf.float32) / 255.0\n    \n    label = tf.cast(label, tf.float32)\n    labels = {\n            'bowel_output': label[0:2],\n            'extravasation_output': label[2:4],\n            'kidney_output': label[4:7],\n            'liver_output': label[7:10],\n            'spleen_output': label[10:13]\n    }\n    \n    return (image, labels)\n\ndef build_dataset(dataframe):\n    image_paths = [f\"{config.DATA_PATH}/{os.path.splitext(path)[0]}.png\" for path in dataframe[\"path\"].values]\n    image_labels = dataframe[config.TARGET_COLS].values.astype(np.float32)\n    ds = (\n        tf.data.Dataset.from_tensor_slices((image_paths, image_labels))\n        .map(load_image_and_label, num_parallel_calls=config.AUTOTUNE)\n        .shuffle(config.BATCH_SIZE * 10)\n        .batch(config.BATCH_SIZE)\n        .prefetch(config.AUTOTUNE)\n    )\n    return ds","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:57:52.69169Z","iopub.execute_input":"2023-10-02T18:57:52.692022Z","iopub.status.idle":"2023-10-02T18:57:52.706955Z","shell.execute_reply.started":"2023-10-02T18:57:52.691996Z","shell.execute_reply":"2023-10-02T18:57:52.705821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data, valid_data = split_data(final_data, 0.7)\ntrain_data_set = build_dataset(train_data)\nvalid_data_set = build_dataset(valid_data)\nprint(f\"Training Data Size: {len(train_data)}, Validation Data Size: {len(valid_data)}\")\nprint(len(train_data_set), len(valid_data_set))","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:57:52.708795Z","iopub.execute_input":"2023-10-02T18:57:52.709855Z","iopub.status.idle":"2023-10-02T18:58:00.669341Z","shell.execute_reply.started":"2023-10-02T18:57:52.709821Z","shell.execute_reply":"2023-10-02T18:58:00.668382Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Distribution of train and validation data:","metadata":{}},{"cell_type":"markdown","source":"#### Train Data:","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize = (15, 15))\nax = fig.gca()\ntrain_data[config.TARGET_COLS].hist(ax=ax)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:00.67096Z","iopub.execute_input":"2023-10-02T18:58:00.671327Z","iopub.status.idle":"2023-10-02T18:58:03.215794Z","shell.execute_reply.started":"2023-10-02T18:58:00.671294Z","shell.execute_reply":"2023-10-02T18:58:03.214909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Validation Data:","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize = (15, 15))\nax = fig.gca()\nvalid_data[config.TARGET_COLS].hist(ax=ax)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:03.216719Z","iopub.execute_input":"2023-10-02T18:58:03.217145Z","iopub.status.idle":"2023-10-02T18:58:06.23531Z","shell.execute_reply.started":"2023-10-02T18:58:03.217115Z","shell.execute_reply":"2023-10-02T18:58:06.234475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualise some of the data:","metadata":{}},{"cell_type":"code","source":"images, labels = next(iter(train_data_set))\nkeras_cv.visualization.plot_image_gallery(\n    images=images,\n    value_range=(0, 1),\n    rows=2,\n    cols=6,\n)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:06.236777Z","iopub.execute_input":"2023-10-02T18:58:06.237329Z","iopub.status.idle":"2023-10-02T18:58:08.25267Z","shell.execute_reply.started":"2023-10-02T18:58:06.237297Z","shell.execute_reply":"2023-10-02T18:58:08.251807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define the Model Architecture:","metadata":{}},{"cell_type":"code","source":"class CustomMultiOutputModel(tf.keras.Model):\n    def __init__(self):\n        super(CustomMultiOutputModel, self).__init__()\n        \n        # Input layer for DICOM images\n        self.conv2D1 = Conv2D(32, (3, 3), activation='silu', input_shape=(config.IMAGE_SIZE[0], config.IMAGE_SIZE[0], 3))\n        self.batchNormalization1 = BatchNormalization()\n        self.maxPool1 = MaxPooling2D((2, 2))\n        \n        # Second convolutional layer\n        self.conv2D2 = Conv2D(32, (3, 3), activation='silu')\n        self.batchNormalization2 = BatchNormalization()\n        self.maxPool2 = MaxPooling2D((2, 2))\n        \n        # Flatten the image features\n        self.gap = GlobalAveragePooling2D()\n        \n        self.hidden_bowel = Dense(32, activation='silu')\n        self.hidden_extravasation = Dense(32, activation='silu')\n        self.hidden_kidney = Dense(32, activation='silu')\n        self.hidden_liver = Dense(32, activation='silu')\n        self.hidden_spleen = Dense(32, activation='silu')\n        \n        \n        # Output layers for each label\n        self.output_bowel = Dense(config.NUM_CLASSES['bowel'], activation='softmax', name='bowel_output')\n        self.output_extravasation = Dense(config.NUM_CLASSES['extravasation'], activation='softmax', name='extravasation_output')\n        self.output_kidney = Dense(config.NUM_CLASSES['kidney'], activation='softmax', name='kidney_output')\n        self.output_liver = Dense(config.NUM_CLASSES['liver'], activation='softmax', name='liver_output')\n        self.output_spleen = Dense(config.NUM_CLASSES['spleen'], activation='softmax', name='spleen_output')\n        \n    def call(self, inputs):\n        # Forward pass\n        x = self.conv2D1(inputs)\n        x = self.batchNormalization1(x)\n        x = self.maxPool1(x)\n        \n        # Second convolutional layer\n        x = self.conv2D2(x)\n        x = self.batchNormalization2(x)\n        x = self.maxPool2(x)\n        \n        # Flatten the image features\n        x = self.gap(x)\n        \n        # Add more layers as needed\n        x_bowel = self.hidden_bowel(x)\n        x_extravasation = self.hidden_extravasation(x)\n        x_kidney = self.hidden_kidney(x)\n        x_liver = self.hidden_liver(x)\n        x_spleen = self.hidden_spleen(x)\n        \n        # Return outputs for each label\n        return {\n            'bowel_output': self.output_bowel(x_bowel),\n            'extravasation_output': self.output_extravasation(x_extravasation),\n            'kidney_output': self.output_kidney(x_kidney),\n            'liver_output': self.output_liver(x_liver),\n            'spleen_output': self.output_spleen(x_spleen)\n        }\n","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:08.254381Z","iopub.execute_input":"2023-10-02T18:58:08.254725Z","iopub.status.idle":"2023-10-02T18:58:08.271417Z","shell.execute_reply.started":"2023-10-02T18:58:08.254694Z","shell.execute_reply":"2023-10-02T18:58:08.270325Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Compile the model:","metadata":{}},{"cell_type":"code","source":"model = CustomMultiOutputModel()\n\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-5),\n    loss={\n        'bowel_output': 'categorical_crossentropy',\n        'extravasation_output': 'categorical_crossentropy',\n        'kidney_output': 'categorical_crossentropy',\n        'liver_output': 'categorical_crossentropy',\n        'spleen_output': 'categorical_crossentropy'\n    },\n    metrics={\n        'bowel_output': 'accuracy',\n        'extravasation_output': 'accuracy',\n        'kidney_output': 'accuracy',\n        'liver_output': 'accuracy',\n        'spleen_output': 'accuracy'\n    }\n)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:08.272947Z","iopub.execute_input":"2023-10-02T18:58:08.273937Z","iopub.status.idle":"2023-10-02T18:58:08.336164Z","shell.execute_reply.started":"2023-10-02T18:58:08.273903Z","shell.execute_reply":"2023-10-02T18:58:08.335218Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train the model:","metadata":{}},{"cell_type":"code","source":"history = model.fit(\n    train_data_set,\n    epochs=config.NUM_EPOCHS,\n    batch_size=config.BATCH_SIZE,\n    validation_data=valid_data_set\n)","metadata":{"execution":{"iopub.status.busy":"2023-10-02T18:58:08.337628Z","iopub.execute_input":"2023-10-02T18:58:08.338016Z","iopub.status.idle":"2023-10-02T19:09:50.490575Z","shell.execute_reply.started":"2023-10-02T18:58:08.337981Z","shell.execute_reply":"2023-10-02T19:09:50.489566Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save the model:","metadata":{}},{"cell_type":"code","source":"model.save(\"/kaggle/working/rsna_model\", save_format=\"tf\")","metadata":{"execution":{"iopub.status.busy":"2023-10-02T19:09:50.491852Z","iopub.execute_input":"2023-10-02T19:09:50.49219Z","iopub.status.idle":"2023-10-02T19:09:53.98515Z","shell.execute_reply.started":"2023-10-02T19:09:50.492159Z","shell.execute_reply":"2023-10-02T19:09:53.984196Z"},"trusted":true},"execution_count":null,"outputs":[]}]}