├── .gitignore ├── 3D_Brain_Tumor_Segmentation_Attention_UNet.ipynb ├── LICENSE ├── README.md └── brain_seg_ui ├── .gitignore ├── Makefile ├── README.md ├── config.py ├── renovate.json ├── requirements.txt ├── segmentation ├── __init__.py ├── assets.py ├── auth.py ├── enums.py ├── forms.py ├── models.py ├── routes.py ├── static │ ├── dist │ │ ├── css │ │ │ ├── account.css │ │ │ └── dashboard.css │ │ ├── img │ │ │ ├── favicon.png │ │ │ ├── flasklogin@2x.png │ │ │ └── logo.png │ │ └── js │ │ │ └── main.min.js │ └── src │ │ ├── js │ │ └── main.js │ │ └── less │ │ ├── account.less │ │ ├── dashboard.less │ │ └── vars.less └── templates │ ├── home.html │ ├── layout.html │ ├── login.html │ ├── nav.html │ ├── patients.html │ ├── scans.html │ ├── signup.html │ └── upload_scans.html └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | brain_seg_ui/env/ 2 | .env 3 | .vscode 4 | brain_seg_ui/segmentation/attention_unet_85/ -------------------------------------------------------------------------------- /3D_Brain_Tumor_Segmentation_Attention_UNet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "TdEse3Kwq3JD" 7 | }, 8 | "source": [ 9 | "# Import Necessary Libraries" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "metadata": { 16 | "id": "WRKzuv_5owuz" 17 | }, 18 | "outputs": [], 19 | "source": [ 20 | "import numpy as np\n", 21 | "import nibabel as nib\n", 22 | "import glob\n", 23 | "from tensorflow.keras.utils import to_categorical # multiclass semantic segmentation, therefore the volumes to categorical\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "from tifffile import imsave\n", 26 | "from sklearn.preprocessing import MinMaxScaler #scale values\n", 27 | "import tensorflow as tf\n", 28 | "import random\n", 29 | "import os.path\n", 30 | "!pip install split-folders\n", 31 | "!pip3 install -U segmentation-models-3D\n", 32 | "import splitfolders\n", 33 | "!pip install -q -U keras-tuner" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": { 40 | "id": "vEtRg2vutWru" 41 | }, 42 | "outputs": [], 43 | "source": [ 44 | "# To always ensure that the GPU is available\n", 45 | "import tensorflow as tf\n", 46 | "device_name = tf.test.gpu_device_name()\n", 47 | "if device_name != '/device:GPU:0':\n", 48 | " raise SystemError('GPU device not found')\n", 49 | "print('Found GPU at: {}'.format(device_name))" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": { 55 | "id": "L5yBxROtvDAI" 56 | }, 57 | "source": [ 58 | "# Define the MinMax Scaler + Mount Drive to access Dataset\n", 59 | "\n", 60 | "* The MinMax scaler is necessary for transforming the scans' features to a range between 0 and 1" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": { 67 | "id": "sqMRiba8q-30" 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "scaler = MinMaxScaler()\n", 72 | "\n", 73 | "from google.colab import drive\n", 74 | "drive.mount('/content/drive')" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "metadata": { 80 | "id": "XH4_Z5f2sfxZ" 81 | }, 82 | "source": [ 83 | "# Load sample images and visualize\n", 84 | "\n" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": { 91 | "id": "SvfI9iTrrZuN" 92 | }, 93 | "outputs": [], 94 | "source": [ 95 | "DATASET_PATH = ''\n", 96 | "\n", 97 | "test_image_flair = nib.load(DATASET_PATH + 'flair.nii').get_fdata()\n", 98 | "print(test_image_flair[156][98][78])\n", 99 | "test_image_flair = scaler.fit_transform(test_image_flair.reshape(-1, test_image_flair.shape[-1])).reshape(test_image_flair.shape)\n", 100 | "print(test_image_flair[156][98][78])\n", 101 | "\n", 102 | "test_image_t1 = nib.load(DATASET_PATH + 't1.nii').get_fdata()\n", 103 | "test_image_t1 = scaler.fit_transform(test_image_t1.reshape(-1, test_image_t1.shape[-1])).reshape(test_image_t1.shape)\n", 104 | "\n", 105 | "test_image_t1ce = nib.load(DATASET_PATH + 't1ce.nii').get_fdata()\n", 106 | "test_image_t1ce = scaler.fit_transform(test_image_t1ce.reshape(-1, test_image_t1ce.shape[-1])).reshape(test_image_t1ce.shape)\n", 107 | "\n", 108 | "test_image_t2 = nib.load(DATASET_PATH + 't2.nii').get_fdata()\n", 109 | "test_image_t2 = scaler.fit_transform(test_image_t2.reshape(-1, test_image_t2.shape[-1])).reshape(test_image_t2.shape)\n", 110 | "\n", 111 | "test_mask = nib.load(DATASET_PATH + 'seg.nii').get_fdata()\n", 112 | "test_mask = test_mask.astype(np.uint8)\n", 113 | "\n", 114 | "print(np.unique(test_mask))\n", 115 | "# Reassign label value 4 to 3\n", 116 | "test_mask[test_mask==4] = 3\n", 117 | "print(np.unique(test_mask))" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": { 124 | "id": "aTkjA-mgwecE" 125 | }, 126 | "outputs": [], 127 | "source": [ 128 | "n_slice = random.randint(0, test_mask.shape[2])\n", 129 | "\n", 130 | "plt.figure(figsize=(12,8))\n", 131 | "plt.subplot(231)\n", 132 | "plt.imshow(test_image_flair[:, :, n_slice], cmap='gray')\n", 133 | "plt.title('Flair Scan')\n", 134 | "\n", 135 | "plt.subplot(232)\n", 136 | "plt.imshow(test_image_t1[:, :, n_slice], cmap='gray')\n", 137 | "plt.title('T1 Scan')\n", 138 | "\n", 139 | "plt.subplot(233)\n", 140 | "plt.imshow(test_image_t1ce[:, :, n_slice], cmap='gray')\n", 141 | "plt.title('T1ce Scan')\n", 142 | "\n", 143 | "plt.subplot(234)\n", 144 | "plt.imshow(test_image_t2[:, :, n_slice], cmap='gray')\n", 145 | "plt.title('T2 Scan')\n", 146 | "\n", 147 | "plt.subplot(235)\n", 148 | "plt.imshow(test_mask[:, :, n_slice])\n", 149 | "plt.title('Mask')\n", 150 | "\n", 151 | "plt.show()\n", 152 | "\n" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "metadata": { 158 | "id": "EORoZoj7yPfW" 159 | }, 160 | "source": [ 161 | "# Data Processing: Combining the volumes of scans to one + Cropping the scans and masks\n", 162 | "\n", 163 | "* The numpy array is reshaped to 2D, the dimensions the scaler can take as input, the array is transformed and then reshaped back to 3D\n", 164 | "* Result: the feature at position [156][98][78] of the loaded FLAIR scan numpy array is transformed from 1920.0 to 0.7683...\n", 165 | "* The three scans to be used are stacked together to forme a combined scan.\n", 166 | "* Result: A FLAIR scan, a T1CE scan and a T2 scan, all of dimensions 255 x 255 x 155 are stacked to form a combined scan of dimensions 255 x 255 x 155 x 3\n", 167 | "* The combined scan is cropped to 128 x 128 x 128 x 3\n", 168 | "* Label 4 in the dataset is reassigned to label 3 resulting to a continuous list of labels: 0, 1, 2, 3" 169 | ] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": null, 174 | "metadata": { 175 | "id": "-3u91yIqybn-" 176 | }, 177 | "outputs": [], 178 | "source": [ 179 | "combined_x = np.stack([test_image_flair, test_image_t1ce, test_image_t2], axis=3)\n", 180 | "combined_x = combined_x[56:184, 56:184, 13:141] #crop to 128 x 128 x 128 X 3\n", 181 | "\n", 182 | "test_mask = test_mask[56:184, 56:184, 13:141]\n", 183 | "n_slice = random.randint(0, test_mask.shape[1])\n", 184 | "plt.figure(figsize=(12, 8))\n", 185 | "\n", 186 | "plt.subplot(231)\n", 187 | "plt.imshow(combined_x[:, :, n_slice, 0], cmap='gray')\n", 188 | "plt.title('Flair Scan')\n", 189 | "\n", 190 | "plt.subplot(232)\n", 191 | "plt.imshow(combined_x[:, :, n_slice, 1], cmap='gray')\n", 192 | "plt.title('T1ce Scan')\n", 193 | "\n", 194 | "plt.subplot(233)\n", 195 | "plt.imshow(combined_x[:, :, n_slice, 2], cmap='gray')\n", 196 | "plt.title('T2 Scan')\n", 197 | "\n", 198 | "plt.subplot(234)\n", 199 | "plt.imshow(test_mask[:, :, n_slice])\n", 200 | "plt.title('Mask')\n", 201 | "\n", 202 | "plt.show()" 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": { 209 | "id": "T8r7sy4QND41" 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "from tensorflow.keras import backend as K\n", 214 | "\n", 215 | "print(K.int_shape(test_image_flair))\n", 216 | "\n", 217 | "print(K.int_shape(combined_x))" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": { 224 | "id": "WeD_PqCv6Vww" 225 | }, 226 | "outputs": [], 227 | "source": [ 228 | "flair_list = sorted(glob.glob(DATASET_PATH + '*/flair.nii'))\n", 229 | "t1_list = sorted(glob.glob(DATASET_PATH + '*/t1.nii'))\n", 230 | "t1ce_list = sorted(glob.glob(DATASET_PATH + '*/t1ce.nii'))\n", 231 | "t2_list = sorted(glob.glob(DATASET_PATH + '*/t2.nii'))\n", 232 | "mask_list = sorted(glob.glob(DATASET_PATH + '*/seg.nii'))\n", 233 | "\n", 234 | "\n", 235 | "for img in range(len(flair_list)):\n", 236 | " print('Now processing image and masks no: ', img)\n", 237 | "\n", 238 | " temp_image_flair = nib.load(flair_list[img]).get_fdata()\n", 239 | " temp_image_flair = scaler.fit_transform(temp_image_flair.reshape(-1, temp_image_flair.shape[-1])).reshape(temp_image_flair.shape)\n", 240 | "\n", 241 | " temp_image_t1 = nib.load(t1_list[img]).get_fdata()\n", 242 | " temp_image_t1 = scaler.fit_transform(temp_image_t1.reshape(-1, temp_image_t1.shape[-1])).reshape(temp_image_t1.shape)\n", 243 | "\n", 244 | " temp_image_t1ce = nib.load(t1ce_list[img]).get_fdata()\n", 245 | " temp_image_t1ce = scaler.fit_transform(temp_image_t1ce.reshape(-1, temp_image_t1ce.shape[-1])).reshape(temp_image_t1ce.shape)\n", 246 | "\n", 247 | " temp_image_t2 = nib.load(t2_list[img]).get_fdata()\n", 248 | " temp_image_t2 = scaler.fit_transform(temp_image_t2.reshape(-1, temp_image_t2.shape[-1])).reshape(temp_image_t2.shape)\n", 249 | "\n", 250 | " temp_mask = nib.load(mask_list[img]).get_fdata()\n", 251 | " temp_mask = temp_mask.astype(np.uint8)\n", 252 | " temp_mask[temp_mask == 4] = 3\n", 253 | "\n", 254 | " temp_combined_images = np.stack([temp_image_flair, temp_image_t1, temp_image_t1ce, temp_image_t2], axis = 3)\n", 255 | " temp_combined_images = temp_combined_images[56:184, 56:184, 13:141]\n", 256 | " temp_mask = temp_mask[56:184, 56:184, 13:141]\n", 257 | "\n", 258 | " val, counts = np.unique(temp_mask, return_counts=True)\n", 259 | "\n", 260 | " if(1 - (counts[0]/counts.sum())) > 0.01:\n", 261 | " temp_mask = to_categorical(temp_mask, num_classes=4)\n", 262 | " np.save(DATASET_PATH + 'final_dataset/scans/image_' + str(img) + '.npy', temp_combined_images)\n", 263 | " np.save(DATASET_PATH + 'final_dataset/masks/image_' + str(img) + '.npy', temp_mask)\n", 264 | " print(\"Saved\")\n", 265 | " else:\n", 266 | " print(\"Not saved\")" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": { 272 | "id": "-wICUx56ugDz" 273 | }, 274 | "source": [ 275 | "# Dataset Splitting: 60:20:20 for train, val and test" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": { 282 | "id": "Oi_g5D01HSnq" 283 | }, 284 | "outputs": [], 285 | "source": [ 286 | "input_folder = DATASET_PATH + 'final_dataset/'\n", 287 | "output_folder = DATASET_PATH + 'split_dataset/'\n", 288 | "splitfolders.ratio(input_folder, output=output_folder, seed=42, ratio=(.6, .2, .2), group_prefix=None)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": { 294 | "id": "RtaRf0B4kPkM" 295 | }, 296 | "source": [ 297 | "# Data Generator\n", 298 | "\n", 299 | "\n" 300 | ] 301 | }, 302 | { 303 | "cell_type": "code", 304 | "execution_count": null, 305 | "metadata": { 306 | "id": "UMfHysy2ixc8" 307 | }, 308 | "outputs": [], 309 | "source": [ 310 | "import os\n", 311 | "import numpy as np\n", 312 | "\n", 313 | "def load_img(img_dir, img_list):\n", 314 | " images=[]\n", 315 | " for i, image_name in enumerate(img_list):\n", 316 | " if(image_name.split('.')[1] == 'npy'):\n", 317 | " image = np.load(img_dir + image_name)\n", 318 | " images.append(image)\n", 319 | " images = np.array(images)\n", 320 | " return images\n", 321 | "\n", 322 | "def imageLoader(img_dir, img_list, mask_dir, mask_list, batch_size):\n", 323 | " L = len(img_list)\n", 324 | " # keras needs the generator infinite, so use while True\n", 325 | " while True:\n", 326 | " batch_start = 0\n", 327 | " batch_end = batch_size\n", 328 | "\n", 329 | " while batch_start < L:\n", 330 | " limit = min(batch_end, L)\n", 331 | " X = load_img(img_dir, img_list[batch_start:limit])\n", 332 | " Y = load_img(mask_dir, mask_list[batch_start:limit])\n", 333 | "\n", 334 | " yield(X, Y) # a tuple with two numpy arrays with batch_size samples\n", 335 | "\n", 336 | " batch_start += batch_size\n", 337 | " batch_end += batch_size\n", 338 | "\n", 339 | "\n", 340 | "# Test the generator\n", 341 | "TRAIN_DATASET_PATH = ''\n", 342 | "train_img_dir = TRAIN_DATASET_PATH + 'scans/'\n", 343 | "train_mask_dir = TRAIN_DATASET_PATH + 'masks/'\n", 344 | "\n", 345 | "train_img_list = os.listdir(train_img_dir)\n", 346 | "train_mask_list = os.listdir(train_mask_dir)\n", 347 | "\n", 348 | "batch_size = 2\n", 349 | "\n", 350 | "train_img_datagen = imageLoader(train_img_dir, train_img_list,\n", 351 | " train_mask_dir, train_mask_list, batch_size)\n", 352 | "\n", 353 | "# Verify generator - In python 3 next() is renamed as __next__()\n", 354 | "img, msk = train_img_datagen.__next__()\n", 355 | "\n", 356 | "img_num = random.randint(0, img.shape[0]-1)\n", 357 | "\n", 358 | "test_img = img[img_num]\n", 359 | "test_mask = msk[img_num]\n", 360 | "test_mask = np.argmax(test_mask, axis=3)\n", 361 | "\n", 362 | "n_slice = random.randint(0, test_mask.shape[2])\n", 363 | "plt.figure(figsize=(12,8))\n", 364 | "\n", 365 | "plt.subplot(221)\n", 366 | "plt.imshow(test_img[:, :, n_slice, 0], cmap='gray')\n", 367 | "plt.title('Flair Scan')\n", 368 | "\n", 369 | "plt.subplot(222)\n", 370 | "plt.imshow(test_img[:, :, n_slice, 1], cmap='gray')\n", 371 | "plt.title('T1ce Scan')\n", 372 | "\n", 373 | "plt.subplot(223)\n", 374 | "plt.imshow(test_img[:, :, n_slice, 2], cmap='gray')\n", 375 | "plt.title('T2 Scan')\n", 376 | "\n", 377 | "plt.subplot(224)\n", 378 | "plt.imshow(test_mask[:, :, n_slice])\n", 379 | "plt.title('Mask')\n", 380 | "\n", 381 | "plt.show()" 382 | ] 383 | }, 384 | { 385 | "cell_type": "markdown", 386 | "metadata": { 387 | "id": "ReTmFPr0QV17" 388 | }, 389 | "source": [ 390 | "# Define image generators for training, validation and testing" 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": null, 396 | "metadata": { 397 | "id": "HS9Dihs_QbqU" 398 | }, 399 | "outputs": [], 400 | "source": [ 401 | "DATASET_PATH = ''\n", 402 | "train_img_dir = DATASET_PATH + 'train/scans/'\n", 403 | "train_mask_dir = DATASET_PATH + 'train/masks/'\n", 404 | "\n", 405 | "val_img_dir = DATASET_PATH + 'val/scans/'\n", 406 | "val_mask_dir = DATASET_PATH + 'val/masks/'\n", 407 | "\n", 408 | "test_img_dir = DATASET_PATH + 'test/scans/'\n", 409 | "test_mask_dir = DATASET_PATH + 'test/masks/'\n", 410 | "\n", 411 | "train_img_list = os.listdir(train_img_dir)\n", 412 | "train_mask_list = os.listdir(train_mask_dir)\n", 413 | "\n", 414 | "val_img_list = os.listdir(val_img_dir)\n", 415 | "val_mask_list = os.listdir(val_mask_dir)\n", 416 | "\n", 417 | "test_img_list = os.listdir(test_img_dir)\n", 418 | "test_mask_list = os.listdir(test_mask_dir)\n", 419 | "\n", 420 | "batch_size = 2\n", 421 | "train_img_datagen = imageLoader(train_img_dir, train_img_list,\n", 422 | " train_mask_dir, train_mask_list, batch_size)\n", 423 | "\n", 424 | "val_img_datagen = imageLoader(val_img_dir, val_img_list,\n", 425 | " val_mask_dir, val_mask_list, batch_size)\n", 426 | "\n", 427 | "test_img_datagen = imageLoader(test_img_dir, test_img_list,\n", 428 | " test_mask_dir, test_mask_list, batch_size)\n" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": { 434 | "id": "dBKMHMn96Z3c" 435 | }, 436 | "source": [ 437 | "# Losses and metrics\n", 438 | "* These losses and metrics best handle the problem of class imbalance\n", 439 | "* Used: dice_coef as a metric, tversky_loss as a loss" 440 | ] 441 | }, 442 | { 443 | "cell_type": "code", 444 | "execution_count": null, 445 | "metadata": { 446 | "id": "pshixCsr6eyt" 447 | }, 448 | "outputs": [], 449 | "source": [ 450 | "import tensorflow.keras.backend as K\n", 451 | "\n", 452 | "\n", 453 | "def dice_coef(y_true, y_pred, smooth=1):\n", 454 | " y_true_f = K.flatten(y_true)\n", 455 | " y_pred_f = K.flatten(y_pred)\n", 456 | " intersection = K.sum(y_true_f * y_pred_f)\n", 457 | " return (2. * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) +\n", 458 | " smooth)\n", 459 | "\n", 460 | "\n", 461 | "def dice_coef_loss(y_true, y_pred):\n", 462 | " return 1 - dice_coef(y_true, y_pred)\n", 463 | "\n", 464 | "\n", 465 | "def tversky(y_true, y_pred, smooth=1, alpha=0.7):\n", 466 | " y_true_pos = K.flatten(y_true)\n", 467 | " y_pred_pos = K.flatten(y_pred)\n", 468 | " true_pos = K.sum(y_true_pos * y_pred_pos)\n", 469 | " false_neg = K.sum(y_true_pos * (1 - y_pred_pos))\n", 470 | " false_pos = K.sum((1 - y_true_pos) * y_pred_pos)\n", 471 | " return (true_pos + smooth) / (true_pos + alpha * false_neg +\n", 472 | " (1 - alpha) * false_pos + smooth)\n", 473 | "\n", 474 | "\n", 475 | "def tversky_loss(y_true, y_pred):\n", 476 | " return 1 - tversky(y_true, y_pred)\n", 477 | "\n", 478 | "\n", 479 | "def focal_tversky_loss(y_true, y_pred, gamma=0.75):\n", 480 | " tv = tversky(y_true, y_pred)\n", 481 | " return K.pow((1 - tv), gamma)" 482 | ] 483 | }, 484 | { 485 | "cell_type": "markdown", 486 | "metadata": { 487 | "id": "2o2WuIhaW5ff" 488 | }, 489 | "source": [ 490 | "# Define loss, metrics and optimizer to be used for training" 491 | ] 492 | }, 493 | { 494 | "cell_type": "code", 495 | "execution_count": null, 496 | "metadata": { 497 | "id": "WxiJ1eUQXJ4I" 498 | }, 499 | "outputs": [], 500 | "source": [ 501 | "from keras.models import Model\n", 502 | "from keras.layers import Input, Conv3D, MaxPooling3D, Activation, add, concatenate, Conv3DTranspose, BatchNormalization, Dropout, UpSampling3D, multiply\n", 503 | "from tensorflow.keras.optimizers import Adam\n", 504 | "from keras import layers\n", 505 | "\n", 506 | "kernel_initializer = 'he_uniform'\n", 507 | "\n", 508 | "import segmentation_models_3D as sm\n", 509 | "\n", 510 | "metrics = [dice_coef]\n", 511 | "\n", 512 | "LR = 0.0001\n", 513 | "optim = Adam(LR)\n", 514 | "\n", 515 | "steps_per_epoch = len(train_img_list) // batch_size\n", 516 | "val_steps_per_epoch = len(val_img_list) // batch_size" 517 | ] 518 | }, 519 | { 520 | "cell_type": "markdown", 521 | "metadata": { 522 | "id": "PR2Ugre0YP-v" 523 | }, 524 | "source": [ 525 | "# 3D UNet Model" 526 | ] 527 | }, 528 | { 529 | "cell_type": "code", 530 | "execution_count": null, 531 | "metadata": { 532 | "id": "N0VyhdjCYVuZ" 533 | }, 534 | "outputs": [], 535 | "source": [ 536 | "def UNet(IMG_HEIGHT, IMG_WIDTH, IMG_DEPTH, IMG_CHANNELS, num_classes):\n", 537 | " inputs = Input((IMG_HEIGHT, IMG_WIDTH, IMG_DEPTH, IMG_CHANNELS))\n", 538 | "\n", 539 | " # Downsampling\n", 540 | " c1 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(inputs)\n", 541 | " c1 = Dropout(0.1)(c1)\n", 542 | " c1 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c1)\n", 543 | " p1 = MaxPooling3D((2, 2, 2))(c1)\n", 544 | "\n", 545 | " c2 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(p1)\n", 546 | " c2 = Dropout(0.1)(c2)\n", 547 | " c2 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c2)\n", 548 | " p2 = MaxPooling3D((2, 2, 2))(c2)\n", 549 | "\n", 550 | " c3 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(p2)\n", 551 | " c3 = Dropout(0.2)(c3)\n", 552 | " c3 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c3)\n", 553 | " p3 = MaxPooling3D((2, 2, 2))(c3)\n", 554 | "\n", 555 | " c4 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(p3)\n", 556 | " c4 = Dropout(0.2)(c4)\n", 557 | " c4 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c4)\n", 558 | " p4 = MaxPooling3D((2, 2, 2))(c4)\n", 559 | "\n", 560 | " c5 = Conv3D(filters = 256, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(p4)\n", 561 | " c5 = Dropout(0.3)(c5)\n", 562 | " c5 = Conv3D(filters = 256, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c5)\n", 563 | " \n", 564 | " # Upsampling part\n", 565 | " u6 = Conv3DTranspose(128, (2, 2, 2), strides=(2, 2, 2), padding='same')(c5)\n", 566 | " u6 = concatenate([u6, c4])\n", 567 | " c6 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(u6)\n", 568 | " c6 = Dropout(0.2)(c6)\n", 569 | " c6 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c6) \n", 570 | " \n", 571 | " u7 = Conv3DTranspose(64, (2, 2, 2), strides=(2, 2, 2), padding='same')(c6)\n", 572 | " u7 = concatenate([u7, c3])\n", 573 | " c7 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(u7)\n", 574 | " c7 = Dropout(0.2)(c7)\n", 575 | " c7 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c7) \n", 576 | " \n", 577 | " u8 = Conv3DTranspose(32, (2, 2, 2), strides=(2, 2, 2), padding='same')(c7)\n", 578 | " u8 = concatenate([u8, c2])\n", 579 | " c8 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(u8)\n", 580 | " c8 = Dropout(0.1)(c8)\n", 581 | " c8 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c8) \n", 582 | "\n", 583 | " u9 = Conv3DTranspose(16, (2, 2, 2), strides=(2, 2, 2), padding='same')(c8)\n", 584 | " u9 = concatenate([u9, c1])\n", 585 | " c9 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(u9)\n", 586 | " c9 = Dropout(0.1)(c9)\n", 587 | " c9 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation='relu', kernel_initializer=kernel_initializer, padding='same')(c9) \n", 588 | "\n", 589 | " outputs = Conv3D(num_classes, (1, 1, 1), activation='softmax')(c9)\n", 590 | "\n", 591 | " model = Model(inputs=[inputs], outputs=[outputs])\n", 592 | " model.summary()\n", 593 | "\n", 594 | " return model" 595 | ] 596 | }, 597 | { 598 | "cell_type": "markdown", 599 | "metadata": { 600 | "id": "-Aw_Peb9iJYb" 601 | }, 602 | "source": [ 603 | "# Test the working of the 3D UNet model" 604 | ] 605 | }, 606 | { 607 | "cell_type": "code", 608 | "execution_count": null, 609 | "metadata": { 610 | "id": "fjdzCTisiMLI" 611 | }, 612 | "outputs": [], 613 | "source": [ 614 | "steps_per_epoch = len(train_img_list)//batch_size\n", 615 | "val_steps_per_epoch = len(val_img_list)//batch_size\n", 616 | "\n", 617 | "model = UNet(IMG_HEIGHT = 128,\n", 618 | " IMG_WIDTH = 128,\n", 619 | " IMG_DEPTH = 128,\n", 620 | " IMG_CHANNELS = 3,\n", 621 | " num_classes = 4)\n", 622 | "\n", 623 | "model.compile(optimizer = optim, loss = tversky_loss, metrics = metrics)\n", 624 | "\n", 625 | "print(model.summary)\n", 626 | "\n", 627 | "print(model.input_shape)\n", 628 | "print(model.output_shape)" 629 | ] 630 | }, 631 | { 632 | "cell_type": "markdown", 633 | "metadata": { 634 | "id": "e6Cvn6hWvars" 635 | }, 636 | "source": [ 637 | "# 3D Attention UNet Model" 638 | ] 639 | }, 640 | { 641 | "cell_type": "code", 642 | "execution_count": null, 643 | "metadata": { 644 | "id": "JBcFdz80v2mL" 645 | }, 646 | "outputs": [], 647 | "source": [ 648 | "from keras.layers.core.activation import Activation\n", 649 | "from tensorflow.keras import backend as K\n", 650 | "from keras.layers import LeakyReLU\n", 651 | "\n", 652 | "def repeat_elem(tensor, rep):\n", 653 | " # lambda function to repeat Repeats the elements of a tensor along an axis\n", 654 | " #by a factor of rep.\n", 655 | " # If tensor has shape (None, 128,128,3), lambda will return a tensor of shape \n", 656 | " #(None, 128,128,6), if specified axis=3 and rep=2.\n", 657 | "\n", 658 | " return layers.Lambda(lambda x, repnum: K.repeat_elements(x, repnum, axis=4),\n", 659 | " arguments={'repnum': rep})(tensor)\n", 660 | "\n", 661 | "def attention_block(x, gating, inter_shape):\n", 662 | " shape_x = K.int_shape(x)\n", 663 | " shape_g = K.int_shape(gating)\n", 664 | "\n", 665 | " # Getting the gating signal to the same number of filters as the inter_shape\n", 666 | " phi_g = Conv3D(filters=inter_shape, kernel_size=1, strides=1, padding='same')(gating)\n", 667 | "\n", 668 | " # Geting the x signal to the same shape as the gating signal\n", 669 | " theta_x = Conv3D(filters=inter_shape, kernel_size=3, strides=(\n", 670 | " shape_x[1] // shape_g[1],\n", 671 | " shape_x[2] // shape_g[2],\n", 672 | " shape_x[3] // shape_g[3]\n", 673 | " ), padding='same')(x)\n", 674 | " shape_theta_x = K.int_shape(theta_x)\n", 675 | "\n", 676 | " print(shape_theta_x, shape_g)\n", 677 | "\n", 678 | " # Elemet-wise addition of the gating and x signals\n", 679 | " xg_sum = add([phi_g, theta_x])\n", 680 | " xg_sum = Activation('relu')(xg_sum)\n", 681 | "\n", 682 | " # 1x1x1 convolution\n", 683 | " psi = Conv3D(filters=1, kernel_size=1, padding='same')(xg_sum)\n", 684 | " sigmoid_psi = Activation('sigmoid')(psi)\n", 685 | " shape_sigmoid = K.int_shape(sigmoid_psi)\n", 686 | "\n", 687 | " # Upsampling psi back to the original dimensions of x signal to enable \n", 688 | " # element-wise multiplication with the signal\n", 689 | "\n", 690 | " upsampled_sigmoid_psi = UpSampling3D(size=(\n", 691 | " shape_x[1] // shape_sigmoid[1], \n", 692 | " shape_x[2] // shape_sigmoid[2],\n", 693 | " shape_x[3] // shape_sigmoid[3]\n", 694 | " ))(sigmoid_psi)\n", 695 | "\n", 696 | " # Expand the filter axis to the number of filters in the original x signal\n", 697 | " upsampled_sigmoid_psi = repeat_elem(upsampled_sigmoid_psi, shape_x[4])\n", 698 | "\n", 699 | " # Element-wise multiplication of attention coefficients back onto original x signal\n", 700 | " attention_coeffs = multiply([upsampled_sigmoid_psi, x])\n", 701 | "\n", 702 | " # Final 1x1x1 convolution to consolidate attention signal to original x dimensions\n", 703 | " output = Conv3D(filters=shape_x[3], kernel_size=1, strides=1, padding='same')(attention_coeffs)\n", 704 | " output = BatchNormalization()(output)\n", 705 | " return output\n", 706 | "\n", 707 | "\n", 708 | "# Gating signal\n", 709 | "def gating_signal(input, output_size, batch_norm=False):\n", 710 | " # Resize the down layer feature map into the same dimensions as the up layer feature map using 1x1 conv\n", 711 | " # Return: the gating feature map with the same dimension of the up layer feature map\n", 712 | " x = Conv3D(output_size, (1, 1, 1), padding='same')(input)\n", 713 | " if batch_norm:\n", 714 | " x = BatchNormalization()(x)\n", 715 | " x = Activation('relu')(x)\n", 716 | " return x\n", 717 | "\n", 718 | "\n", 719 | "# Attention UNet\n", 720 | "def attention_unet(IMG_HEIGHT, IMG_WIDTH, IMG_DEPTH, IMG_CHANNELS, num_classes, batch_norm = True):\n", 721 | " inputs = Input((IMG_HEIGHT, IMG_WIDTH, IMG_DEPTH, IMG_CHANNELS))\n", 722 | " FILTER_NUM = 64 #\n", 723 | " FILTER_SIZE = 3 #\n", 724 | " UP_SAMPLING_SIZE = 2 # \n", 725 | "\n", 726 | " c1 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(inputs)\n", 727 | " c1 = Dropout(0.1)(c1)\n", 728 | " c1 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c1)\n", 729 | " p1 = MaxPooling3D((2, 2, 2))(c1)\n", 730 | "\n", 731 | " c2 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(p1)\n", 732 | " c2 = Dropout(0.1)(c2)\n", 733 | " c2 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c2)\n", 734 | " p2 = MaxPooling3D((2, 2, 2))(c2)\n", 735 | "\n", 736 | " c3 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(p2)\n", 737 | " c3 = Dropout(0.2)(c3)\n", 738 | " c3 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c3)\n", 739 | " p3 = MaxPooling3D((2, 2, 2))(c3)\n", 740 | "\n", 741 | " c4 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(p3)\n", 742 | " c4 = Dropout(0.2)(c4)\n", 743 | " c4 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c4)\n", 744 | " p4 = MaxPooling3D((2, 2, 2))(c4)\n", 745 | "\n", 746 | " c5 = Conv3D(filters = 256, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(p4)\n", 747 | " c5 = Dropout(0.3)(c5)\n", 748 | " c5 = Conv3D(filters = 256, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c5)\n", 749 | " \n", 750 | "\n", 751 | " gating_6 = gating_signal(c5, 128, batch_norm)\n", 752 | " att_6 = attention_block(c4, gating_6, 128)\n", 753 | " u6 = UpSampling3D((2, 2, 2), data_format='channels_last')(c5)\n", 754 | " u6 = concatenate([u6, att_6])\n", 755 | " c6 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(u6)\n", 756 | " c6 = Dropout(0.2)(c6)\n", 757 | " c6 = Conv3D(filters = 128, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c6) \n", 758 | " \n", 759 | " gating_7 = gating_signal(c6, 64, batch_norm)\n", 760 | " att_7 = attention_block(c3, gating_6, 64)\n", 761 | " u7 = UpSampling3D((2, 2, 2), data_format='channels_last')(c6)\n", 762 | " u7 = concatenate([u7, att_7])\n", 763 | " c7 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(u7)\n", 764 | " c7 = Dropout(0.2)(c7)\n", 765 | " c7 = Conv3D(filters = 64, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c7) \n", 766 | " \n", 767 | " gating_8 = gating_signal(c7, 64, batch_norm)\n", 768 | " att_8 = attention_block(c2, gating_6, 64)\n", 769 | " u8 = UpSampling3D((2, 2, 2), data_format='channels_last')(c7)\n", 770 | " u8 = concatenate([u8, att_8])\n", 771 | " c8 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(u8)\n", 772 | " c8 = Dropout(0.1)(c8)\n", 773 | " c8 = Conv3D(filters = 32, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c8) \n", 774 | "\n", 775 | " gating_9 = gating_signal(c8, 64, batch_norm)\n", 776 | " att_9 = attention_block(c1, gating_6, 64)\n", 777 | " u9 = UpSampling3D((2, 2, 2), data_format='channels_last')(c8)\n", 778 | " u9 = concatenate([u9, att_9])\n", 779 | " c9 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(u9)\n", 780 | " c9 = Dropout(0.1)(c9)\n", 781 | " c9 = Conv3D(filters = 16, kernel_size = 3, strides = 1, activation=LeakyReLU(alpha=0.1), kernel_initializer=kernel_initializer, padding='same')(c9) \n", 782 | "\n", 783 | " outputs = Conv3D(num_classes, (1, 1, 1))(c9)\n", 784 | " outputs = BatchNormalization()(outputs)\n", 785 | " outputs = Activation('softmax')(outputs)\n", 786 | "\n", 787 | " model = Model(inputs=[inputs], outputs=[outputs], name=\"Attention_UNet\")\n", 788 | " model.summary()\n", 789 | "\n", 790 | " return model" 791 | ] 792 | }, 793 | { 794 | "cell_type": "markdown", 795 | "metadata": { 796 | "id": "xndmsEwjVhn7" 797 | }, 798 | "source": [ 799 | "# Test the working of a 3D Attention UNet Model" 800 | ] 801 | }, 802 | { 803 | "cell_type": "code", 804 | "execution_count": null, 805 | "metadata": { 806 | "id": "pBNjxGbjVn9U" 807 | }, 808 | "outputs": [], 809 | "source": [ 810 | "steps_per_epoch = len(train_img_list)//batch_size\n", 811 | "val_steps_per_epoch = len(val_img_list)//batch_size\n", 812 | "\n", 813 | "model = attention_unet(IMG_HEIGHT = 128,\n", 814 | " IMG_WIDTH = 128,\n", 815 | " IMG_DEPTH = 128,\n", 816 | " IMG_CHANNELS = 3,\n", 817 | " num_classes = 4)\n", 818 | "\n", 819 | "model.compile(optimizer = optim, loss = tversky_loss, metrics = metrics)\n", 820 | "\n", 821 | "print(model.summary)\n", 822 | "\n", 823 | "print(model.input_shape)\n", 824 | "print(model.output_shape)" 825 | ] 826 | }, 827 | { 828 | "cell_type": "markdown", 829 | "metadata": { 830 | "id": "8qnlrlr1YXu4" 831 | }, 832 | "source": [ 833 | "# Fit the Model" 834 | ] 835 | }, 836 | { 837 | "cell_type": "code", 838 | "execution_count": null, 839 | "metadata": { 840 | "id": "UXmCjFvjYaSG" 841 | }, 842 | "outputs": [], 843 | "source": [ 844 | "import tensorflow.keras as keras\n", 845 | "from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau, CSVLogger, TerminateOnNaN\n", 846 | "\n", 847 | "checkpoint_path = ''\n", 848 | "log_path = ''\n", 849 | "\n", 850 | "callbacks = [\n", 851 | " EarlyStopping(monitor='val_loss', patience=4, verbose=1),\n", 852 | " ReduceLROnPlateau(factor=0.1,\n", 853 | " monitor='val_loss',\n", 854 | " patience=4,\n", 855 | " min_lr=0.0001,\n", 856 | " verbose=1,\n", 857 | " mode='min'),\n", 858 | " ModelCheckpoint(checkpoint_path,\n", 859 | " monitor='val_loss',\n", 860 | " mode='min',\n", 861 | " verbose=0,\n", 862 | " save_best_only=True),\n", 863 | " CSVLogger(log_path, separator=',', append=True),\n", 864 | " TerminateOnNaN()\n", 865 | "]\n", 866 | "\n", 867 | "history = model.fit(train_img_datagen,\n", 868 | " steps_per_epoch=steps_per_epoch,\n", 869 | " epochs=100,\n", 870 | " verbose=1,\n", 871 | " validation_data=val_img_datagen,\n", 872 | " validation_steps=val_steps_per_epoch,\n", 873 | " callbacks=callbacks\n", 874 | " )\n", 875 | "\n", 876 | "history_callback = np.save('', history.history)" 877 | ] 878 | }, 879 | { 880 | "cell_type": "markdown", 881 | "metadata": { 882 | "id": "pfcKmJv4jP2J" 883 | }, 884 | "source": [ 885 | "# Load Model for more training" 886 | ] 887 | }, 888 | { 889 | "cell_type": "code", 890 | "execution_count": null, 891 | "metadata": { 892 | "id": "7RXukeY_jiad" 893 | }, 894 | "outputs": [], 895 | "source": [ 896 | "import tensorflow.keras.models as load\n", 897 | "import keras\n", 898 | "model = load.load_model('', custom_objects={\n", 899 | " 'tversky_loss': tversky_loss,\n", 900 | " 'dice_coef': dice_coef\n", 901 | "})\n", 902 | "\n", 903 | "checkpoint_path = ''\n", 904 | "log_path = ''\n", 905 | "\n", 906 | "callbacks = [\n", 907 | " EarlyStopping(monitor='val_loss', patience=4, verbose=1),\n", 908 | " ReduceLROnPlateau(factor=0.1,\n", 909 | " monitor='val_loss',\n", 910 | " patience=4,\n", 911 | " min_lr=0.0001,\n", 912 | " verbose=1,\n", 913 | " mode='min'),\n", 914 | " ModelCheckpoint(checkpoint_path,\n", 915 | " monitor='val_loss',\n", 916 | " mode='min',\n", 917 | " verbose=0,\n", 918 | " save_best_only=True),\n", 919 | " CSVLogger(log_path, separator=',', append=True),\n", 920 | " TerminateOnNaN()\n", 921 | "]\n", 922 | "\n", 923 | "history = model.fit(train_img_datagen,\n", 924 | " steps_per_epoch=steps_per_epoch,\n", 925 | " epochs=100,\n", 926 | " verbose=1,\n", 927 | " validation_data=val_img_datagen,\n", 928 | " validation_steps=val_steps_per_epoch,\n", 929 | " callbacks=callbacks\n", 930 | " )\n", 931 | "\n", 932 | "history_callback = np.save('', history.history)" 933 | ] 934 | }, 935 | { 936 | "cell_type": "markdown", 937 | "metadata": { 938 | "id": "SPBUC1HIfqDt" 939 | }, 940 | "source": [ 941 | "# Plot the training and validation loss (tversky) and dice coefficient (metric) at each epoch" 942 | ] 943 | }, 944 | { 945 | "cell_type": "code", 946 | "execution_count": null, 947 | "metadata": { 948 | "id": "I7e4YkM5f1Jg" 949 | }, 950 | "outputs": [], 951 | "source": [ 952 | "history = np.load('',allow_pickle='TRUE').item()\n", 953 | "\n", 954 | "print(history)\n", 955 | "loss = history['loss']\n", 956 | "val_loss = history['val_loss']\n", 957 | "epochs = range(1, len(loss) + 1)\n", 958 | "plt.plot(epochs, loss, 'y', label='Training loss')\n", 959 | "plt.plot(epochs, val_loss, 'r', label='Validation loss')\n", 960 | "plt.title('Training and Validation Loss')\n", 961 | "plt.xlabel('Epochs')\n", 962 | "plt.ylabel('Loss')\n", 963 | "plt.legend()\n", 964 | "plt.show()\n", 965 | "\n", 966 | "acc = history['dice_coef']\n", 967 | "val_acc = history['val_dice_coef']\n", 968 | "\n", 969 | "plt.plot(epochs, acc, 'y', label='Training accuracy')\n", 970 | "plt.plot(epochs, val_acc, 'r', label='Validation accuracy')\n", 971 | "plt.title('Trainign and Validation Accuracy')\n", 972 | "plt.xlabel('Epochs')\n", 973 | "plt.ylabel('Accuracy')\n", 974 | "plt.legend()\n", 975 | "plt.show()" 976 | ] 977 | }, 978 | { 979 | "cell_type": "markdown", 980 | "metadata": { 981 | "id": "XV8kjMkemQ-W" 982 | }, 983 | "source": [ 984 | "# Model Evaluation" 985 | ] 986 | }, 987 | { 988 | "cell_type": "code", 989 | "execution_count": null, 990 | "metadata": { 991 | "id": "ChhYHB8PmTnK" 992 | }, 993 | "outputs": [], 994 | "source": [ 995 | "from tensorflow.keras.models import load_model\n", 996 | "my_model = load_model('', custom_objects={\n", 997 | " 'tversky_loss': tversky_loss,\n", 998 | " 'dice_coef': dice_coef},\n", 999 | " compile = True)\n", 1000 | "\n", 1001 | "# Verify IoU on a batch of images from the test dataset\n", 1002 | "batch_size = 8\n", 1003 | "test_img_datagen = imageLoader(val_img_dir, val_img_list,\n", 1004 | " val_mask_dir, val_mask_list, batch_size)\n", 1005 | "\n", 1006 | "test_image_batch, test_mask_batch = test_img_datagen.__next__()\n", 1007 | "\n", 1008 | "test_mask_batch_argmax = np.argmax(test_mask_batch, axis=4)\n", 1009 | "\n", 1010 | "results = my_model.evaluate(test_image_batch, test_mask_batch, batch_size=batch_size)\n", 1011 | "print(\"test acc, test loss:\", results)" 1012 | ] 1013 | }, 1014 | { 1015 | "cell_type": "markdown", 1016 | "metadata": { 1017 | "id": "xvEqiU6SqY2y" 1018 | }, 1019 | "source": [ 1020 | "# Predict on a test scan" 1021 | ] 1022 | }, 1023 | { 1024 | "cell_type": "code", 1025 | "execution_count": null, 1026 | "metadata": { 1027 | "id": "8-MUQpCiqcxd" 1028 | }, 1029 | "outputs": [], 1030 | "source": [ 1031 | "from tensorflow.keras.models import load_model\n", 1032 | "my_model = load_model('', compile=False)\n", 1033 | "\n", 1034 | "img_num = 53\n", 1035 | "test_scan = np.load('' + str(img_num) + '.npy')\n", 1036 | "\n", 1037 | "test_mask = np.load('' + str(img_num) + '.npy')\n", 1038 | "test_mask_argmax = np.argmax(test_mask, axis = 3)\n", 1039 | "\n", 1040 | "test_scan_input = np.expand_dims(test_scan, axis = 0)\n", 1041 | "test_prediction = my_model.predict(test_scan_input)\n", 1042 | "test_prediction_argmax = np.argmax(test_prediction, axis = 4)[0, :, :, :]" 1043 | ] 1044 | }, 1045 | { 1046 | "cell_type": "code", 1047 | "execution_count": null, 1048 | "metadata": { 1049 | "colab": { 1050 | "background_save": true 1051 | }, 1052 | "id": "65FmAMNhmX8E" 1053 | }, 1054 | "outputs": [], 1055 | "source": [ 1056 | "# n_slice = 55\n", 1057 | "n_slice = random.randint(0, test_mask_argmax.shape[2])\n", 1058 | "\n", 1059 | "plt.figure(figsize=(12,8))\n", 1060 | "plt.subplot(231)\n", 1061 | "plt.imshow(test_scan[:, :, n_slice, 1], cmap='gray')\n", 1062 | "plt.title('Testing Scan')\n", 1063 | "\n", 1064 | "plt.subplot(232)\n", 1065 | "plt.imshow(test_mask_argmax[:, :, n_slice])\n", 1066 | "plt.title('Testing Label')\n", 1067 | "\n", 1068 | "plt.subplot(235)\n", 1069 | "plt.imshow(test_prediction_argmax[:, :, n_slice])\n", 1070 | "plt.title('Prediction on test image')\n", 1071 | "\n", 1072 | "plt.show()" 1073 | ] 1074 | } 1075 | ], 1076 | "metadata": { 1077 | "accelerator": "GPU", 1078 | "colab": { 1079 | "collapsed_sections": [ 1080 | "TdEse3Kwq3JD", 1081 | "L5yBxROtvDAI", 1082 | "EORoZoj7yPfW", 1083 | "-wICUx56ugDz", 1084 | "nq3p80zN2ew2", 1085 | "dBKMHMn96Z3c", 1086 | "PR2Ugre0YP-v", 1087 | "-Aw_Peb9iJYb", 1088 | "e6Cvn6hWvars", 1089 | "xndmsEwjVhn7", 1090 | "pfcKmJv4jP2J" 1091 | ], 1092 | "provenance": [] 1093 | }, 1094 | "gpuClass": "standard", 1095 | "kernelspec": { 1096 | "display_name": "Python 3", 1097 | "name": "python3" 1098 | }, 1099 | "language_info": { 1100 | "name": "python" 1101 | } 1102 | }, 1103 | "nbformat": 4, 1104 | "nbformat_minor": 0 1105 | } 1106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Maryann Gitonga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brain-Tumor-Segmentation-Using-3D-Attention-Based-UNet 2 | A 3D Attention-based UNet for segmenting Multimodal Brain Tumors 3 | -------------------------------------------------------------------------------- /brain_seg_ui/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | env/ 3 | .DS_Store 4 | __pycache__ 5 | .ropeproject 6 | .idea 7 | .webassets-cache 8 | 9 | # Scans directories 10 | segmentation/static/predicted 11 | segmentation/static/scans 12 | 13 | # trained model 14 | segmentation/trial_4attention_unet_3d 15 | 16 | # Byte-compiled / optimized / DLL files 17 | __pycache__/ 18 | *.py[cod] 19 | *$py.class 20 | 21 | # C extensions 22 | *.so 23 | 24 | # Distribution / packaging 25 | .Python 26 | build/ 27 | develop-eggs/ 28 | downloads/ 29 | eggs/ 30 | .eggs/ 31 | lib/ 32 | lib64/ 33 | parts/ 34 | sdist/ 35 | var/ 36 | wheels/ 37 | pip-wheel-metadata/ 38 | share/python-wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | MANIFEST 43 | 44 | # PyInstaller 45 | # Usually these files are written by a python script from a template 46 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 47 | *.manifest 48 | *.spec 49 | 50 | # Installer logs 51 | pip-log.txt 52 | pip-delete-this-directory.txt 53 | 54 | # Unit test / coverage reports 55 | htmlcov/ 56 | .tox/ 57 | .nox/ 58 | .coverage 59 | .coverage.* 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | *.cover 64 | *.py,cover 65 | .hypothesis/ 66 | .pytest_cache/ 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | local_settings.py 75 | db.sqlite3 76 | db.sqlite3-journal 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # IPython 95 | profile_default/ 96 | ipython_config.py 97 | 98 | # pyenv 99 | .python-version 100 | 101 | # pipenv 102 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 | # install all needed dependencies. 106 | #Pipfile.lock 107 | 108 | # celery beat schedule file 109 | celerybeat-schedule 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | -------------------------------------------------------------------------------- /brain_seg_ui/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := $(shell basename $CURDIR) 2 | VIRTUAL_ENVIRONMENT := $(CURDIR)/.venv 3 | LOCAL_PYTHON := $(VIRTUAL_ENVIRONMENT)/bin/python3 4 | 5 | define HELP 6 | Manage $(PROJECT_NAME). Usage: 7 | 8 | make run - Run $(PROJECT_NAME). 9 | make install - Create virtual env, install dependencies, and run project. 10 | make deploy - Install and run script by running `make install` and `make run` in succession. 11 | make update - Update pip dependencies via Poetry and export output to requirements.txt. 12 | make format - Format code with Pythons `Black` library. 13 | make lint - Check code formatting with `flake8`. 14 | make clean - Remove cached files and lock files. 15 | 16 | endef 17 | export HELP 18 | 19 | 20 | .PHONY: run install deploy update format lint clean help 21 | 22 | requirements: .requirements.txt 23 | env: ./.env/Scripts/activate 24 | 25 | 26 | .requirements.txt: requirements.txt 27 | $(shell . .env/Scripts/activate && pip install -r requirements.txt) 28 | 29 | 30 | all help: 31 | @echo "$$HELP" 32 | 33 | 34 | .PHONY: run 35 | run: env 36 | flask run 37 | 38 | 39 | .PHONY: install 40 | install: 41 | if [ ! -d "./.venv" ]; then python3 -m venv $(VIRTUAL_ENVIRONMENT); fi 42 | . .venv/bin/activate 43 | $(LOCAL_PYTHON) -m pip install --upgrade pip setuptools wheel 44 | $(LOCAL_PYTHON) -m pip install -r requirements.txt 45 | 46 | 47 | .PHONY: deploy 48 | deploy: 49 | make install 50 | make run 51 | 52 | 53 | .PHONY: update 54 | update: 55 | if [ ! -d "./.venv" ]; then python3 -m venv $(VIRTUAL_ENVIRONMENT); fi 56 | .venv/bin/python3 -m pip install --upgrade pip setuptools wheel 57 | poetry update 58 | poetry export -f requirements.txt --output requirements.txt --without-hashes 59 | 60 | 61 | .PHONY: format 62 | format: env 63 | isort --multi-line=3 . 64 | black . 65 | 66 | 67 | .PHONY: lint 68 | lint: 69 | flake8 . --count \ 70 | --select=E9,F63,F7,F82 \ 71 | --exclude .git,.github,__pycache__,.pytest_cache,.venv,logs,creds,.venv,docs,logs \ 72 | --show-source \ 73 | --statistics 74 | 75 | 76 | .PHONY: clean 77 | clean: 78 | find . -name '*.pyc' -delete 79 | find . -name '__pycache__' -delete 80 | find . -name 'poetry.lock' -delete 81 | find . -name '*.log' -delete 82 | find . -name '.DS_Store' -delete 83 | find . -wholename 'logs/*.json' -delete 84 | find . -wholename '.pytest_cache' -delete 85 | find . -wholename '**/.pytest_cache' -delete 86 | find . -wholename './logs/*.json' -delete 87 | find . -wholename './logs' -delete 88 | find . -wholename '*.html' -delete 89 | find . -wholename '**/.webassets-cache' -delete 90 | -------------------------------------------------------------------------------- /brain_seg_ui/README.md: -------------------------------------------------------------------------------- 1 | # brain_seg_ui 2 | 3 | A Flask ui for testing the 3D Attention-based UNet Model for segmenting Multimodal Brain Tumors 4 | -------------------------------------------------------------------------------- /brain_seg_ui/config.py: -------------------------------------------------------------------------------- 1 | from os import environ, path 2 | from dotenv import load_dotenv 3 | 4 | basedir = path.abspath(path.dirname(__file__)) 5 | load_dotenv(path.join(basedir, '.env')) 6 | 7 | class Config: 8 | """"Set Flask Configuration from environment variables""" 9 | FLASK_APP = environ.get("FLASK_APP") 10 | FLASK_ENV = environ.get("FLASK_ENV") 11 | SECRET_KEY = environ.get("SECRET_KEY") 12 | 13 | # Static Assets 14 | STATIC_FOLDER = 'static' 15 | TEMPLATES_FOLDER = 'templates' 16 | COMPRESSOR_DEBUG = environ.get('COMPRESSOR_DEBUG') 17 | 18 | # # Flask-Postgres 19 | # HOST=environ.get('HOST'), 20 | # DATABASE=environ.get('DB_NAME'), 21 | # USER=environ.get('DB_USERNAME'), 22 | # PASSWORD=environ.get('DB_PASSWORD') 23 | 24 | # Flask-SQLAlchemy 25 | SQLALCHEMY_DATABASE_URI = environ.get('SQLALCHEMY_DATABASE_URI') 26 | SQLALCHEMY_ECHO = False 27 | SQLALCHEMY_TRACK_MODIFICATIONS = False 28 | 29 | # Flask-Assets 30 | LESS_BIN = environ.get("LESS_BIN") 31 | ASSETS_DEBUG = environ.get("ASSETS_DEBUG") 32 | LESS_RUN_IN_DEBUG = environ.get("LESS_RUN_IN_DEBUG") -------------------------------------------------------------------------------- /brain_seg_ui/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /brain_seg_ui/requirements.txt: -------------------------------------------------------------------------------- 1 | aadict==0.2.3 2 | absl-py==1.3.0 3 | asset==0.6.13 4 | astunparse==1.6.3 5 | cachetools==5.2.0 6 | certifi==2022.9.24 7 | charset-normalizer==2.1.1 8 | click==8.1.2 9 | colorama==0.4.4 10 | contourpy==1.0.6 11 | cycler==0.11.0 12 | dnspython==2.2.1 13 | email-validator==1.1.3 14 | Flask==2.2.2 15 | Flask-Assets==2.0 16 | Flask-Login==0.6.2 17 | Flask-SQLAlchemy==2.5.1 18 | Flask-WTF==1.0.1 19 | flatbuffers==22.10.26 20 | fonttools==4.38.0 21 | gast==0.4.0 22 | globre==0.1.5 23 | google-auth==2.14.1 24 | google-auth-oauthlib==0.4.6 25 | google-pasta==0.2.0 26 | greenlet==1.1.2 27 | grpcio==1.50.0 28 | h5py==3.7.0 29 | idna==3.3 30 | importlib-metadata==4.11.3 31 | itsdangerous==2.1.2 32 | Jinja2==3.1.1 33 | joblib==1.2.0 34 | keras==2.10.0 35 | Keras-Preprocessing==1.1.2 36 | kiwisolver==1.4.4 37 | lessc==0.1.3 38 | libclang==14.0.6 39 | Markdown==3.4.1 40 | MarkupSafe==2.1.1 41 | matplotlib==3.6.2 42 | nibabel==4.0.2 43 | numpy==1.23.4 44 | oauthlib==3.2.2 45 | opt-einsum==3.3.0 46 | packaging==21.3 47 | Pillow==9.1.0 48 | protobuf==3.19.6 49 | psycopg2==2.9.5 50 | pyasn1==0.4.8 51 | pyasn1-modules==0.2.8 52 | pyparsing==3.0.9 53 | python-dateutil==2.8.2 54 | python-dotenv==0.20.0 55 | requests==2.28.1 56 | requests-oauthlib==1.3.1 57 | rsa==4.9 58 | scikit-learn==1.1.3 59 | scipy==1.9.3 60 | six==1.16.0 61 | SQLAlchemy==1.4.35 62 | tensorboard==2.10.1 63 | tensorboard-data-server==0.6.1 64 | tensorboard-plugin-wit==1.8.1 65 | tensorflow==2.10.0 66 | tensorflow-estimator==2.10.0 67 | tensorflow-io-gcs-filesystem==0.27.0 68 | termcolor==2.1.0 69 | threadpoolctl==3.1.0 70 | typing_extensions==4.4.0 71 | urllib3==1.26.12 72 | webassets==2.0 73 | Werkzeug==2.2.2 74 | wrapt==1.14.1 75 | WTForms==3.0.1 76 | zipp==3.8.0 77 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialize app.""" 2 | from flask import Flask 3 | from flask_login import LoginManager 4 | from flask_sqlalchemy import SQLAlchemy 5 | 6 | db = SQLAlchemy() 7 | login_manager = LoginManager() 8 | 9 | 10 | def create_app(): 11 | """Construct the core app object.""" 12 | app = Flask(__name__, instance_relative_config=False) 13 | app.config.from_object("config.Config") 14 | 15 | # Initialize Plugins 16 | db.init_app(app) 17 | login_manager.init_app(app) 18 | 19 | with app.app_context(): 20 | from . import auth, routes 21 | from .assets import compile_static_assets 22 | 23 | # Register Blueprints 24 | app.register_blueprint(routes.main_bp) 25 | app.register_blueprint(auth.auth_bp) 26 | 27 | # Create Database Models 28 | db.create_all() 29 | 30 | # Compile static assets 31 | if app.config["FLASK_ENV"] == "development": 32 | compile_static_assets(app) 33 | 34 | return app 35 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/assets.py: -------------------------------------------------------------------------------- 1 | """Create and bundle CSS and JS files.""" 2 | from flask_assets import Bundle, Environment 3 | 4 | 5 | def compile_static_assets(app): 6 | """Configure static asset bundles.""" 7 | assets = Environment(app) 8 | Environment.auto_build = True 9 | Environment.debug = False 10 | # Stylesheets Bundles 11 | account_less_bundle = Bundle( 12 | "src/less/account.less", 13 | filters="less,cssmin", 14 | output="dist/css/account.css", 15 | extra={"rel": "stylesheet/less"}, 16 | ) 17 | dashboard_less_bundle = Bundle( 18 | "src/less/dashboard.less", 19 | filters="less,cssmin", 20 | output="dist/css/dashboard.css", 21 | extra={"rel": "stylesheet/less"}, 22 | ) 23 | # JavaScript Bundle 24 | js_bundle = Bundle("src/js/main.js", filters="jsmin", output="dist/js/main.min.js") 25 | # Register assets 26 | assets.register("account_less_bundle", account_less_bundle) 27 | assets.register("dashboard_less_bundle", dashboard_less_bundle) 28 | assets.register("js_all", js_bundle) 29 | # Build assets 30 | account_less_bundle.build() 31 | dashboard_less_bundle.build() 32 | js_bundle.build() 33 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/auth.py: -------------------------------------------------------------------------------- 1 | from flask import ( 2 | Blueprint, 3 | render_template, 4 | flash, 5 | request, 6 | redirect, 7 | url_for 8 | ) 9 | from .forms import SignupForm, LoginForm 10 | from .enums import Roles 11 | from flask_login import ( 12 | current_user, 13 | login_user 14 | ) 15 | 16 | from .models import db, User 17 | from . import login_manager 18 | 19 | auth_bp = Blueprint( 20 | 'auth_bp', __name__, 21 | template_folder='templates', 22 | static_folder='static' 23 | ) 24 | 25 | @auth_bp.route('/login', methods=['GET', 'POST']) 26 | def login(): 27 | if current_user.is_authenticated: 28 | return redirect(url_for('main_bp.home')) 29 | 30 | form = LoginForm() 31 | if form.validate_on_submit(): 32 | user = User.query.filter_by(medical_id = form.medical_id.data).first() 33 | # if user.role != Roles.neurologist.value or user.role != Roles.sonographer.value: 34 | # return redirect(url_for('auth_bp.login')) 35 | 36 | if user and user.check_password(password = form.password.data): 37 | login_user(user) 38 | next_page = request.args.get("next") 39 | return redirect(next_page or url_for('main_bp.upload_scans')) 40 | 41 | flash('Invalid medical ID or password.') 42 | return redirect(url_for('auth_bp.login')) 43 | 44 | return render_template( 45 | 'login.html', 46 | form = form, 47 | title = 'Log In.', 48 | template = 'login-page', 49 | ) 50 | 51 | @auth_bp.route('/signup', methods=['GET', 'POST']) 52 | def signup(): 53 | form = SignupForm() 54 | if form.validate_on_submit(): 55 | existing_user = User.query.filter_by(medical_id = form.medical_id.data).first() 56 | 57 | if existing_user is None: 58 | user = User( 59 | medical_id = form.medical_id.data, 60 | first_name = form.first_name.data, 61 | last_name = form.last_name.data, 62 | phone_no = form.phone_no.data, 63 | email = form.email.data, 64 | role = Roles[form.role.data], 65 | ) 66 | 67 | user.set_password(form.password.data) 68 | db.session.add(user) 69 | db.session.commit() 70 | print('User: ', user.medical_id) 71 | login_user(user) 72 | return redirect(url_for('main_bp.home')) 73 | 74 | flash('A user already exists with that email address') 75 | 76 | return render_template( 77 | 'signup.html', 78 | title = 'Create an Account', 79 | form = form, 80 | template = 'signup-page', 81 | ) 82 | 83 | 84 | @login_manager.user_loader 85 | def load_user(user_id): 86 | """Check if user is logged-in upon page load.""" 87 | if user_id is not None: 88 | return User.query.get(user_id) 89 | return None 90 | 91 | 92 | @login_manager.unauthorized_handler 93 | def unauthorized(): 94 | """Redirect unauthorized users to Login page.""" 95 | flash("You must be logged in to view that page.") 96 | return redirect(url_for("auth_bp.login")) 97 | 98 | 99 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/enums.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | class Roles(enum.Enum): 4 | neurologist = 'neurologist' 5 | sonographer = 'sonographer' 6 | patient = 'patient' 7 | 8 | 9 | class ScanTypes(enum.Enum): 10 | combined = 'combined' 11 | predicted = 'predicted' -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, IntegerField, PasswordField, SelectField, FileField, SubmitField 3 | from wtforms.validators import ( 4 | DataRequired, 5 | Email, 6 | EqualTo, 7 | Length, 8 | Optional 9 | ) 10 | 11 | from .models import db, User 12 | 13 | class SignupForm(FlaskForm): 14 | 15 | medical_id = IntegerField( 16 | 'Medical ID', 17 | validators = [DataRequired()] 18 | ) 19 | 20 | first_name = StringField( 21 | 'First Name', 22 | validators = [DataRequired()] 23 | ) 24 | 25 | last_name = StringField( 26 | 'Last Name', 27 | validators = [DataRequired()] 28 | ) 29 | 30 | email = StringField( 31 | 'Email', 32 | validators=[ 33 | Length(min=6), 34 | Email(message='Enter a valid email.'), 35 | DataRequired() 36 | ] 37 | ) 38 | 39 | phone_no = StringField( 40 | 'Phone Number', 41 | ) 42 | 43 | role = SelectField( 44 | 'Role', 45 | validators=[ DataRequired()], 46 | choices=[('neurologist', 'neurologist'), ('sonographer', 'sonographer')] 47 | ) 48 | 49 | password = PasswordField( 50 | 'Password', 51 | validators=[ 52 | DataRequired(), 53 | Length(min=8, message='Select a stronger password') 54 | ] 55 | ) 56 | 57 | confirm = PasswordField( 58 | 'Confirm your Password', 59 | validators=[ 60 | DataRequired(), 61 | EqualTo('password', message='Passwords must match') 62 | ] 63 | ) 64 | 65 | submit = SubmitField('Register') 66 | 67 | 68 | class LoginForm(FlaskForm): 69 | 70 | medical_id = IntegerField( 71 | 'Medical ID', 72 | validators = [DataRequired()] 73 | ) 74 | 75 | password = PasswordField( 76 | 'Password', 77 | validators=[DataRequired()] 78 | ) 79 | 80 | submit = SubmitField('Log In') 81 | 82 | class UploadScansForm(FlaskForm): 83 | def getPatients(): 84 | patients_list = list() 85 | patients = db.session.query(User).filter(User.role == None) 86 | 87 | for patient in patients: 88 | patients_list.append(( 89 | patient.id, 90 | str(patient.medical_id) + ' - ' + patient.first_name + ' ' + patient.last_name 91 | )) 92 | 93 | return patients_list 94 | 95 | patient = SelectField( 96 | 'Patient', 97 | validators=[DataRequired()], 98 | choices=getPatients() 99 | ) 100 | 101 | flair_scan = FileField( 102 | 'Flair Scan', 103 | validators=[DataRequired()] 104 | ) 105 | 106 | t1ce_scan = FileField( 107 | 'T1CE Scan', 108 | validators=[DataRequired()] 109 | ) 110 | 111 | t2_scan = FileField( 112 | 'T2 Scan', 113 | validators=[DataRequired()] 114 | ) 115 | 116 | submit = SubmitField('Segment Tumor') 117 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from flask_login import UserMixin 3 | # import sqlalchemy as db 4 | from . import db 5 | from .enums import Roles, ScanTypes 6 | from werkzeug.security import generate_password_hash, check_password_hash 7 | 8 | class User(UserMixin, db.Model): 9 | 10 | __tablename__ = 'users' 11 | id = db.Column( 12 | db.Integer, 13 | nullable = False, 14 | primary_key = True 15 | ) 16 | 17 | medical_id = db.Column( 18 | db.Integer, 19 | nullable = False, 20 | unique = True 21 | ) 22 | 23 | first_name = db.Column( 24 | db.String(100), 25 | nullable = False, 26 | unique = False 27 | ) 28 | 29 | last_name = db.Column( 30 | db.String(100), 31 | nullable = False, 32 | unique = False 33 | ) 34 | 35 | phone_no = db.Column( 36 | db.String(14), 37 | unique = True, 38 | nullable = True 39 | ) 40 | 41 | email = db.Column( 42 | db.String(40), 43 | unique = True, 44 | nullable = False 45 | ) 46 | 47 | role = db.Column( 48 | db.Enum(Roles), 49 | nullable = True 50 | ) 51 | 52 | password = db.Column( 53 | db.String(200), 54 | unique = True, 55 | nullable = False 56 | ) 57 | 58 | scans = db.relationship("Scan", backref='users', lazy = True) 59 | 60 | created_on = db.Column( 61 | db.DateTime, 62 | index = False, 63 | unique = False, 64 | nullable = True 65 | ) 66 | 67 | 68 | def set_password(self, password): 69 | self.password = generate_password_hash(password, method='sha256') 70 | 71 | def check_password(self, password): 72 | return check_password_hash(self.password, password) 73 | 74 | def __repr__(self) -> str: 75 | return ''.format(self.first_name + self.last_name) 76 | 77 | 78 | class Scan(db.Model): 79 | 80 | __tablename__ = 'scans' 81 | 82 | id = db.Column( 83 | db.Integer, 84 | nullable = False, 85 | primary_key = True 86 | ) 87 | 88 | scan_file = db.Column( 89 | db.String(100), 90 | nullable = False, 91 | unique = False 92 | ) 93 | 94 | scan_type = db.Column( 95 | db.Enum(ScanTypes), 96 | nullable = False 97 | ) 98 | 99 | created_on = db.Column( 100 | db.DateTime, 101 | index = False, 102 | unique = False, 103 | default = datetime.now() 104 | ) 105 | 106 | patient_id = db.Column(db.Integer, db.ForeignKey('users.id')) -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/routes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | from flask import Blueprint, redirect, render_template, url_for, request 4 | from flask_login import current_user, login_required, logout_user 5 | from .enums import Roles, ScanTypes 6 | from .forms import UploadScansForm 7 | from sklearn.preprocessing import MinMaxScaler 8 | from werkzeug.utils import secure_filename 9 | import numpy as np 10 | import nibabel as nib 11 | import tensorflow as tf 12 | import matplotlib.pyplot as plt 13 | from .models import db, User, Scan 14 | from os.path import join, dirname, realpath 15 | 16 | SCANS_PATH = join(dirname(realpath(__file__)), 'static/scans/') 17 | PREDICTED_PATH = join(dirname(realpath(__file__)), 'static/predicted/') 18 | MODEL_PATH = join(dirname(realpath(__file__)), './trial_4attention_unet_3d') 19 | 20 | # Blueprint Configuration 21 | main_bp = Blueprint( 22 | "main_bp", __name__, template_folder="templates", static_folder="static" 23 | ) 24 | 25 | 26 | @main_bp.route("/", methods=["GET"]) 27 | @login_required 28 | def home(): 29 | return render_template( 30 | "home.html", 31 | title="Home - Brain Tumor Segmentation", 32 | template="home-template", 33 | current_user=current_user, 34 | neur = Roles.neurologist, 35 | ) 36 | 37 | 38 | @main_bp.route('/seed_patients', methods=['GET']) 39 | @login_required 40 | def seed_patients(): 41 | patients = list() 42 | user_1 = User( 43 | medical_id = 100, 44 | first_name = 'Jane', 45 | last_name = 'Doe', 46 | email = 'jane.doe@gmail.com', 47 | password = '12345678' 48 | ) 49 | patients.append(user_1) 50 | 51 | user_2 = User( 52 | medical_id = 200, 53 | first_name = 'John', 54 | last_name = 'Doe', 55 | email = 'john.doe@gmail.com', 56 | password = '12345678' 57 | ) 58 | patients.append(user_2) 59 | 60 | user_3 = User( 61 | medical_id = 300, 62 | first_name = 'Jeanne', 63 | last_name = 'Eliah', 64 | email = 'jeanne.eliah@gmail.com', 65 | password = '12345678' 66 | ) 67 | patients.append(user_3) 68 | 69 | for user in patients: 70 | user.set_password(user.password) 71 | db.session.add(user) 72 | db.session.commit() 73 | 74 | return redirect(url_for('main_bp.home')) 75 | 76 | 77 | @main_bp.route('/upload_scans', methods=['GET', 'POST']) 78 | @login_required 79 | def upload_scans(): 80 | form = UploadScansForm() 81 | 82 | if form.validate_on_submit(): 83 | scaler = MinMaxScaler() 84 | 85 | flair_scan = request.files['flair_scan'] 86 | flair_scan_path = secure_filename(flair_scan.filename) 87 | flair_scan.save(os.path.join(SCANS_PATH, flair_scan_path)) 88 | 89 | t1ce_scan = request.files['t1ce_scan'] 90 | t1ce_scan_path = secure_filename(t1ce_scan.filename) 91 | t1ce_scan.save(os.path.join(SCANS_PATH, t1ce_scan_path)) 92 | 93 | t2_scan = request.files['t2_scan'] 94 | t2_scan_path = secure_filename(t2_scan.filename) 95 | t2_scan.save(os.path.join(SCANS_PATH, t2_scan_path)) 96 | 97 | temp_image_flair = nib.load(os.path.join(SCANS_PATH,flair_scan_path)).get_fdata() 98 | temp_image_flair = scaler.fit_transform(temp_image_flair.reshape(-1, temp_image_flair.shape[-1])).reshape(temp_image_flair.shape) 99 | 100 | temp_image_t1ce = nib.load(os.path.join(SCANS_PATH,t1ce_scan_path)).get_fdata() 101 | temp_image_t1ce = scaler.fit_transform(temp_image_t1ce.reshape(-1, temp_image_t1ce.shape[-1])).reshape(temp_image_t1ce.shape) 102 | 103 | temp_image_t2 = nib.load(os.path.join(SCANS_PATH,t2_scan_path)).get_fdata() 104 | temp_image_t2 = scaler.fit_transform(temp_image_t2.reshape(-1, temp_image_t2.shape[-1])).reshape(temp_image_t2.shape) 105 | 106 | temp_combined_images = np.stack([temp_image_flair, temp_image_t1ce, temp_image_t2], axis = 3) 107 | test_scan = temp_combined_images[56:184, 56:184, 13:141] 108 | 109 | test_scan_input = np.expand_dims(test_scan, axis = 0) 110 | 111 | # config = tf.compat.v1.ConfigProto() 112 | 113 | # tf.compat.v1.disable_eager_execution() 114 | # with tf.compat.v1.Session(config = config) as sess: 115 | # with sess.as_default: 116 | 117 | my_model = tf.keras.models.load_model(MODEL_PATH, compile=False) 118 | 119 | test_prediction = my_model.predict(test_scan_input) 120 | test_prediction_argmax = np.argmax(test_prediction, axis = 4)[0, :, :, :] 121 | 122 | n_slice = 40 123 | rand_num = str(random.randint(100, 999)) 124 | file_path = rand_num + '.png' 125 | 126 | plt.figure(figsize=(8,8)) 127 | plt.plot(231) 128 | plt.imshow(test_scan[:, :, n_slice, 1], cmap='gray') 129 | plt.title('Testing Scan') 130 | plt.savefig(os.path.join(SCANS_PATH, file_path)) 131 | 132 | combined_scan = Scan( 133 | scan_file = 'scans/' + file_path, 134 | scan_type = ScanTypes.combined, 135 | patient_id = form.patient.data 136 | ) 137 | 138 | db.session.add(combined_scan) 139 | db.session.commit() 140 | 141 | print('Classes in prediction************ ', np.unique(test_prediction)) 142 | print('Classes in argmax************ ', np.unique(test_prediction_argmax)) 143 | plt.clf() 144 | plt.figure(figsize=(8,8)) 145 | plt.plot(231) 146 | plt.imshow(test_prediction_argmax[:, :, n_slice]) 147 | plt.title('Prediction on test image') 148 | plt.savefig(os.path.join(PREDICTED_PATH, file_path)) 149 | 150 | predicted_scan = Scan( 151 | scan_file = 'predicted/' + file_path, 152 | scan_type = ScanTypes.predicted, 153 | patient_id = form.patient.data 154 | ) 155 | 156 | db.session.add(predicted_scan) 157 | db.session.commit() 158 | 159 | 160 | return redirect(url_for('main_bp.upload_scans')) 161 | 162 | return render_template( 163 | "upload_scans.html", 164 | title="Upload Scans", 165 | form = form, 166 | template = 'signup-page', 167 | neur = Roles.neurologist, 168 | ) 169 | 170 | @main_bp.route('/view_patients', methods=['GET']) 171 | @login_required 172 | def view_patients(): 173 | patients = db.session.query(User).filter(User.role == None) 174 | 175 | return render_template( 176 | 'patients.html', 177 | template = 'dashboard-page', 178 | patients = patients, 179 | body = 'View Patients', 180 | neur = Roles.neurologist, 181 | 182 | ) 183 | 184 | @main_bp.route('/view_scans/', methods=['GET']) 185 | @login_required 186 | def view_scans(patient_ID): 187 | scans_list = list() 188 | print("Patient ID: ", patient_ID) 189 | scans = db.session.query(Scan).filter_by(patient_id = patient_ID) 190 | 191 | patient = db.session.query(User).filter_by(id = patient_ID).first() 192 | 193 | if scans.count() > 0: 194 | for i in range(scans.count() - 1): 195 | scans_list.append({ 196 | 'scan': scans[i].scan_file, 197 | 'predicted': scans[i + 1].scan_file, 198 | 'created_on' : scans[i].created_on.date() 199 | }) 200 | 201 | return render_template('scans.html', 202 | scans = scans_list, 203 | template = 'dashboard-page', 204 | body = 'Scans for: ' + ' ' + str(patient.medical_id) + ' - ' + patient.first_name + ' ' + patient.last_name, 205 | neur = Roles.neurologist, 206 | ) 207 | return redirect('main_bp.view_patients') 208 | 209 | @main_bp.route("/logout") 210 | @login_required 211 | def logout(): 212 | """User log-out logic.""" 213 | logout_user() 214 | return redirect(url_for("auth_bp.login")) -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/css/account.css: -------------------------------------------------------------------------------- 1 | .signup-page, 2 | .login-page { 3 | background: #e1eaf5 4 | } 5 | 6 | .signup-page .form-wrapper, 7 | .login-page .form-wrapper { 8 | background: white; 9 | width: 370px; 10 | max-width: 470px; 11 | margin: 80px auto 0; 12 | box-shadow: 0 0 5px rgba(65, 67, 144, 0.15); 13 | padding: 50px 14 | } 15 | 16 | @media(max-width:600px) { 17 | .signup-page .form-wrapper, 18 | .login-page .form-wrapper { 19 | width: 100%; 20 | margin: 15px auto; 21 | padding: 30px 22 | } 23 | } 24 | 25 | .signup-page .form-wrapper .logo, 26 | .login-page .form-wrapper .logo { 27 | text-align: center; 28 | margin: 0 auto; 29 | } 30 | 31 | .signup-page .form-wrapper .logo img, 32 | .login-page .form-wrapper .logo img { 33 | width: 180px; 34 | margin: auto; 35 | } 36 | 37 | @media(max-width:600px) { 38 | .signup-page .form-wrapper .logo img, 39 | .login-page .form-wrapper .logo img { 40 | width: 25px 41 | } 42 | } 43 | 44 | .signup-page .form-wrapper .alert-warning, 45 | .login-page .form-wrapper .alert-warning { 46 | color: #856404; 47 | background-color: #fff3cd; 48 | border-color: #ffeeba; 49 | font-weight: 300; 50 | font-size: .8em; 51 | margin: 30px 0; 52 | display: flex; 53 | align-items: center; 54 | justify-content: space-between 55 | } 56 | 57 | @media(max-width:600px) { 58 | .signup-page .form-wrapper .alert-warning, 59 | .login-page .form-wrapper .alert-warning { 60 | padding: 10px 15px 61 | } 62 | } 63 | 64 | .signup-page .form-wrapper .alert-warning .close, 65 | .login-page .form-wrapper .alert-warning .close { 66 | color: #856404; 67 | font-weight: 300; 68 | order: 1; 69 | outline: none!important; 70 | transition: all .3s ease-out 71 | } 72 | 73 | .signup-page .form-wrapper .alert, 74 | .login-page .form-wrapper .alert { 75 | position: relative; 76 | padding: .75rem 1.25rem; 77 | margin-bottom: 1rem; 78 | border: 1px solid transparent; 79 | border-radius: .25rem; 80 | font-family: 'Poppins', sans-serif 81 | } 82 | 83 | .signup-page .form-wrapper .alert button.close, 84 | .login-page .form-wrapper .alert button.close { 85 | padding: 0; 86 | background-color: transparent; 87 | border: 0; 88 | -webkit-appearance: none; 89 | float: right; 90 | font-size: 1.5rem; 91 | font-weight: 700; 92 | line-height: 1; 93 | color: #000; 94 | text-shadow: 0 1px 0 #fff; 95 | opacity: .4 96 | } 97 | 98 | .signup-page .form-wrapper .alert button.close:hover, 99 | .login-page .form-wrapper .alert button.close:hover { 100 | cursor: pointer 101 | } 102 | 103 | .signup-page .form-wrapper h1, 104 | .login-page .form-wrapper h1 { 105 | font-size: 1.5rem; 106 | color: #5f6988; 107 | font-weight: 300; 108 | border-bottom: 1px solid #dee2ef; 109 | padding-bottom: 5px; 110 | margin-bottom: 20px; 111 | font-family: 'Poppins', sans-serif 112 | } 113 | 114 | @media(max-width:600px) { 115 | .signup-page .form-wrapper h1, 116 | .login-page .form-wrapper h1 { 117 | font-size: 1.3rem 118 | } 119 | } 120 | 121 | .signup-page .form-wrapper input, 122 | .login-page .form-wrapper input { 123 | padding: 10px 13px; 124 | margin-bottom: 15px; 125 | width: -webkit-fill-available; 126 | width: -moz-available; 127 | border-radius: 2px; 128 | border: 1px solid #d4d9e3; 129 | font-weight: 200; 130 | color: #4d5060; 131 | font-family: 'Poppins', sans-serif; 132 | transition: all .3s ease-out; 133 | font-size: .9em; 134 | outline-color: transparent; 135 | outline-style: none 136 | } 137 | 138 | @media(max-width:600px) { 139 | .signup-page .form-wrapper input, 140 | .login-page .form-wrapper input { 141 | font-size: .9em 142 | } 143 | } 144 | 145 | .signup-page .form-wrapper input::placeholder, 146 | .login-page .form-wrapper input::placeholder { 147 | color: #d4d9e3 148 | } 149 | 150 | .signup-page .form-wrapper input:hover, 151 | .login-page .form-wrapper input:hover { 152 | border-color: #344372; 153 | background: #d9f6ff 154 | } 155 | 156 | .signup-page .form-wrapper input:hover::placeholder, 157 | .login-page .form-wrapper input:hover::placeholder { 158 | color: #344372 159 | } 160 | 161 | .signup-page .form-wrapper input:focus, 162 | .login-page .form-wrapper input:focus { 163 | background: white; 164 | border-color: #344372; 165 | box-shadow: unset 166 | } 167 | 168 | .signup-page .form-wrapper input:focus::placeholder, 169 | .login-page .form-wrapper input:focus::placeholder { 170 | color: #d4d9e3 171 | } 172 | 173 | .signup-page .form-wrapper fieldset, 174 | .login-page .form-wrapper fieldset { 175 | border: none; 176 | padding: 0; 177 | margin: 0 178 | } 179 | 180 | .signup-page .form-wrapper label, 181 | .login-page .form-wrapper label { 182 | font-size: .9em; 183 | color: #5f6988; 184 | margin-bottom: 5px; 185 | display: block; 186 | font-weight: 300; 187 | font-family: 'Poppins', sans-serif 188 | } 189 | 190 | .signup-page .form-wrapper input[type="submit"], 191 | .login-page .form-wrapper input[type="submit"] { 192 | background: #344372; 193 | color: white; 194 | border-radius: 2px; 195 | margin-top: 15px; 196 | font-weight: 400; 197 | border: 1px solid #344372; 198 | transition: all .3s ease-out 199 | } 200 | 201 | .signup-page .form-wrapper input[type="submit"]:hover, 202 | .login-page .form-wrapper input[type="submit"]:hover { 203 | cursor: pointer; 204 | background: white; 205 | color: #344372 206 | } 207 | 208 | .signup-page .form-wrapper .errors, 209 | .login-page .form-wrapper .errors { 210 | margin-top: -11px; 211 | margin-bottom: 1rem; 212 | list-style: none; 213 | padding: 0; 214 | font-size: .9em; 215 | color: #b75353 216 | } 217 | 218 | .signup-page .login-signup, 219 | .login-page .login-signup { 220 | text-align: center; 221 | font-weight: 300; 222 | font-size: .8em; 223 | font-family: 'Poppins', sans-serif; 224 | margin-top: 20px 225 | } 226 | 227 | .signup-page .login-signup a, 228 | .login-page .login-signup a { 229 | color: #344372; 230 | font-weight: 500 231 | } 232 | 233 | nav { 234 | margin: 0; 235 | padding: 30px 50px; 236 | background: #fff; 237 | } 238 | 239 | nav a { 240 | margin-right: 30px; 241 | color: #344372; 242 | text-decoration: none; 243 | font-size: 18px; 244 | } 245 | 246 | .container { 247 | padding: 50px; 248 | } 249 | 250 | h1 { 251 | margin-top: 0; 252 | } 253 | 254 | #right { 255 | float: right; 256 | margin-top: 20px; 257 | } -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/css/dashboard.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: 'Poppins', sans-serif 4 | } 5 | 6 | .dashboard-template { 7 | color: white; 8 | background: #344372 9 | } 10 | 11 | .dashboard-template .container { 12 | text-align: center; 13 | position: absolute; 14 | left: 0; 15 | right: 0; 16 | top: 0; 17 | bottom: 0; 18 | margin: auto; 19 | height: fit-content 20 | } 21 | 22 | .dashboard-template .container h1 { 23 | font-family: 'agenda', sans-serif; 24 | font-weight: 600; 25 | text-shadow: -1px 1px 0 #344372; 26 | line-height: 1 27 | } 28 | 29 | .dashboard-template .container p { 30 | font-size: 1.5em; 31 | font-weight: 300; 32 | text-shadow: 0 0 3px #344372 33 | } 34 | 35 | .dashboard-template .container a { 36 | font-size: 1em; 37 | font-weight: 500; 38 | text-decoration: underline; 39 | margin-top: 50px; 40 | display: block; 41 | color: white; 42 | opacity: .7; 43 | transition: all .3s ease-out 44 | } 45 | 46 | .dashboard-template .container a:hover { 47 | cursor: pointer; 48 | opacity: 1 49 | } 50 | 51 | nav { 52 | margin: 0; 53 | padding: 30px 50px; 54 | background: #e8e8e8; 55 | } 56 | 57 | nav a { 58 | margin-right: 30px; 59 | color: #344372; 60 | text-decoration: none; 61 | } 62 | 63 | .container { 64 | padding: 50px; 65 | } 66 | 67 | h1 { 68 | margin-top: 0; 69 | } 70 | 71 | #right { 72 | float: right; 73 | margin-top: 18px; 74 | } 75 | 76 | .styled-table { 77 | border-collapse: collapse; 78 | margin: 25px auto; 79 | font-size: 0.9em; 80 | font-family: sans-serif; 81 | min-width: 1000px; 82 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); 83 | } 84 | 85 | .styled-table th, 86 | .styled-table td { 87 | padding: 12px 15px; 88 | text-align: center; 89 | } 90 | 91 | .styled-table tbody tr { 92 | border-bottom: 1px solid #dddddd; 93 | font-weight: bold; 94 | color: #344372; 95 | } 96 | 97 | .styled-table tbody tr:nth-of-type(even) { 98 | background-color: #f3f3f3; 99 | } 100 | 101 | .styled-table tbody tr:last-of-type { 102 | border-bottom: 2px solid #344372; 103 | } 104 | 105 | .styled-table tbody tr.active-row { 106 | font-weight: bold; 107 | color: #344372; 108 | } 109 | 110 | * { 111 | box-sizing: border-box; 112 | } 113 | 114 | .img-container { 115 | float: left; 116 | width: 50%; 117 | padding: 5px; 118 | } 119 | 120 | .clearfix::after { 121 | content: ""; 122 | clear: both; 123 | display: table; 124 | } 125 | 126 | 127 | /* Scans Key */ 128 | 129 | .key { 130 | position: relative; 131 | float: left; 132 | width: 30%; 133 | padding: 5px; 134 | } 135 | 136 | .key:before { 137 | position: absolute; 138 | left: -30px; 139 | content: ""; 140 | height: 30px; 141 | width: 30px; 142 | margin-bottom: 15px; 143 | border: 1px solid black; 144 | } 145 | 146 | .key.yellow:before { 147 | background-color: #fde724; 148 | } 149 | 150 | .key.green:before { 151 | background-color: #35b778; 152 | } 153 | 154 | .key.blue:before { 155 | background-color: #30678d; 156 | } -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaryannGitonga/Brain-Tumor-Segmentation-Using-3D-Attention-Based-UNet/95ec3bce489ef42c9e37cb2e4f350f3bd617c0dc/brain_seg_ui/segmentation/static/dist/img/favicon.png -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/img/flasklogin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaryannGitonga/Brain-Tumor-Segmentation-Using-3D-Attention-Based-UNet/95ec3bce489ef42c9e37cb2e4f350f3bd617c0dc/brain_seg_ui/segmentation/static/dist/img/flasklogin@2x.png -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaryannGitonga/Brain-Tumor-Segmentation-Using-3D-Attention-Based-UNet/95ec3bce489ef42c9e37cb2e4f350f3bd617c0dc/brain_seg_ui/segmentation/static/dist/img/logo.png -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/dist/js/main.min.js: -------------------------------------------------------------------------------- 1 | const alertButton=document.querySelector('.alert button');if(alertButton){alertButton.addEventListener('click',function(event){event.preventDefault();alertButton.parentNode.style.display='none';},false);} -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/src/js/main.js: -------------------------------------------------------------------------------- 1 | // Remove Alert on Close 2 | 3 | const alertButton = document.querySelector('.alert button'); 4 | 5 | if (alertButton){ 6 | alertButton.addEventListener('click', function (event) { 7 | event.preventDefault(); 8 | alertButton.parentNode.style.display = 'none'; 9 | }, false); 10 | } 11 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/src/less/account.less: -------------------------------------------------------------------------------- 1 | @import 'vars.less'; 2 | 3 | .signup-page, 4 | .login-page { 5 | background: @background-color; 6 | 7 | .form-wrapper { 8 | background: white; 9 | width: 370px; 10 | max-width: 470px; 11 | margin: 80px auto 0; 12 | box-shadow: @box-shadow; 13 | padding: 50px; 14 | 15 | @media (max-width: 600px) { 16 | width: 100%; 17 | margin: 15px auto; 18 | padding: 30px; 19 | } 20 | 21 | .logo { 22 | text-align: center; 23 | margin: 0 auto 20px; 24 | 25 | img { 26 | width: 30px; 27 | margin: auto; 28 | 29 | @media (max-width: 600px) { 30 | width: 25px; 31 | } 32 | } 33 | } 34 | 35 | .alert-warning { 36 | color: #856404; 37 | background-color: #fff3cd; 38 | border-color: #ffeeba; 39 | font-weight: 300; 40 | font-size: .8em; 41 | margin: 30px 0; 42 | display: flex; 43 | align-items: center; 44 | justify-content: space-between; 45 | @media (max-width: 600px) { 46 | padding: 10px 15px; 47 | } 48 | 49 | .close { 50 | color: #856404; 51 | font-weight: 300; 52 | order: 1; 53 | outline: none !important; 54 | transition: @transition; 55 | } 56 | } 57 | 58 | .alert { 59 | position: relative; 60 | padding: .75rem 1.25rem; 61 | margin-bottom: 1rem; 62 | border: 1px solid transparent; 63 | border-radius: .25rem; 64 | font-family: @body-font; 65 | 66 | button.close { 67 | padding: 0; 68 | background-color: transparent; 69 | border: 0; 70 | -webkit-appearance: none; 71 | float: right; 72 | font-size: 1.5rem; 73 | font-weight: 700; 74 | line-height: 1; 75 | color: #000; 76 | text-shadow: 0 1px 0 #fff; 77 | opacity: .4; 78 | 79 | &:hover { 80 | cursor: pointer; 81 | } 82 | } 83 | } 84 | 85 | h1 { 86 | font-size: 1.5rem; 87 | color: @header-color; 88 | font-weight: 300; 89 | border-bottom: 1px solid #dee2ef; 90 | padding-bottom: 5px; 91 | margin-bottom: 20px; 92 | font-family: @body-font; 93 | 94 | @media (max-width: 600px) { 95 | font-size: 1.3rem; 96 | } 97 | } 98 | 99 | 100 | input { 101 | padding: 10px 13px; 102 | margin-bottom: 15px; 103 | width: -webkit-fill-available; 104 | width: -moz-available; 105 | border-radius: 2px; 106 | border: 1px solid #d4d9e3; 107 | font-weight: 200; 108 | color: #4d5060; 109 | font-family: @body-font; 110 | transition: @transition; 111 | font-size: .9em; 112 | outline-color: transparent; 113 | outline-style: none; 114 | @media (max-width: 600px) { 115 | font-size: .9em; 116 | } 117 | 118 | &::placeholder { 119 | color: #828895; 120 | } 121 | 122 | &:hover { 123 | border-color: @theme-color; 124 | background: #d9f6ff; 125 | 126 | &::placeholder { 127 | color: @theme-color; 128 | } 129 | } 130 | 131 | &:focus { 132 | background: white; 133 | border-color: @theme-color; 134 | box-shadow: unset; 135 | 136 | &::placeholder { 137 | color: #d4d9e3; 138 | } 139 | } 140 | } 141 | 142 | fieldset { 143 | border: none; 144 | padding: 0; 145 | margin: 0; 146 | } 147 | 148 | label { 149 | font-size: .9em; 150 | color: @header-color; 151 | margin-bottom: 5px; 152 | display: block; 153 | font-weight: 300; 154 | font-family: @body-font; 155 | } 156 | 157 | input[type="submit"] { 158 | background: @theme-color; 159 | color: white; 160 | border-radius: 2px; 161 | margin-top: 15px; 162 | font-weight: 400; 163 | border: 1px solid @theme-color; 164 | transition: @transition; 165 | 166 | &:hover { 167 | cursor: pointer; 168 | background: white; 169 | color: @theme-color; 170 | } 171 | } 172 | 173 | .errors { 174 | margin-top: -11px; 175 | margin-bottom: 1rem; 176 | list-style: none; 177 | padding: 0; 178 | font-size: .9em; 179 | color: #b75353; 180 | } 181 | } 182 | 183 | .login-signup { 184 | text-align: center; 185 | font-weight: 300; 186 | font-size: 0.8em; 187 | font-family: @body-font; 188 | margin-top: 20px; 189 | 190 | a { 191 | color: @theme-color; 192 | font-weight: 500; 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/src/less/dashboard.less: -------------------------------------------------------------------------------- 1 | @import 'vars.less'; 2 | 3 | html, 4 | body { 5 | font-family: @body-font; 6 | } 7 | 8 | .dashboard-template { 9 | color: white; 10 | background: @theme-color; 11 | 12 | .container { 13 | text-align: center; 14 | position: absolute; 15 | left: 0; 16 | right: 0; 17 | top: 0; 18 | bottom: 0; 19 | margin: auto; 20 | height: fit-content; 21 | 22 | h1 { 23 | font-family: 'agenda', sans-serif; 24 | font-weight: 600; 25 | text-shadow: -1px 1px 0 #3e798d; 26 | line-height: 1; 27 | } 28 | 29 | p { 30 | font-size: 1.5em; 31 | font-weight: 300; 32 | text-shadow: 0 0 3px #507b89; 33 | } 34 | 35 | a { 36 | font-size: 1em; 37 | font-weight: 500; 38 | text-decoration: underline; 39 | margin-top: 50px; 40 | display: block; 41 | color: white; 42 | opacity: .7; 43 | transition: @transition; 44 | 45 | &:hover{ 46 | cursor: pointer; 47 | opacity: 1; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/static/src/less/vars.less: -------------------------------------------------------------------------------- 1 | // Colors 2 | @theme-color: #344372; 3 | @background-color: #e1eaf5; 4 | @box-shadow: 0 0 5px rgba(65, 67, 144, 0.15); 5 | @header-color: #5f6988; 6 | 7 | // Fonts 8 | @body-font: 'Poppins', sans-serif; 9 | 10 | // Animation 11 | @transition: all .3s ease-out; 12 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block pagestyles %} 2 | 7 | 8 | {% endblock %} {% block content %} 9 |

{{ body }}

10 | 11 | {% if current_user.is_authenticated %} 12 |

Hi {{ current_user.first_name }}!

13 | {% endif %} {% endblock %} 14 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 10 | 11 | 16 | 20 | 21 | 25 | {% block pagestyles %}{% endblock %} 26 | 27 | 28 | 29 |
30 | {% include 'nav.html' %} {% block content %}{% endblock %} 31 |
32 | 33 | {% block additionalscripts %}{% endblock %} 34 | 35 | 36 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block pagestyles %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 11 | {% for message in get_flasked_messages %} 12 |
13 | 16 | {{ message }} 17 |
18 | {% endfor %} 19 | 20 |

Log In

21 | 22 |
23 | {{ form.csrf_token }} 24 | 25 |
26 | {{ form.medical_id.label }} 27 | {{ form.medical_id(placeholder = '1234567') }} 28 | {% if form.medical_id.errors %} 29 |
    30 | {% for error in form.medical_id.errors %} 31 |
  • {{ error }}
  • 32 | {% endfor %} 33 |
34 | {% endif %} 35 |
36 | 37 |
38 | {{ form.password.label }} 39 | {{ form.password }} 40 | {% if form.password.errors %} 41 |
    42 | {% for error in form.password.errors %} 43 |
  • {{ error }}
  • 44 | {% endfor %} 45 |
46 | {% endif %} 47 |
48 | 49 |
50 | {{ form.submit }} 51 |
52 | 53 | 54 | 60 |
61 | {% endblock %} 62 | 63 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/nav.html: -------------------------------------------------------------------------------- 1 | {% if current_user.is_authenticated %} 2 |
3 | 26 |
27 | {% endif %} 28 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/patients.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block pagestyles %} 2 | 7 | 8 | {% endblock %} {% block content %} 9 |

{{ body }}

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for patient in patients %} 21 | 22 | 23 | 24 | 39 | 40 | {% endfor %} 41 | 42 |
Patient IDPatient NameAction
{{ patient.medical_id }}{{ patient.first_name + ' ' + patient.last_name }} 25 | {% if patient.scans %} 26 | View Scans 37 | {% else %} No scans segmented yet {% endif %} 38 |
43 | 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/scans.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block pagestyles %} 2 | 7 | 8 | {% endblock %} {% block content %} 9 | 10 |

