{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"tpuV5e8","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":13289642,"sourceType":"datasetVersion","datasetId":8409626}],"dockerImageVersionId":31155,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 1. Import packages and setup environment (CPU/GPU/TPU)","metadata":{}},{"cell_type":"code","source":"## For TPU environment (install missing packages / reinstall tensorflow to solve NaN topic during training / restart kernel)\n\nimport IPython\nimport tensorflow as tf\n\nif len(tf.config.experimental.list_logical_devices('TPU')) > 0:\n    !pip install -q tensorflow-tpu -f https://storage.googleapis.com/libtpu-tf-releases/index.html --force-reinstall\n    !pip install -q pydot\n    !pip install -q -U keras-tuner\n    !pip install -q polars\n    !pip install -q pydicom\n    !pip install -q protobuf==5.29.5 # to solve tuner compatibility issue\n    IPython.Application.instance().kernel.do_shutdown(True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Import packages\n\n# General purpose modules\nimport os\nimport shutil\nfrom collections import defaultdict\nimport re\nimport math\nfrom tqdm import tqdm\nimport time\n\n# Data handling and visualization modules\nimport numpy as np\nimport pandas as pd\nimport polars as pl\nimport matplotlib.pyplot as plt\nfrom matplotlib.pyplot import imshow\nfrom matplotlib.patches import Circle\nimport pydicom\n\n# Skikit-learn preprocessing modules\nfrom sklearn.preprocessing import OneHotEncoder\nfrom sklearn.preprocessing import MinMaxScaler\nfrom sklearn.model_selection import StratifiedGroupKFold\n\n# Tensorflow modules\nimport tensorflow as tf\nimport keras_tuner as kt\n\n# Custom specific evaluation module\nimport kaggle_evaluation.rsna_inference_server","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Detect hardware (CPU/GPU/TPU), setup environment and return appropriate distribution strategy\n\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect(tpu='local') # set tpu is local as it should be available in the VM\n    print('✅ Running on TPU ', tpu.master())\nexcept:\n    print('❌ Using CPU/GPU')\n    tpu = None\n\nif tpu:\n    strategy = tf.distribute.TPUStrategy(tpu)\nelse:\n    strategy = tf.distribute.get_strategy() # default distribution strategy in Tensorflow. Works on CPU and single GPU.\n\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. Load and explore data","metadata":{}},{"cell_type":"code","source":"## Read csv files and merge them into a single dataframe\n\npath = '/kaggle/input/rsna-intracranial-aneurysm-detection/'\ntrainval = pd.read_csv(path + \"train.csv\")\ntrainval_localizers = pd.read_csv(path + \"train_localizers.csv\")\ntrainval = trainval.merge(trainval_localizers, on='SeriesInstanceUID', how='outer')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"We use StratifiedGroupKFold for the splitting of the data into training and validation set to ensure a similar label distribution and keep samples with same SeriesInstanceUID together to avoid data leakage.","metadata":{}},{"cell_type":"code","source":"## Spliting trainval data into train and validation data with StratifiedGroupKFold\n\n# Create a column with multiclass label for StratifiedGroupKFold splitting\nlabel_columns = trainval.columns[trainval.columns.str.contains('Artery|Tip|Other|Present', case=True)]\nlabel2class = {}\ntrainval['class'] = 0\nfor i, col in enumerate(label_columns[:]):\n    label2class[col] = i + 1\n    if i < 13:\n        trainval['class'] = trainval['class'] + trainval[col] * (i + 1)\n\n# Shuffle and split trainval data into train and validation data\nskf = StratifiedGroupKFold(n_splits=10, shuffle=True, random_state=42) # Baseline 42\nfor fold, (train_idx, val_idx) in enumerate(skf.split(trainval, y=trainval['class'], groups=trainval['SeriesInstanceUID'])):\n    train, val = trainval.iloc[train_idx], trainval.iloc[val_idx]\n    print(f\"✅ Fold {fold}: Train size = {len(train_idx)}, Val size = {len(val_idx)}\")\n    break  # Use only the first fold for now","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Preprocessing functions\n\nimage_size = 300 # input image size fo neural network model\n\n# Remove black frame around images and adjust coordinates accordingly\ndef crop_image(image, x, y, tol = 0.05, crop=True):\n    img = image[0, :, :, 0]\n    mask = img > tol\n    if crop and mask.sum()>1000:\n        masked_idx = np.ix_(mask.any(1), mask.any(0))\n        image = img[masked_idx]\n        image = image.reshape(1, image.shape[0], image.shape[1], 1)\n    if crop and x >= 0 and mask.sum()>1000: # if valid labels are present rescale coordinates according masking\n        coor = np.zeros((img.shape), dtype=float)\n        coor[round(y), round(x)] = 1\n        coor_masked = coor[masked_idx]\n        row, col = np.where(coor_masked == 1)\n        y, x = row[0], col[0]\n    return image, x, y\n\n# Pad and resize images (while retaining aspect ratio) and adjust coordinates accordingly\ndef pad_and_resize(image, x, y):\n    _, image_size_rows, image_size_cols, _ = image.shape\n    pad_size = max(image_size_rows, image_size_cols)\n    image_padded = tf.image.resize_with_crop_or_pad(image, pad_size, pad_size)\n    image_resized = tf.image.resize(image_padded, [image_size, image_size], method=tf.image.ResizeMethod.BICUBIC)\n    if x >= 0:\n        coor = np.zeros((image.shape), dtype=float)\n        coor[:, round(y), round(x), :] = 1\n        coor_padded = tf.image.resize_with_crop_or_pad(coor, pad_size, pad_size)\n        coor_resized = tf.image.resize(coor_padded, [image_size, image_size], method=tf.image.ResizeMethod.AREA)\n        _, row, col, _ = np.where(coor_resized.numpy() == coor_resized.numpy().max())\n        y, x = row[0], col[0]\n    return image_resized, x, y\n\n# Zoom/rotate/translate images and adjust coordinates accordingly\ndef image_augmentation(image, x, y, augmentation=True):\n    if augmentation:\n        coor = np.zeros((image.shape), dtype=float)\n        zoom_fac = np.random.uniform(0.0, 0.0)\n        rot_fac = np.random.uniform(-0.1, 0.1)\n        trans_fac = np.random.uniform(-0.05, 0.05)\n        z = tf.keras.layers.RandomZoom(height_factor=(zoom_fac, zoom_fac), fill_mode='constant', name='auglay1')(image)\n        z = tf.keras.layers.RandomRotation(factor=(rot_fac, rot_fac), fill_mode='constant', name='auglay2')(z)\n        image = tf.keras.layers.RandomTranslation(height_factor=(trans_fac, trans_fac), width_factor=(trans_fac, trans_fac),\n                                                  interpolation='nearest', fill_mode='constant', name='auglay3')(z)\n        if x >= 0:\n            coor[:, round(y), round(x), :] = 1\n            coor = tf.convert_to_tensor(coor)\n            z = tf.keras.layers.RandomZoom(height_factor=(zoom_fac, zoom_fac), fill_mode='constant', name='auglay1')(coor)\n            z = tf.keras.layers.RandomRotation(factor=(rot_fac, rot_fac), fill_mode='constant', name='auglay2')(z)\n            coor = tf.keras.layers.RandomTranslation(height_factor=(trans_fac, trans_fac), width_factor=(trans_fac, trans_fac),\n                                                  interpolation='nearest', fill_mode='constant', name='auglay3')(z)\n            _, row, col, _ = np.where(coor.numpy() == coor.numpy().max())\n            y, x = row[0], col[0]\n    return image, x, y\n\ndef preprocess_images(image, x, y, crop, augmentation):\n    image_scaled = (MinMaxScaler().fit_transform(image.reshape(-1, 1))).reshape(1, image.shape[0], image.shape[1], 1).astype(dtype=np.float32)\n    try:\n        image_croped, x_croped, y_croped = crop_image(image_scaled, x, y, crop=crop)\n    except:\n        image_croped, x_croped, y_croped = crop_image(image_scaled, x, y, crop=False)\n    image_aug, x_aug, y_aug = image_augmentation(image_croped, x_croped, y_croped, augmentation=augmentation)\n    image_resized, x_resized, y_resized = pad_and_resize(image_aug, x_aug, y_aug)\n    image_resized = tf.cast(image_resized*255, dtype=tf.uint8)\n    if x >= 0:\n        x_resized_scaled, y_resized_scaled = x_resized/image_size, y_resized/image_size\n    else:\n        x_resized_scaled, y_resized_scaled = -1, -1\n    return image_resized, x_resized_scaled, y_resized_scaled","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Display croped 2D image(s) with bounding circle around aneurysm center for choosen modalities\n\nmodality = 'MRI T2' # Choose modality to show from: CTA/MRA/MRI T2/MRI T1post\naneurysm_present = train[train['Aneurysm Present'] == 1]\naneurysm_present_mod_fil = aneurysm_present[aneurysm_present['Modality'] == modality] \nrows = 3 # Number of images in a row\ncolumns = 4 # Number of images in a column\nfig,ax = plt.subplots(rows, columns, figsize=(12, 12))\n\nfor i, index in enumerate(aneurysm_present_mod_fil.index[:rows*columns]):\n    # Extract raw data for selected sample\n    data_slice = aneurysm_present_mod_fil.loc[index]\n    SI_UID, SOPI_UID, = data_slice['SeriesInstanceUID'], data_slice['SOPInstanceUID'], \n    sex, age = data_slice['PatientSex'], data_slice['PatientAge']\n    x, y = eval(data_slice['coordinates'])['x'], eval(data_slice['coordinates'])['y']\n    image_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/series/' + SI_UID + '/' + SOPI_UID + '.dcm'\n    image = pydicom.dcmread(image_path).pixel_array\n    if len(image.shape) == 3:\n        image = image[0, :, :]\n\n    # Preprocess raw data (scaling, croping, padding, resizing)\n    image_resized, x_resized, y_resized = preprocess_images(image, x, y, crop=True, augmentation=True)\n    \n    # Plot processed images\n    row = i % rows\n    col = i // rows\n    ax[row,col].imshow(image_resized[0], cmap=plt.cm.hot)\n    ax[row,col].set_title(sex + ' ' + str(age))\n    ax[row,col].add_patch(Circle((x_resized*image_size, y_resized*image_size), 20, fill=False, ec='cyan'))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. Preprocess data","metadata":{}},{"cell_type":"markdown","source":"The number of images without aneurysm is much higher than with aneurysm. However, the train.csv is pretty good balanced. The idea is to loop over the series folders of the .csv file and take only 1 image from every series folder at every iteration. The images without aneurysm (only 1 frame in every series) will go through some augmentation layers to avoid using the same images in every iteration which might help the generalization. The number of iteration can be configured (every iteration takes around 7 minutes). To allow higher number of iterations wo having Out-of-Memory issues, the images are stored as uint8 tensors and will be converted back to float32 tensors in the model self.","metadata":{}},{"cell_type":"code","source":"## Preprocess train and validation data\n\nSUBMISSIONING = False # configuration flag to save runtime during submissioning\niter_tot_nr = 6 # number of iteration loops over train.csv entries resulting in iter_tot_nr * 4263 samples\nroot_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/series'\nencoder_mod = OneHotEncoder(handle_unknown='ignore', sparse_output=False).fit([['MR'], ['CT']])\n\ndef preprocess_step(data, iter_nr):\n    image_list = []\n    modality_list = []\n    label_list = []\n    coordinates_list = []\n    \n    # Loop over data lines and append image, label and coordinates to the corresponding lists\n    for i, index in enumerate(tqdm(data.index)):\n        data_slice = data.loc[index]\n        subfolder = data_slice['SeriesInstanceUID']\n        subfolder_path = root_path + '/' + subfolder\n        \n        if not data_slice[['SOPInstanceUID']].isna().values[0]:\n            file_name = data_slice['SOPInstanceUID'] + '.dcm'\n        else:\n            file_list = os.listdir(subfolder_path)\n            file_tot_nr = len(file_list) # number of frames\n            file_nr_list = np.rint((np.linspace(0, file_tot_nr-1, iter_tot_nr)))\n            file_nr = int(file_nr_list[iter_nr])\n            file_name = file_list[file_nr]\n\n        # Preprocess image data and coordinates\n        aneurysm_present = (data_slice['Aneurysm Present'] == 1)\n        augmentation = (iter_nr > 0) and aneurysm_present\n        dcm = pydicom.dcmread(os.path.join(subfolder_path, file_name))\n        try: # Set frame_nr for multiframe dcm (either from csv label if aneurysm is present or iteration number)\n            frames_tot_nr = int(dcm.NumberOfFrames)\n            if aneurysm_present:\n                frame_nr = int(eval(data_slice['coordinates'])['f'])\n            else:\n                frame_nr_list = np.rint((np.linspace(0, frames_tot_nr-1, iter_tot_nr)))\n                frame_nr = int(frame_nr_list[iter_nr])\n        except: # Exception if dcm file is not multiframe dcm\n            frame_nr = 0\n        image = pydicom.pixels.pixel_array(dcm, index=frame_nr)\n        mod = encoder_mod.transform([[dcm.Modality]])\n        if data_slice[['coordinates']].isna().values[0]:\n            x, y = -1, -1\n        else:\n            x, y = eval(data_slice['coordinates'])['x'], eval(data_slice['coordinates'])['y']\n        image_resized, x_resized, y_resized = preprocess_images(image, x, y, crop=True, augmentation=augmentation)\n        coordinates_tensor = tf.expand_dims(tf.convert_to_tensor([x_resized, y_resized], dtype=np.float32), 0)\n        image_list.append(image_resized)\n        modality_list.append(mod)\n        coordinates_list.append(coordinates_tensor)\n        \n        # Preprocess labels\n        labels = data_slice[label_columns]\n        label_tensor = tf.expand_dims(tf.convert_to_tensor(labels, dtype=np.float32), 0)\n        label_list.append(label_tensor)\n            \n    # Concat list of sample tensors\n    images = tf.concat(image_list, axis=0)\n    modalities = tf.concat(modality_list, axis=0)\n    labels = tf.concat(label_list, axis=0)\n    coordinates = tf.concat(coordinates_list, axis=0)\n    return images, labels, coordinates, modalities\n\ndef preprocess_loop(data, iter_tot_nr):\n    data_images_list = []\n    data_modalities_list = []\n    data_labels_list = []\n    data_coordinates_list = []\n    for iter_nr in range(iter_tot_nr):\n        data_images, data_labels, data_coordinates, data_modalities = preprocess_step(data, iter_nr)\n        data_images_list.append(data_images)\n        data_modalities_list.append(data_modalities)\n        data_labels_list.append(data_labels)\n        data_coordinates_list.append(data_coordinates)\n    data_images = tf.concat(data_images_list, axis=0)\n    data_modalities = tf.concat(data_modalities_list, axis=0)\n    data_labels = tf.concat(data_labels_list, axis=0)\n    data_coordinates = tf.concat(data_coordinates_list, axis=0)\n    return data_images, data_labels, data_coordinates, data_modalities\n\nif not SUBMISSIONING:\n    train_images, train_labels, train_coordinates, train_modalities = preprocess_loop(train[:], iter_tot_nr)\n    val_images, val_labels, val_coordinates, val_modalities = preprocess_loop(val[:], iter_tot_nr)\nelse:\n    train_images, train_labels, train_coordinates, train_modalities = preprocess_loop(train[:64], iter_tot_nr)\n    val_images, val_labels, val_coordinates, val_modalities = preprocess_loop(val[:64], iter_tot_nr)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Calculate the total memory size of train images in Gigabytes\n\nnum_elements = tf.size(train_images).numpy() # Get the number of elements in the tensor\nelement_size = train_images.dtype.size # Get the size of each element in bytes\ntotal_memory = num_elements * element_size\n\nprint(f\"Number of elements in train images: {num_elements}\")\nprint(f\"Size of each element in train images: {element_size} bytes\")\nprint(f\"Total memory size of train images: {total_memory/1024**3} Gigabytes\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Create train and validation datasets\n\nSEED=42\nbatch_size=32\nbatch_size_val=32\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(({\"input_img\": train_images, \"input_mod\": train_modalities},\n                                               {\"class\": train_labels, \"reg\": train_coordinates}))\ntrain_ds = train_ds.shuffle(len(train_labels), seed=SEED).repeat().batch(batch_size, drop_remainder=True).prefetch(tf.data.AUTOTUNE)\nval_ds = tf.data.Dataset.from_tensor_slices(({\"input_img\": val_images, \"input_mod\": val_modalities},\n                                             {\"class\": val_labels, \"reg\": val_coordinates}))\nval_ds = val_ds.batch(batch_size_val, drop_remainder=True).prefetch(tf.data.AUTOTUNE)\n\nprint('Size of train dataset: '+ str(len(train_labels)))\nprint('Number of batches in train dataset: '+ f'{len(train_labels)//batch_size}')\nprint('Size of validation dataset: '+ str(len(val_labels)))\nprint('Number of batches in val dataset: '+ f'{len(val_labels)//batch_size_val}')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Check validation dataset batch dimensions\n\nfor X, y in val_ds.take(1):\n    print(X['input_img'].shape)\n    print(X['input_mod'].shape)\n    print(y['class'].shape)\n    print(y['reg'].shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. Build and explore neural network","metadata":{}},{"cell_type":"code","source":"## Custom BCE class and AUC function to handle imbalanced label classes\n\nlabel_weights = tf.constant([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13], dtype=tf.float32)/26\n\n@tf.keras.utils.register_keras_serializable()\nclass WeightedBinaryCrossentropy(tf.keras.losses.Loss):\n    def __init__(self, name=\"weighted_bce_loss\", reduction='sum_over_batch_size'):\n        super().__init__(name=name, reduction=reduction)\n        self.weight_positive = 1 - tf.reduce_sum(train_labels, axis=0)/len(train_labels)\n        self.weight_negative = tf.reduce_sum(train_labels, axis=0)/len(train_labels)\n        self.label_weights = label_weights\n        \n    def call(self, y_true, y_pred):\n        # Clip predictions to avoid log(0) errors\n        y_pred = tf.clip_by_value(y_pred, 1e-7, 1 - 1e-7)\n        # Compute the class (0/1) weighted binary cross-entropy losses\n        bce_loss = -(self.weight_positive * y_true * tf.math.log(y_pred) +\n                     self.weight_negative * (1 - y_true) * tf.math.log(1 - y_pred))\n        # Compute the multilabel weighted binary cross-entropy losses\n        bce_loss = bce_loss*self.label_weights\n        return tf.reduce_mean(bce_loss)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The base network is a version of EfficientNet architecture (https://arxiv.org/pdf/1905.11946). The version number and the number of layers to retune are hyperparameters and can be tuned accordingly.","metadata":{}},{"cell_type":"markdown","source":"![](https://1.bp.blogspot.com/-Cdtb97FtgdA/XO3BHsB7oEI/AAAAAAAAEKE/bmtkonwgs8cmWyI5esVo8wJPnhPLQ5bGQCLcBGAs/s1600/image4.png)","metadata":{}},{"cell_type":"code","source":"## Network configuration and preprocessing layer\n\n# Network configurations\nbase_network = {4: \"enb0\",     # EfficientNetB0\n                5: \"enb1\",     # EfficientNetB1\n                6: \"enb2\",     # EfficientNetB2\n                7: \"enb3\",     # EfficientNetB3\n                8: \"enb4\",     # EfficientNetB4\n                9: \"env2b0\",   # EfficientNetV2B0\n                10: \"env2b1\",  # EfficientNetV2B1\n                11: \"env2b2\",  # EfficientNetV2B2\n                12: \"env2b3\",  # EfficientNetV2B3\n                13: \"env2s\"}   # EfficientNetV2S\n\n# Custom layer for preprocessing\n@tf.keras.utils.register_keras_serializable()\nclass Rescale(tf.keras.layers.Layer):\n    def __init__(self, **kwargs):\n        super(Rescale, self).__init__(**kwargs)\n    def call(self, inputs):\n        x = tf.cast(inputs, tf.float32)/255\n        x = tf.image.grayscale_to_rgb(x)\n        return x\n\n@tf.keras.utils.register_keras_serializable()\nclass PreProcess(tf.keras.layers.Layer):\n    def __init__(self, base_network_type, **kwargs):\n        super(PreProcess, self).__init__(**kwargs)\n        if base_network_type < 9: self.preprocess_input = tf.keras.applications.efficientnet.preprocess_input\n        elif base_network_type >= 9: self.preprocess_input = tf.keras.applications.efficientnet_v2.preprocess_input\n        else: print('Wrong base network number have been choosen!!!')\n    def call(self, inputs):\n        return self.preprocess_input(inputs*255.0)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Build Squeeze-and-Excitation network (SENet)\n\ndef build_network(hp):\n    # Select base model and corresponding preprocessing\n    base_network_type = 7 #hp.Int(name='base_network_type', min_value=4, max_value=13, step=1, default=7) # Choose Pretrained Network\n    if base_network_type == 4: base_model = tf.keras.applications.EfficientNetB0(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 5: base_model = tf.keras.applications.EfficientNetB1(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 6: base_model = tf.keras.applications.EfficientNetB2(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 7: base_model = tf.keras.applications.EfficientNetB3(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 8: base_model = tf.keras.applications.EfficientNetB4(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 9: base_model = tf.keras.applications.EfficientNetV2B0(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 10: base_model = tf.keras.applications.EfficientNetV2B1(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 11: base_model = tf.keras.applications.EfficientNetV2B2(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 12: base_model = tf.keras.applications.EfficientNetV2B3(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    elif base_network_type == 13: base_model = tf.keras.applications.EfficientNetV2S(\n        include_top=False, input_shape=[image_size, image_size, 3], weights='imagenet')\n    else: print('Wrong base network number have been choosen!!!')\n    prepocessing = PreProcess(base_network_type=base_network_type, name='preprocessing')\n\n    # Choose base model layers to be trained\n    base_model.trainable = False # freeze base model layers\n    max_layer_nr = len(base_model.layers)\n    # (B0): all:238 / 2ab+:220 / 3ab+:191 / 4abc+:162 / 5abc+:118 / 6abcd+:75 / 7a+:16\n    # (B3): all:385 / 2ab+:355 / 3ab+:311 / 4abc+:267 / 5abc+:193 / 6abcd+:120 / 7a+:31\n    layer_id = 355 #hp.Choice(name='layer_id', values=[355, max_layer_nr, 311, 267, 193, 120, 31]) # layer number from the network shall be trained\n    print('Unfreeze base model layers from layer ' + str(base_model.layers[-layer_id]))\n    for layer in base_model.layers[-layer_id:]: # unfreeze choosen layers\n        layer.trainable = True\n\n    # define the sets of inputs\n    input_img = tf.keras.Input(shape=(image_size, image_size, 1), name='input_img')\n    input_mod = tf.keras.Input(shape=(2,), name='input_mod')\n\n    # cast and rescale image tensors\n    x = Rescale(name='rescaling')(input_img)\n\n    # define pretrained EfficientNet layers\n    gap_do = hp.Float(name='do_dnn', min_value=0, max_value=0.4, step=0.05, default=0.1)\n    x = prepocessing(x)\n    x_base = base_model(x)\n\n    # prepare the output of EfficentNet and combine with the modality input for final classification dnn layer(s)\n    x_class = tf.keras.layers.GlobalAveragePooling2D(name=f'gap2d')(x_base)\n    x_class = tf.keras.layers.BatchNormalization(name=f'gap2d_bn')(x_class)\n    x_class = tf.keras.layers.Dropout(gap_do, name=f'gap2d_do')(x_class)\n    x_class = tf.keras.layers.Concatenate(name='concat_gap')([x_class, input_mod])\n\n    # prepare the output of EfficentNet and combine with the modality input for final regression dnn layer(s) (optional)\n    fil_1x1conv = hp.Int(name='fil_1x1conv', min_value=16, max_value=64, step=16, default=16)\n    x_reg = tf.keras.layers.Conv2D(fil_1x1conv, (1,1), name=f'1x1conv2d')(x_base)\n    x_reg = tf.keras.layers.Flatten(name=f'1x1conv2d_flatten')(x_reg)\n    x_reg = tf.keras.layers.BatchNormalization(name=f'1x1conv2d_bn')(x_reg)\n    x_reg = tf.keras.layers.Dropout(gap_do, name=f'1x1conv2d_do')(x_reg)\n    x_reg = tf.keras.layers.Concatenate(name='concat_1x1conv2d_reg')([x_reg, input_mod])\n            \n    # define DNN layer(s) for classification\n    x_class_fin = x_class\n    reg_dnn = None #tf.keras.regularizers.L2(1e-4)\n    ki_dnn = 'he_uniform'\n    do_dnn = gap_do\n    layers_final = 1 #hp.Int(name='layers_final', min_value=0, max_value=2, step=1, default=1)\n    units_final = 512 #hp.Int(name='units_final', min_value=128, max_value=512, step=128, default=512)\n    for i, units in enumerate([units_final for j in range(layers_final)]):\n        x_class_fin = tf.keras.layers.Dense(units, activation=\"relu\", kernel_regularizer=reg_dnn,\n                                            kernel_initializer=ki_dnn, name=f'final_fc{i+1}')(x_class_fin)\n        x_class_fin = tf.keras.layers.BatchNormalization(name=f'final_bn{i+1}')(x_class_fin)\n        x_class_fin = tf.keras.layers.Dropout(do_dnn, name=f'final_do{i+1}')(x_class_fin)\n    output_lab = tf.keras.layers.Dense(len(label2class.keys()), activation=\"sigmoid\", kernel_regularizer=reg_dnn, name='class')(x_class_fin)\n    \n    # define DNN layer(s) for regression\n    x_reg_fin = x_reg\n    reg_dnn_reg = None #tf.keras.regularizers.L2(1e-4)\n    ki_dnn_reg = 'he_uniform'\n    do_dnn_reg = gap_do\n    layers_final_reg = 1 #hp.Int(name='layers_final_reg', min_value=0, max_value=3, step=1, default=1)\n    units_final_reg = 256 #hp.Int(name='units_final_reg', min_value=128, max_value=512, step=128, default=256)\n    for i, units in enumerate([units_final_reg for j in range(layers_final_reg)]):\n        x_reg_fin = tf.keras.layers.Dense(units, activation=\"relu\", kernel_regularizer=reg_dnn_reg,\n                                          kernel_initializer=ki_dnn_reg, name=f'final_fc_reg{i+1}')(x_reg_fin)\n        #x_reg_fin = tf.keras.layers.BatchNormalization(name=f'final_bn_reg{i+1}')(x_reg_fin)\n        x_reg_fin = tf.keras.layers.Dropout(do_dnn_reg, name=f'final_do_reg{i+1}')(x_reg_fin)\n    output_coord = tf.keras.layers.Dense(2, activation=\"linear\", kernel_regularizer=reg_dnn_reg, name='reg')(x_reg_fin)\n\n    # define model\n    model = tf.keras.Model(inputs=[input_img, input_mod], outputs=[output_lab, output_coord], name='RSNA_Class')\n\n    # define optimizer/loss and compile model\n    lr_tune = 1e-3 #hp.Float(name='learning_rate', min_value=1e-4, max_value=1e-2, sampling='log', default=1e-3)\n    optimizer = tf.keras.optimizers.Adam(lr_tune)\n    loss_class = WeightedBinaryCrossentropy()\n    loss = {\"reg\": \"mean_squared_error\", \"class\": loss_class}\n    loss_weights = {\"reg\": 0.01, \"class\": 0.99} #{\"reg\": 0.01, \"class\": 0.99}\n    auc = tf.keras.metrics.AUC(multi_label=True, label_weights=label_weights, name='auc')\n    metrics = {\"reg\": [\"mae\"], \"class\": [auc]}\n\n    model.compile(optimizer=optimizer, loss=loss, loss_weights=loss_weights, metrics=metrics, run_eagerly=False)\n    return model\n\nif not SUBMISSIONING:\n    with strategy.scope():\n        model = build_network(kt.HyperParameters())\nelse:\n    # Load pre-trained model for submission\n    model = tf.keras.models.load_model('/kaggle/input/rsna-3xx/rsna_3_11_0.h5')\n    print('Model weights have been loaded!')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Explore model architecture\n\nmodel.summary(line_length=110)\n# tf.keras.utils.plot_model(model, to_file='model_architecture.png', show_shapes=True, show_dtype=False,\n#                           show_layer_names=True, show_layer_activations=True, show_trainable=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. Training","metadata":{}},{"cell_type":"code","source":"## Training parameters\n\nepochs = 100\nsteps_per_epoch = len(train_labels)//batch_size\nTUNING = False and not SUBMISSIONING\nTRAINING = True and not SUBMISSIONING\nFINETUNING = False and not SUBMISSIONING","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Tuner configurations\n\nif TUNING:\n    i_TunerTyp = 1 # Choose desired tuner type: {1: 'grid', 2: 'random', 3: 'hyper'}\n    TunerStr = {1: 'grid', 2: 'random', 3: 'hyper'}\n    \n    tuner_grid = kt.GridSearch(hypermodel=build_network, objective=kt.Objective(\"val_class_auc\", direction=\"max\"),\n                               max_trials=15, max_consecutive_failed_trials=1,\n                               overwrite=True, directory=\"tuner\", project_name=\"RSNA\", distribution_strategy = strategy)\n    \n    tuner_random = kt.RandomSearch(hypermodel=build_network, objective=kt.Objective(\"val_class_auc\", direction=\"max\"),\n                                   max_trials=10, executions_per_trial=1,\n                                   overwrite=True, directory=\"tuner\", project_name=\"RSNA\", distribution_strategy = strategy)\n    \n    tuner_hyper = kt.Hyperband(hypermodel=build_network, objective=kt.Objective(\"val_class_auc\", direction=\"max\"),\n                               max_epochs=60, factor=4, hyperband_iterations=1,\n                               overwrite=True, directory=\"tuner\", project_name=\"RSNA\", distribution_strategy = strategy)\n    \n    tuner = globals()[f'tuner_{TunerStr[i_TunerTyp]}']\n    tuner.search_space_summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Train or tune model\n\n# Callback functions\nlr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(factor=0.2, patience=5, verbose=1, monitor='val_class_auc', mode='max')\nearly_stopping_cb = tf.keras.callbacks.EarlyStopping(patience=10, verbose=1, monitor='val_class_auc', mode='max', restore_best_weights=True)\nlr_schedule = tf.keras.callbacks.LearningRateScheduler(lambda epoch: 1e-5 * 10**(epoch / 10)) # Find starting learning\n\n# Training\nif TRAINING or FINETUNING:\n    history = model.fit(train_ds, validation_data=val_ds, epochs=epochs, steps_per_epoch=steps_per_epoch,\n                        callbacks=[lr_scheduler, early_stopping_cb])\n\n# Tuning\nif TUNING:\n    tuner.search(train_ds, validation_data=val_ds, epochs=epochs, steps_per_epoch=steps_per_epoch,\n                 callbacks=[lr_scheduler, early_stopping_cb])\n    best_models = tuner.get_best_models(num_models=2)\n    model = best_models[0]\n    model.summary()\n    tuner.results_summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Save weights of model after training/tuning/finetuning\n\nif TRAINING or TUNING or FINETUNING:\n    model.save('rsna_3_11_0.h5', include_optimizer=False)\n    print('Model weights have been saved!')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. Evaluation","metadata":{}},{"cell_type":"code","source":"## Plot learning curves\n\nif TRAINING or FINETUNING:\n    history_fil = {key: history.history[key] for key in ['class_auc', 'val_class_auc']}\n    history_fil2 = {key: history.history[key] for key in ['class_loss', 'val_class_loss']}\n    history_fil3 = {key: history.history[key] for key in ['learning_rate']}\n    \n    pd.DataFrame(history_fil).plot()\n    plt.ylabel(\"AUC\")\n    plt.xlabel(\"epochs\")\n    pd.DataFrame(history_fil2).plot()\n    plt.ylabel(\"Loss\")\n    plt.xlabel(\"epochs\")\n    #plt.axis([10, len(history_fil2['val_loss']), 0, history_fil2['val_loss'][10]+0.1*history_fil2['val_loss'][10]])\n    pd.DataFrame(history_fil3).plot()\n    plt.ylabel(\"Learning rate\")\n    plt.xlabel(\"epochs\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Compare true and predicted values for the first 10 samples from validation set\n\nval_vis = val[['class']][:10].rename(columns={'class': 'class_true'})\nval_prob, val_coord = model.predict((val_images[:10], val_modalities[:10]), verbose=0)\nprob_vis = pd.DataFrame(val_prob, val_vis.index)\ntrue_coord_vis = pd.DataFrame(val_coordinates[:10], val_vis.index, columns=['x_true', 'y_true'])\nval_coord_vis = pd.DataFrame(val_coord, val_vis.index, columns=['x_pred', 'y_pred'])\npd.concat([val_vis, prob_vis, true_coord_vis, val_coord_vis], axis=1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. Submission","metadata":{}},{"cell_type":"code","source":"## Make prediction and run submission script\n\nID_COL = 'SeriesInstanceUID'\n\nLABEL_COLS = ['Left Infraclinoid Internal Carotid Artery', 'Right Infraclinoid Internal Carotid Artery',\n              'Left Supraclinoid Internal Carotid Artery', 'Right Supraclinoid Internal Carotid Artery',\n              'Left Middle Cerebral Artery', 'Right Middle Cerebral Artery', 'Anterior Communicating Artery',\n              'Left Anterior Cerebral Artery', 'Right Anterior Cerebral Artery', 'Left Posterior Communicating Artery',\n              'Right Posterior Communicating Artery', 'Basilar Tip', 'Other Posterior Circulation', 'Aneurysm Present']\n\n# All tags (other than PixelData and SeriesInstanceUID) that may be in a test set dcm file\nDICOM_TAG_ALLOWLIST = ['BitsAllocated', 'BitsStored', 'Columns', 'FrameOfReferenceUID', 'HighBit', 'ImageOrientationPatient',\n                       'ImagePositionPatient', 'InstanceNumber', 'Modality', 'PatientID', 'PhotometricInterpretation',\n                       'PixelRepresentation', 'PixelSpacing', 'PlanarConfiguration', 'RescaleIntercept', 'RescaleSlope',\n                       'RescaleType', 'Rows', 'SOPClassUID', 'SOPInstanceUID', 'SamplesPerPixel', 'SliceThickness',\n                       'SpacingBetweenSlices', 'StudyInstanceUID', 'TransferSyntaxUID']\n\ndef predict(series_path: str) -> pl.DataFrame | pd.DataFrame:\n    \"\"\"Make a prediction.\"\"\"\n\n    # Collect file paths of test series\n    series_id = os.path.basename(series_path)\n    all_filepaths = []\n    for root, _, files in os.walk(series_path):\n        for file in files:\n            if file.endswith('.dcm'):\n                all_filepaths.append(os.path.join(root, file))\n    all_filepaths.sort()\n    \n    # Preprocess test series data\n    image_list = []\n    mod_list = []\n    for image_path in all_filepaths:\n        dcm = pydicom.dcmread(image_path)\n        image = dcm.pixel_array #pydicom.pixels.pixel_array(dcm, index=iter_nr)\n        mod = encoder_mod.transform([[dcm.Modality]])\n        if len(image.shape) == 3: # Multiframe dcm\n            for frame in image:\n                image_resized, x_resized, y_resized = preprocess_images(frame, -1, -1, crop=True, augmentation=False)\n                image_list.append(tf.cast(image_resized, tf.float32))\n                mod_list.append(tf.cast(mod, tf.float32))\n        else: # Single frame dcm\n            image_resized, x_resized, y_resized = preprocess_images(image, -1, -1, crop=True, augmentation=False)\n            image_list.append(tf.cast(image_resized, tf.float32))\n            mod_list.append(tf.cast(mod, tf.float32))\n    test_images = tf.concat(image_list, axis=0)\n    test_mods = tf.concat(mod_list, axis=0)\n\n    # Make prediction\n    lab, coor = model.predict((test_images, test_mods), verbose=0)\n    prob_lab = np.max(lab, axis=0)\n    predictions_list = prob_lab.astype(dtype='float').tolist()\n\n    # Prepare submisstion output format\n    predictions = pl.DataFrame(data=[[series_id] + predictions_list],\n                               schema=[ID_COL, *LABEL_COLS], orient='row')\n\n    # ----------------------------- IMPORTANT ------------------------------\n    # You MUST have the following code in your `predict` function\n    # to prevent \"out of disk space\" errors. This is a temporary workaround\n    # as we implement improvements to our evaluation system.\n    shutil.rmtree('/kaggle/shared', ignore_errors=True)\n    # ----------------------------------------------------------------------\n    \n    return predictions.drop(ID_COL)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Run server\n\nif not TRAINING and not TUNING and not FINETUNING:\n    shutil.rmtree('/kaggle/shared', ignore_errors=True)\n    \n    inference_server = kaggle_evaluation.rsna_inference_server.RSNAInferenceServer(predict)\n    \n    if os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n        inference_server.serve()\n    else:\n        inference_server.run_local_gateway()\n        display(pl.read_parquet('/kaggle/working/submission.parquet'))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if TUNING:\n    shutil.rmtree('/kaggle/working/tuner')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. Experimental code (e.g. for debugging)","metadata":{}},{"cell_type":"code","source":"# ## Plot learning curves for definition of start leraning rate\n# lrs = 1e-5 * (10 ** (np.arange(len(history.history[\"loss\"])) / 10)) # Define the learning rate array\n# plt.figure(figsize=(10, 6)) # Set the figure size\n# plt.grid(True) # Set the grid\n# plt.semilogx(lrs, history.history[\"loss\"]) # Plot the loss in log scale\n# plt.tick_params('both', length=10, width=1, which='both') # Increase the tickmarks size\n# #plt.axis([1e-5, 1e-0, 0, 10]) # Set the plot boundaries","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ## Visualisation in 3D\n\n# def load_dicom_series(folder_path): # Function to load DICOM series (multiple slices)\n#     import os\n#     slices = []\n#     for file_name in sorted(os.listdir(folder_path)):\n#         if file_name.endswith(\".dcm\"):\n#             dicom_file = pydicom.dcmread(os.path.join(folder_path, file_name))\n#             NoF = int(dicom_file.NumberOfFrames)\n#     return dicom_file.pixel_array\n\n# def plot_3d(image_array, threshold=0.5):\n#     x, y, z = np.where(image_array > (image_array.max()-image_array.min())* threshold + image_array.min())\n#     fig = plt.figure(figsize=(10, 10))\n#     ax = fig.add_subplot(projection='3d')\n#     colors = image_array[x,y,z]\n#     ax.scatter3D(x, y, z, c=colors, vmin=colors.min(), vmax=colors.max(),\n#                cmap='gist_rainbow', alpha=0.02, marker='o', s=0.1)\n#     #ax.view_init(15, 15, 0)\n#     ax.set_xlabel('X-axis')\n#     ax.set_ylabel('Y-axis')\n#     ax.set_zlabel('Z-axis')\n#     plt.show()\n\n# folder_path = '/kaggle/input/rsna-intracranial-aneurysm-detection/series/1.2.826.0.1.3680043.8.498.10134365079002163886508836892471866754'\n# dicom_volume = load_dicom_series(folder_path)\n# plot_3d(dicom_volume, threshold=0.02)\n# x, y, z = np.where(dicom_volume > (dicom_volume.max()-dicom_volume.min())* 0.5 + dicom_volume.min())\n# dicom_volume[x, y, z].max()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}