{{ body }}

11 | 12 |
13 |

Tumor Regions:

14 |
=> Necrotic tumor
15 |
=> Edema (invaded tissue)
16 |
=> Enhancing tumor
17 |
18 | 19 |
20 | {% for scan in scans %} 21 |

For {{ scan.created_on }}:

22 |
23 | Italy 28 |
29 |
30 | Forest 35 |
36 | {% endfor %} 37 |
38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block pagestyles %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | 12 | {% for message in get_flasked_messages %} 13 |
14 | 17 | {{ message }} 18 |
19 | {% endfor %} 20 | 21 |

Sign Up

22 | 23 |
24 | {{ form.csrf_token }} 25 | 26 |
27 | {{ form.medical_id.label }} 28 | {{ form.medical_id(placeholder = '1234567') }} 29 | {% if form.medical_id.errors %} 30 |
    31 | {% for error in form.medical_id.errors %} 32 |
  • {{ error }}
  • 33 | {% endfor %} 34 |
35 | {% endif %} 36 |
37 | 38 |
39 | {{ form.first_name.label }} 40 | {{ form.first_name(placeholder = 'John') }} 41 | {% if form.first_name.errors %} 42 |
    43 | {% for error in form.first_name.errors %} 44 |
  • {{ error }}
  • 45 | {% endfor %} 46 |
47 | {% endif %} 48 |
49 | 50 |
51 | {{ form.last_name.label }} 52 | {{ form.last_name(placeholder = 'Smith') }} 53 | {% if form.last_name.errors %} 54 |
    55 | {% for error in form.last_name.errors %} 56 |
  • {{ error }}
  • 57 | {% endfor %} 58 |
59 | {% endif %} 60 |
61 | 62 |
63 | {{ form.phone_no.label }} 64 | {{ form.phone_no(placeholder = '0712345678') }} 65 | {% if form.phone_no.errors %} 66 |
    67 | {% for error in form.phone_no.errors %} 68 |
  • {{ error }}
  • 69 | {% endfor %} 70 |
71 | {% endif %} 72 |
73 | 74 |
134 | {% endblock %} -------------------------------------------------------------------------------- /brain_seg_ui/segmentation/templates/upload_scans.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block pagestyles %} 4 | 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 | 13 | {% for message in get_flasked_messages %} 14 |
15 | 18 | {{ message }} 19 |
20 | {% endfor %} 21 | 22 |

Upload Scans

23 | 24 |
25 | {{ form.csrf_token }} 26 | 27 |
28 | {{ form.patient.label }} 29 | {{ form.patient }} 30 | {% if form.patient.errors %} 31 |
    32 | {% for error in form.patient.errors %} 33 |
  • {{ error }}
  • 34 | {% endfor %} 35 |
36 | {% endif %} 37 |
38 | 39 |
40 | {{ form.flair_scan.label }} 41 | {{ form.flair_scan(placeholder = 'FLAIR Scan') }} 42 | {% if form.flair_scan.errors %} 43 |
    44 | {% for error in form.flair_scan.errors %} 45 |
  • {{ error }}
  • 46 | {% endfor %} 47 |
48 | {% endif %} 49 |
50 | 51 |
52 | {{ form.t1ce_scan.label }} 53 | {{ form.t1ce_scan(placeholder = 'T1CE Scan') }} 54 | {% if form.t1ce_scan.errors %} 55 |
    56 | {% for error in form.t1ce_scan.errors %} 57 |
  • {{ error }}
  • 58 | {% endfor %} 59 |
60 | {% endif %} 61 |
62 | 63 |
64 | {{ form.t2_scan.label }} 65 | {{ form.t2_scan(placeholder = 'T2 Scan') }} 66 | {% if form.t2_scan.errors %} 67 |
    68 | {% for error in form.t2_scan.errors %} 69 |
  • {{ error }}
  • 70 | {% endfor %} 71 |
72 | {% endif %} 73 |
74 | 75 |
76 | {{ form.submit }} 77 |
78 | 79 |
80 | {% endblock %} -------------------------------------------------------------------------------- /brain_seg_ui/wsgi.py: -------------------------------------------------------------------------------- 1 | """Application entry point.""" 2 | from segmentation import create_app 3 | 4 | app = create_app() 5 | 6 | if __name__ == "__main__": 7 | app.run(host="0.0.0.0") 8 | --------------------------------------------------------------------------------